@vizzly-testing/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +363 -0
- package/bin/vizzly.js +3 -0
- package/dist/cli.js +104 -0
- package/dist/client/index.js +237 -0
- package/dist/commands/doctor.js +158 -0
- package/dist/commands/init.js +102 -0
- package/dist/commands/run.js +224 -0
- package/dist/commands/status.js +164 -0
- package/dist/commands/tdd.js +212 -0
- package/dist/commands/upload.js +181 -0
- package/dist/container/index.js +184 -0
- package/dist/errors/vizzly-error.js +149 -0
- package/dist/index.js +31 -0
- package/dist/screenshot-wrapper.js +68 -0
- package/dist/sdk/index.js +364 -0
- package/dist/server/index.js +522 -0
- package/dist/services/api-service.js +215 -0
- package/dist/services/base-service.js +154 -0
- package/dist/services/build-manager.js +214 -0
- package/dist/services/screenshot-server.js +96 -0
- package/dist/services/server-manager.js +61 -0
- package/dist/services/service-utils.js +171 -0
- package/dist/services/tdd-service.js +444 -0
- package/dist/services/test-runner.js +210 -0
- package/dist/services/uploader.js +413 -0
- package/dist/types/cli.d.ts +2 -0
- package/dist/types/client/index.d.ts +76 -0
- package/dist/types/commands/doctor.d.ts +11 -0
- package/dist/types/commands/init.d.ts +14 -0
- package/dist/types/commands/run.d.ts +13 -0
- package/dist/types/commands/status.d.ts +13 -0
- package/dist/types/commands/tdd.d.ts +13 -0
- package/dist/types/commands/upload.d.ts +13 -0
- package/dist/types/container/index.d.ts +61 -0
- package/dist/types/errors/vizzly-error.d.ts +75 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/index.js +153 -0
- package/dist/types/screenshot-wrapper.d.ts +27 -0
- package/dist/types/sdk/index.d.ts +108 -0
- package/dist/types/server/index.d.ts +38 -0
- package/dist/types/services/api-service.d.ts +77 -0
- package/dist/types/services/base-service.d.ts +72 -0
- package/dist/types/services/build-manager.d.ts +68 -0
- package/dist/types/services/screenshot-server.d.ts +10 -0
- package/dist/types/services/server-manager.d.ts +8 -0
- package/dist/types/services/service-utils.d.ts +45 -0
- package/dist/types/services/tdd-service.d.ts +55 -0
- package/dist/types/services/test-runner.d.ts +25 -0
- package/dist/types/services/uploader.d.ts +34 -0
- package/dist/types/types/index.d.ts +373 -0
- package/dist/types/utils/colors.d.ts +12 -0
- package/dist/types/utils/config-helpers.d.ts +6 -0
- package/dist/types/utils/config-loader.d.ts +22 -0
- package/dist/types/utils/console-ui.d.ts +61 -0
- package/dist/types/utils/diagnostics.d.ts +69 -0
- package/dist/types/utils/environment-config.d.ts +54 -0
- package/dist/types/utils/environment.d.ts +36 -0
- package/dist/types/utils/error-messages.d.ts +42 -0
- package/dist/types/utils/fetch-utils.d.ts +1 -0
- package/dist/types/utils/framework-detector.d.ts +5 -0
- package/dist/types/utils/git.d.ts +44 -0
- package/dist/types/utils/help.d.ts +11 -0
- package/dist/types/utils/image-comparison.d.ts +42 -0
- package/dist/types/utils/logger-factory.d.ts +26 -0
- package/dist/types/utils/logger.d.ts +79 -0
- package/dist/types/utils/package-info.d.ts +15 -0
- package/dist/types/utils/package.d.ts +1 -0
- package/dist/types/utils/project-detection.d.ts +19 -0
- package/dist/types/utils/ui-helpers.d.ts +23 -0
- package/dist/utils/colors.js +66 -0
- package/dist/utils/config-helpers.js +8 -0
- package/dist/utils/config-loader.js +120 -0
- package/dist/utils/console-ui.js +226 -0
- package/dist/utils/diagnostics.js +184 -0
- package/dist/utils/environment-config.js +93 -0
- package/dist/utils/environment.js +109 -0
- package/dist/utils/error-messages.js +34 -0
- package/dist/utils/fetch-utils.js +9 -0
- package/dist/utils/framework-detector.js +40 -0
- package/dist/utils/git.js +226 -0
- package/dist/utils/help.js +66 -0
- package/dist/utils/image-comparison.js +172 -0
- package/dist/utils/logger-factory.js +76 -0
- package/dist/utils/logger.js +231 -0
- package/dist/utils/package-info.js +38 -0
- package/dist/utils/package.js +9 -0
- package/dist/utils/project-detection.js +145 -0
- package/dist/utils/ui-helpers.js +86 -0
- package/package.json +103 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logger Factory
|
|
3
|
+
* Centralized logger creation with consistent patterns
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createLogger } from './logger.js';
|
|
7
|
+
import { getLogLevel } from './environment-config.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Create a service logger with consistent naming and options
|
|
11
|
+
* @param {string} serviceName - Name of the service (e.g., 'TDD', 'SERVER', 'API')
|
|
12
|
+
* @param {Object} options - Logger options
|
|
13
|
+
* @returns {Logger} Configured logger instance
|
|
14
|
+
*/
|
|
15
|
+
export function createServiceLogger(serviceName, options = {}) {
|
|
16
|
+
return createLogger({
|
|
17
|
+
level: options.level || getLogLevel(),
|
|
18
|
+
verbose: options.verbose || false,
|
|
19
|
+
silent: options.silent || false,
|
|
20
|
+
colors: options.colors !== false,
|
|
21
|
+
logFile: options.logFile,
|
|
22
|
+
prefix: serviceName,
|
|
23
|
+
...options
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Create a component logger for CLI commands and utilities
|
|
29
|
+
* @param {string} componentName - Name of the component
|
|
30
|
+
* @param {Object} options - Logger options
|
|
31
|
+
* @returns {Logger} Configured logger instance
|
|
32
|
+
*/
|
|
33
|
+
export function createComponentLogger(componentName, options = {}) {
|
|
34
|
+
return createLogger({
|
|
35
|
+
level: options.level || 'info',
|
|
36
|
+
verbose: options.verbose || false,
|
|
37
|
+
silent: options.silent || false,
|
|
38
|
+
colors: options.colors !== false,
|
|
39
|
+
logFile: options.logFile,
|
|
40
|
+
prefix: componentName,
|
|
41
|
+
...options
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Create a basic logger with standard defaults
|
|
47
|
+
* @param {Object} options - Logger options
|
|
48
|
+
* @returns {Logger} Configured logger instance
|
|
49
|
+
*/
|
|
50
|
+
export function createStandardLogger(options = {}) {
|
|
51
|
+
return createLogger({
|
|
52
|
+
level: options.level || 'info',
|
|
53
|
+
verbose: options.verbose || false,
|
|
54
|
+
silent: options.silent || false,
|
|
55
|
+
colors: options.colors !== false,
|
|
56
|
+
logFile: options.logFile,
|
|
57
|
+
...options
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Create a logger for uploader service with specific defaults
|
|
63
|
+
* @param {Object} options - Logger options
|
|
64
|
+
* @returns {Logger} Configured logger instance
|
|
65
|
+
*/
|
|
66
|
+
export function createUploaderLogger(options = {}) {
|
|
67
|
+
return createLogger({
|
|
68
|
+
level: options.logLevel || 'info',
|
|
69
|
+
verbose: options.verbose || false,
|
|
70
|
+
silent: options.silent || false,
|
|
71
|
+
colors: options.colors !== false,
|
|
72
|
+
logFile: options.logFile,
|
|
73
|
+
prefix: 'UPLOADER',
|
|
74
|
+
...options
|
|
75
|
+
});
|
|
76
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { writeFileSync, appendFileSync, mkdirSync } from 'fs';
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
import { getEnvironmentDetails } from '../utils/environment.js';
|
|
4
|
+
import { getLogLevel } from './environment-config.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Structured logger with multiple output targets and log levels
|
|
8
|
+
*/
|
|
9
|
+
export class Logger {
|
|
10
|
+
constructor(options = {}) {
|
|
11
|
+
this.level = options.level || 'info';
|
|
12
|
+
this.logFile = options.logFile;
|
|
13
|
+
this.verbose = options.verbose || false;
|
|
14
|
+
this.silent = options.silent || false;
|
|
15
|
+
this.colors = options.colors !== false; // Default to true unless explicitly disabled
|
|
16
|
+
|
|
17
|
+
this.levels = {
|
|
18
|
+
error: 0,
|
|
19
|
+
warn: 1,
|
|
20
|
+
info: 2,
|
|
21
|
+
debug: 3
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Initialize log file if specified
|
|
25
|
+
if (this.logFile) {
|
|
26
|
+
this.initLogFile();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Initialize log file with session header
|
|
32
|
+
*/
|
|
33
|
+
initLogFile() {
|
|
34
|
+
try {
|
|
35
|
+
mkdirSync(dirname(this.logFile), {
|
|
36
|
+
recursive: true
|
|
37
|
+
});
|
|
38
|
+
const sessionHeader = {
|
|
39
|
+
timestamp: new Date().toISOString(),
|
|
40
|
+
session_start: true,
|
|
41
|
+
environment: getEnvironmentDetails(),
|
|
42
|
+
pid: process.pid,
|
|
43
|
+
node_version: process.version,
|
|
44
|
+
platform: process.platform
|
|
45
|
+
};
|
|
46
|
+
writeFileSync(this.logFile, JSON.stringify(sessionHeader) + '\n');
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.error('Failed to initialize log file:', error.message);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Check if message should be logged at current level
|
|
54
|
+
*/
|
|
55
|
+
shouldLog(level) {
|
|
56
|
+
return this.levels[level] <= this.levels[this.level];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Log a message with specified level
|
|
61
|
+
*/
|
|
62
|
+
log(level, message, data = {}) {
|
|
63
|
+
if (!this.shouldLog(level)) return;
|
|
64
|
+
const logEntry = {
|
|
65
|
+
timestamp: new Date().toISOString(),
|
|
66
|
+
level,
|
|
67
|
+
message,
|
|
68
|
+
...data
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// Write to log file if configured
|
|
72
|
+
if (this.logFile) {
|
|
73
|
+
try {
|
|
74
|
+
appendFileSync(this.logFile, JSON.stringify(logEntry) + '\n');
|
|
75
|
+
} catch {
|
|
76
|
+
// Silently fail to avoid infinite loops
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Output to console unless silent
|
|
81
|
+
if (!this.silent) {
|
|
82
|
+
this.outputToConsole(level, message, data);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Output formatted message to console
|
|
88
|
+
*/
|
|
89
|
+
outputToConsole(level, message, data) {
|
|
90
|
+
const prefix = this.getColoredPrefix(level);
|
|
91
|
+
const formattedMessage = `${prefix} ${message}`;
|
|
92
|
+
|
|
93
|
+
// Use appropriate console method
|
|
94
|
+
switch (level) {
|
|
95
|
+
case 'error':
|
|
96
|
+
console.error(formattedMessage);
|
|
97
|
+
if (this.verbose && data.stack) {
|
|
98
|
+
console.error(data.stack);
|
|
99
|
+
}
|
|
100
|
+
break;
|
|
101
|
+
case 'warn':
|
|
102
|
+
console.warn(formattedMessage);
|
|
103
|
+
break;
|
|
104
|
+
case 'debug':
|
|
105
|
+
if (this.verbose) {
|
|
106
|
+
console.log(formattedMessage);
|
|
107
|
+
if (Object.keys(data).length > 0) {
|
|
108
|
+
console.log(' Data:', JSON.stringify(data, null, 2));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
break;
|
|
112
|
+
default:
|
|
113
|
+
console.log(formattedMessage);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Get colored prefix for log level
|
|
119
|
+
*/
|
|
120
|
+
getColoredPrefix(level) {
|
|
121
|
+
if (!this.colors) {
|
|
122
|
+
return `[${level.toUpperCase()}]`;
|
|
123
|
+
}
|
|
124
|
+
const colors = {
|
|
125
|
+
error: '\x1b[31m✖\x1b[0m',
|
|
126
|
+
// Red X
|
|
127
|
+
warn: '\x1b[33m⚠\x1b[0m',
|
|
128
|
+
// Yellow warning
|
|
129
|
+
info: '\x1b[36mℹ\x1b[0m',
|
|
130
|
+
// Cyan info
|
|
131
|
+
debug: '\x1b[35m🔍\x1b[0m' // Magenta debug
|
|
132
|
+
};
|
|
133
|
+
return colors[level] || `[${level.toUpperCase()}]`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Convenience methods
|
|
138
|
+
*/
|
|
139
|
+
error(message, data = {}) {
|
|
140
|
+
this.log('error', message, data);
|
|
141
|
+
}
|
|
142
|
+
warn(message, data = {}) {
|
|
143
|
+
this.log('warn', message, data);
|
|
144
|
+
}
|
|
145
|
+
info(message, data = {}) {
|
|
146
|
+
this.log('info', message, data);
|
|
147
|
+
}
|
|
148
|
+
debug(message, data = {}) {
|
|
149
|
+
this.log('debug', message, data);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Log progress updates
|
|
154
|
+
*/
|
|
155
|
+
progress(stage, message, data = {}) {
|
|
156
|
+
this.info(`[${stage}] ${message}`, data);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Log command execution
|
|
161
|
+
*/
|
|
162
|
+
command(command, data = {}) {
|
|
163
|
+
this.debug(`Executing command: ${command}`, data);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Log performance metrics
|
|
168
|
+
*/
|
|
169
|
+
perf(operation, duration, data = {}) {
|
|
170
|
+
this.debug(`${operation} completed in ${duration}ms`, data);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Create child logger with additional context
|
|
175
|
+
*/
|
|
176
|
+
child(context = {}) {
|
|
177
|
+
return new ChildLogger(this, context);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Child logger that inherits from parent with additional context
|
|
183
|
+
*/
|
|
184
|
+
class ChildLogger {
|
|
185
|
+
constructor(parent, context) {
|
|
186
|
+
this.parent = parent;
|
|
187
|
+
this.context = context;
|
|
188
|
+
}
|
|
189
|
+
log(level, message, data = {}) {
|
|
190
|
+
this.parent.log(level, message, {
|
|
191
|
+
...this.context,
|
|
192
|
+
...data
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
error(message, data = {}) {
|
|
196
|
+
this.log('error', message, data);
|
|
197
|
+
}
|
|
198
|
+
warn(message, data = {}) {
|
|
199
|
+
this.log('warn', message, data);
|
|
200
|
+
}
|
|
201
|
+
info(message, data = {}) {
|
|
202
|
+
this.log('info', message, data);
|
|
203
|
+
}
|
|
204
|
+
debug(message, data = {}) {
|
|
205
|
+
this.log('debug', message, data);
|
|
206
|
+
}
|
|
207
|
+
progress(stage, message, data = {}) {
|
|
208
|
+
this.info(`[${stage}] ${message}`, data);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Create default logger instance
|
|
214
|
+
*/
|
|
215
|
+
export function createLogger(options = {}) {
|
|
216
|
+
// Auto-detect color support
|
|
217
|
+
const supportsColor = process.stdout.isTTY && process.env.TERM !== 'dumb' && !process.env.NO_COLOR && !options.noColor;
|
|
218
|
+
|
|
219
|
+
// Determine log level from environment
|
|
220
|
+
const level = options.level || getLogLevel();
|
|
221
|
+
|
|
222
|
+
// Create log file path if verbose mode
|
|
223
|
+
const logFile = options.verbose ? join(process.cwd(), '.vizzly', 'debug.log') : options.logFile;
|
|
224
|
+
return new Logger({
|
|
225
|
+
level,
|
|
226
|
+
logFile,
|
|
227
|
+
verbose: options.verbose,
|
|
228
|
+
silent: options.silent,
|
|
229
|
+
colors: supportsColor && options.colors !== false
|
|
230
|
+
});
|
|
231
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package information utility
|
|
3
|
+
* Centralized access to package.json data
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { readFileSync } from 'fs';
|
|
7
|
+
import { dirname, join } from 'path';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = dirname(__filename);
|
|
11
|
+
let packageJson;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Get package.json information
|
|
15
|
+
* @returns {Object} Package.json data
|
|
16
|
+
*/
|
|
17
|
+
export function getPackageInfo() {
|
|
18
|
+
if (!packageJson) {
|
|
19
|
+
packageJson = JSON.parse(readFileSync(join(__dirname, '../../package.json'), 'utf-8'));
|
|
20
|
+
}
|
|
21
|
+
return packageJson;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get package version
|
|
26
|
+
* @returns {string} Package version
|
|
27
|
+
*/
|
|
28
|
+
export function getPackageVersion() {
|
|
29
|
+
return getPackageInfo().version;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Get package name
|
|
34
|
+
* @returns {string} Package name
|
|
35
|
+
*/
|
|
36
|
+
export function getPackageName() {
|
|
37
|
+
return getPackageInfo().name;
|
|
38
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { readFile } from 'fs/promises';
|
|
2
|
+
import { resolve, dirname } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
5
|
+
export async function getPackageInfo() {
|
|
6
|
+
const packagePath = resolve(__dirname, '../../package.json');
|
|
7
|
+
const content = await readFile(packagePath, 'utf-8');
|
|
8
|
+
return JSON.parse(content);
|
|
9
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Detection Utilities
|
|
3
|
+
* Automatically detect project type and framework
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { readFile, access } from 'fs/promises';
|
|
7
|
+
import { join } from 'path';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Detect project type and framework
|
|
11
|
+
* @param {string} directory - Directory to analyze
|
|
12
|
+
* @returns {Promise<Object>} Project information
|
|
13
|
+
*/
|
|
14
|
+
export async function detectProjectType(directory = process.cwd()) {
|
|
15
|
+
const packageJsonPath = join(directory, 'package.json');
|
|
16
|
+
let packageJsonData = null;
|
|
17
|
+
try {
|
|
18
|
+
const content = await readFile(packageJsonPath, 'utf8');
|
|
19
|
+
packageJsonData = JSON.parse(content);
|
|
20
|
+
} catch {
|
|
21
|
+
// No package.json found
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Detect framework based on dependencies and files
|
|
25
|
+
const framework = await detectFramework(directory, packageJsonData);
|
|
26
|
+
const type = detectProjectTypeFromFramework(framework);
|
|
27
|
+
return {
|
|
28
|
+
type,
|
|
29
|
+
framework,
|
|
30
|
+
hasPackageJson: !!packageJsonData,
|
|
31
|
+
projectName: packageJsonData?.name || 'unknown',
|
|
32
|
+
dependencies: packageJsonData?.dependencies || {},
|
|
33
|
+
devDependencies: packageJsonData?.devDependencies || {}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Detect testing framework
|
|
39
|
+
* @param {string} directory - Directory to analyze
|
|
40
|
+
* @param {Object} packageJson - Package.json content
|
|
41
|
+
* @returns {Promise<string>} Framework name
|
|
42
|
+
*/
|
|
43
|
+
async function detectFramework(directory, packageJson) {
|
|
44
|
+
const dependencies = {
|
|
45
|
+
...packageJson?.dependencies,
|
|
46
|
+
...packageJson?.devDependencies
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// Check for specific framework dependencies
|
|
50
|
+
if (dependencies.cypress) {
|
|
51
|
+
return 'cypress';
|
|
52
|
+
}
|
|
53
|
+
if (dependencies.playwright || dependencies['@playwright/test']) {
|
|
54
|
+
return 'playwright';
|
|
55
|
+
}
|
|
56
|
+
if (dependencies.webdriverio || dependencies['@wdio/cli']) {
|
|
57
|
+
return 'webdriver';
|
|
58
|
+
}
|
|
59
|
+
if (dependencies.jest || dependencies['@jest/core']) {
|
|
60
|
+
return 'jest';
|
|
61
|
+
}
|
|
62
|
+
if (dependencies.vitest) {
|
|
63
|
+
return 'vitest';
|
|
64
|
+
}
|
|
65
|
+
if (dependencies.mocha) {
|
|
66
|
+
return 'mocha';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Check for config files
|
|
70
|
+
const configFiles = ['cypress.config.js', 'playwright.config.js', 'wdio.conf.js', 'jest.config.js', 'vitest.config.js'];
|
|
71
|
+
for (const configFile of configFiles) {
|
|
72
|
+
try {
|
|
73
|
+
await access(join(directory, configFile));
|
|
74
|
+
return configFile.split('.')[0];
|
|
75
|
+
} catch {
|
|
76
|
+
// File doesn't exist
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return 'generic';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Determine project type from framework
|
|
84
|
+
* @param {string} framework - Framework name
|
|
85
|
+
* @returns {string} Project type
|
|
86
|
+
*/
|
|
87
|
+
function detectProjectTypeFromFramework(framework) {
|
|
88
|
+
const e2eFrameworks = ['cypress', 'playwright', 'webdriver'];
|
|
89
|
+
if (e2eFrameworks.includes(framework)) {
|
|
90
|
+
return 'e2e';
|
|
91
|
+
}
|
|
92
|
+
return 'web';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Get suggested test command for framework
|
|
97
|
+
* @param {string} framework - Framework name
|
|
98
|
+
* @param {Object} packageJson - Package.json content
|
|
99
|
+
* @returns {string} Suggested test command
|
|
100
|
+
*/
|
|
101
|
+
export function getSuggestedTestCommand(framework, packageJson) {
|
|
102
|
+
const scripts = packageJson?.scripts || {};
|
|
103
|
+
|
|
104
|
+
// Check for common script names
|
|
105
|
+
const testScripts = ['test:e2e', 'test:integration', 'e2e', 'cypress:run', 'playwright:test', 'test'];
|
|
106
|
+
for (const script of testScripts) {
|
|
107
|
+
if (scripts[script]) {
|
|
108
|
+
return `npm run ${script}`;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Framework-specific defaults
|
|
113
|
+
switch (framework) {
|
|
114
|
+
case 'cypress':
|
|
115
|
+
return 'npx cypress run';
|
|
116
|
+
case 'playwright':
|
|
117
|
+
return 'npx playwright test';
|
|
118
|
+
case 'webdriver':
|
|
119
|
+
return 'npx wdio run';
|
|
120
|
+
case 'jest':
|
|
121
|
+
return 'npx jest';
|
|
122
|
+
case 'vitest':
|
|
123
|
+
return 'npx vitest run';
|
|
124
|
+
default:
|
|
125
|
+
return 'npm test';
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Get suggested screenshots directory for framework
|
|
131
|
+
* @param {string} framework - Framework name
|
|
132
|
+
* @returns {string} Screenshots directory
|
|
133
|
+
*/
|
|
134
|
+
export function getSuggestedScreenshotsDir(framework) {
|
|
135
|
+
switch (framework) {
|
|
136
|
+
case 'cypress':
|
|
137
|
+
return './cypress/screenshots';
|
|
138
|
+
case 'playwright':
|
|
139
|
+
return './test-results';
|
|
140
|
+
case 'webdriver':
|
|
141
|
+
return './screenshots';
|
|
142
|
+
default:
|
|
143
|
+
return './screenshots';
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Color and styling utilities for Ink components
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Check if colors should be used
|
|
7
|
+
*/
|
|
8
|
+
export function shouldUseColors(flags = {}) {
|
|
9
|
+
return !flags.noColor && !process.env.NO_COLOR && process.stdout.isTTY && process.env.TERM !== 'dumb';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Get color for text based on type and color support
|
|
14
|
+
*/
|
|
15
|
+
export function getColor(type, flags = {}) {
|
|
16
|
+
if (!shouldUseColors(flags)) {
|
|
17
|
+
return undefined; // No color
|
|
18
|
+
}
|
|
19
|
+
const colors = {
|
|
20
|
+
success: 'green',
|
|
21
|
+
error: 'red',
|
|
22
|
+
warning: 'yellow',
|
|
23
|
+
info: 'blue',
|
|
24
|
+
progress: 'cyan',
|
|
25
|
+
dim: 'gray'
|
|
26
|
+
};
|
|
27
|
+
return colors[type];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Get status icon with fallback for no-color mode
|
|
32
|
+
*/
|
|
33
|
+
export function getStatusIcon(status, flags = {}) {
|
|
34
|
+
if (shouldUseColors(flags)) {
|
|
35
|
+
// Colored emoji icons
|
|
36
|
+
const icons = {
|
|
37
|
+
success: '✅',
|
|
38
|
+
error: '❌',
|
|
39
|
+
warning: '⚠️',
|
|
40
|
+
progress: '🔄',
|
|
41
|
+
uploading: '📤',
|
|
42
|
+
waiting: '⏳',
|
|
43
|
+
running: '🧪',
|
|
44
|
+
starting: '🚀'
|
|
45
|
+
};
|
|
46
|
+
return icons[status] || '•';
|
|
47
|
+
} else {
|
|
48
|
+
// Text-only fallback
|
|
49
|
+
const icons = {
|
|
50
|
+
success: '[OK]',
|
|
51
|
+
error: '[ERR]',
|
|
52
|
+
warning: '[WARN]',
|
|
53
|
+
progress: '[...]',
|
|
54
|
+
uploading: '[UP]',
|
|
55
|
+
waiting: '[WAIT]',
|
|
56
|
+
running: '[RUN]',
|
|
57
|
+
starting: '[START]'
|
|
58
|
+
};
|
|
59
|
+
return icons[status] || '[•]';
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Responsive layout helper
|
|
65
|
+
*/
|
|
66
|
+
export function getLayout(columns = process.stdout.columns || 80) {
|
|
67
|
+
if (columns < 60) {
|
|
68
|
+
return {
|
|
69
|
+
type: 'compact',
|
|
70
|
+
showDetails: false,
|
|
71
|
+
maxWidth: columns - 4
|
|
72
|
+
};
|
|
73
|
+
} else if (columns < 100) {
|
|
74
|
+
return {
|
|
75
|
+
type: 'normal',
|
|
76
|
+
showDetails: true,
|
|
77
|
+
maxWidth: columns - 8
|
|
78
|
+
};
|
|
79
|
+
} else {
|
|
80
|
+
return {
|
|
81
|
+
type: 'wide',
|
|
82
|
+
showDetails: true,
|
|
83
|
+
maxWidth: Math.min(columns - 16, 120)
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vizzly-testing/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Visual review platform for UI developers and designers",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"visual-testing",
|
|
7
|
+
"screenshot-testing",
|
|
8
|
+
"visual-regression",
|
|
9
|
+
"visual-review",
|
|
10
|
+
"ui-testing",
|
|
11
|
+
"collaboration",
|
|
12
|
+
"testing",
|
|
13
|
+
"cli"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://vizzly.dev",
|
|
16
|
+
"bugs": "https://github.com/vizzly/cli/issues",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "https://github.com/vizzly/cli.git"
|
|
20
|
+
},
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"author": "Stubborn Mule Software <support@vizzly.dev>",
|
|
23
|
+
"type": "module",
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"import": "./dist/index.js",
|
|
27
|
+
"require": "./dist/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./client": {
|
|
30
|
+
"import": "./dist/client/index.js",
|
|
31
|
+
"require": "./dist/client/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./sdk": {
|
|
34
|
+
"import": "./dist/sdk/index.js",
|
|
35
|
+
"require": "./dist/sdk/index.js"
|
|
36
|
+
},
|
|
37
|
+
"./config": {
|
|
38
|
+
"import": "./dist/utils/config-helpers.js",
|
|
39
|
+
"require": "./dist/utils/config-helpers.js"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"main": "./dist/index.js",
|
|
43
|
+
"types": "./dist/types/index.d.ts",
|
|
44
|
+
"bin": {
|
|
45
|
+
"vizzly": "./bin/vizzly.js"
|
|
46
|
+
},
|
|
47
|
+
"files": [
|
|
48
|
+
"bin",
|
|
49
|
+
"dist",
|
|
50
|
+
"README.md",
|
|
51
|
+
"LICENSE"
|
|
52
|
+
],
|
|
53
|
+
"scripts": {
|
|
54
|
+
"start": "node src/index.js",
|
|
55
|
+
"build": "npm run clean && npm run compile && npm run types",
|
|
56
|
+
"clean": "rimraf dist",
|
|
57
|
+
"compile": "babel src --out-dir dist --ignore '**/*.test.js'",
|
|
58
|
+
"types": "tsc --emitDeclarationOnly --outDir dist/types",
|
|
59
|
+
"prepublishOnly": "npm test && npm run build",
|
|
60
|
+
"test": "vitest run",
|
|
61
|
+
"test:watch": "vitest",
|
|
62
|
+
"lint": "eslint src tests",
|
|
63
|
+
"lint:fix": "eslint src tests --fix",
|
|
64
|
+
"format": "prettier --write src tests",
|
|
65
|
+
"format:check": "prettier --check src tests"
|
|
66
|
+
},
|
|
67
|
+
"engines": {
|
|
68
|
+
"node": ">=20.0.0"
|
|
69
|
+
},
|
|
70
|
+
"publishConfig": {
|
|
71
|
+
"access": "public",
|
|
72
|
+
"registry": "https://registry.npmjs.org/"
|
|
73
|
+
},
|
|
74
|
+
"dependencies": {
|
|
75
|
+
"@babel/runtime": "^7.23.6",
|
|
76
|
+
"@vitejs/plugin-react": "^4.7.0",
|
|
77
|
+
"commander": "^11.1.0",
|
|
78
|
+
"cosmiconfig": "^9.0.0",
|
|
79
|
+
"form-data": "^4.0.0",
|
|
80
|
+
"glob": "^10.3.10",
|
|
81
|
+
"inquirer": "^12.9.1",
|
|
82
|
+
"odiff-bin": "^3.2.1"
|
|
83
|
+
},
|
|
84
|
+
"devDependencies": {
|
|
85
|
+
"@babel/cli": "^7.28.0",
|
|
86
|
+
"@babel/core": "^7.23.6",
|
|
87
|
+
"@babel/preset-env": "^7.23.6",
|
|
88
|
+
"@babel/preset-react": "^7.27.1",
|
|
89
|
+
"@babel/preset-typescript": "^7.23.6",
|
|
90
|
+
"@eslint/js": "^9.31.0",
|
|
91
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
92
|
+
"babel-plugin-transform-remove-console": "^6.9.4",
|
|
93
|
+
"eslint": "^9.31.0",
|
|
94
|
+
"eslint-config-prettier": "^10.1.8",
|
|
95
|
+
"eslint-plugin-prettier": "^5.5.3",
|
|
96
|
+
"prettier": "^3.6.2",
|
|
97
|
+
"puppeteer": "^24.16.1",
|
|
98
|
+
"rimraf": "^5.0.5",
|
|
99
|
+
"typescript": "^5.0.4",
|
|
100
|
+
"vite": "^7.1.2",
|
|
101
|
+
"vitest": "^3.2.4"
|
|
102
|
+
}
|
|
103
|
+
}
|