@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,120 @@
|
|
|
1
|
+
import { cosmiconfigSync } from 'cosmiconfig';
|
|
2
|
+
import { resolve } from 'path';
|
|
3
|
+
import { getApiToken, getApiUrl } from './environment-config.js';
|
|
4
|
+
const DEFAULT_CONFIG = {
|
|
5
|
+
// API Configuration
|
|
6
|
+
apiKey: getApiToken(),
|
|
7
|
+
apiUrl: getApiUrl(),
|
|
8
|
+
// Server Configuration (for run command)
|
|
9
|
+
server: {
|
|
10
|
+
port: 47392,
|
|
11
|
+
timeout: 30000,
|
|
12
|
+
screenshotPath: '/screenshot'
|
|
13
|
+
},
|
|
14
|
+
// Build Configuration
|
|
15
|
+
build: {
|
|
16
|
+
name: 'Build {timestamp}',
|
|
17
|
+
environment: 'test'
|
|
18
|
+
},
|
|
19
|
+
// Upload Configuration (for upload command)
|
|
20
|
+
upload: {
|
|
21
|
+
screenshotsDir: './screenshots',
|
|
22
|
+
batchSize: 10,
|
|
23
|
+
timeout: 30000
|
|
24
|
+
},
|
|
25
|
+
// Comparison Configuration
|
|
26
|
+
comparison: {
|
|
27
|
+
threshold: 0.1
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
export async function loadConfig(configPath = null, cliOverrides = {}) {
|
|
31
|
+
// Create a proper clone of the default config to avoid shared object references
|
|
32
|
+
const config = {
|
|
33
|
+
...DEFAULT_CONFIG,
|
|
34
|
+
server: {
|
|
35
|
+
...DEFAULT_CONFIG.server
|
|
36
|
+
},
|
|
37
|
+
build: {
|
|
38
|
+
...DEFAULT_CONFIG.build
|
|
39
|
+
},
|
|
40
|
+
upload: {
|
|
41
|
+
...DEFAULT_CONFIG.upload
|
|
42
|
+
},
|
|
43
|
+
comparison: {
|
|
44
|
+
...DEFAULT_CONFIG.comparison
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// 1. Load from config file using cosmiconfig
|
|
49
|
+
const explorer = cosmiconfigSync('vizzly');
|
|
50
|
+
const result = configPath ? explorer.load(configPath) : explorer.search();
|
|
51
|
+
if (result && result.config) {
|
|
52
|
+
mergeConfig(config, result.config);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 2. Override with environment variables
|
|
56
|
+
const envApiKey = getApiToken();
|
|
57
|
+
const envApiUrl = getApiUrl();
|
|
58
|
+
if (envApiKey) config.apiKey = envApiKey;
|
|
59
|
+
if (envApiUrl !== 'https://vizzly.dev') config.apiUrl = envApiUrl;
|
|
60
|
+
|
|
61
|
+
// 3. Apply CLI overrides (highest priority)
|
|
62
|
+
applyCLIOverrides(config, cliOverrides);
|
|
63
|
+
return config;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Apply CLI option overrides to config
|
|
68
|
+
* @param {Object} config - The config object to modify
|
|
69
|
+
* @param {Object} cliOverrides - CLI options to apply
|
|
70
|
+
*/
|
|
71
|
+
function applyCLIOverrides(config, cliOverrides = {}) {
|
|
72
|
+
// Global overrides
|
|
73
|
+
if (cliOverrides.token) config.apiKey = cliOverrides.token;
|
|
74
|
+
|
|
75
|
+
// Build-related overrides
|
|
76
|
+
if (cliOverrides.buildName) config.build.name = cliOverrides.buildName;
|
|
77
|
+
if (cliOverrides.environment) config.build.environment = cliOverrides.environment;
|
|
78
|
+
if (cliOverrides.branch) config.build.branch = cliOverrides.branch;
|
|
79
|
+
if (cliOverrides.commit) config.build.commit = cliOverrides.commit;
|
|
80
|
+
if (cliOverrides.message) config.build.message = cliOverrides.message;
|
|
81
|
+
|
|
82
|
+
// Server overrides
|
|
83
|
+
if (cliOverrides.port) config.server.port = parseInt(cliOverrides.port, 10);
|
|
84
|
+
if (cliOverrides.timeout) config.server.timeout = parseInt(cliOverrides.timeout, 10);
|
|
85
|
+
|
|
86
|
+
// Upload overrides
|
|
87
|
+
if (cliOverrides.batchSize !== undefined) {
|
|
88
|
+
config.upload.batchSize = parseInt(cliOverrides.batchSize, 10);
|
|
89
|
+
}
|
|
90
|
+
if (cliOverrides.uploadTimeout !== undefined) {
|
|
91
|
+
config.upload.timeout = parseInt(cliOverrides.uploadTimeout, 10);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Comparison overrides
|
|
95
|
+
if (cliOverrides.threshold !== undefined) config.comparison.threshold = cliOverrides.threshold;
|
|
96
|
+
|
|
97
|
+
// Baseline overrides
|
|
98
|
+
if (cliOverrides.baselineBuild) config.baselineBuildId = cliOverrides.baselineBuild;
|
|
99
|
+
if (cliOverrides.baselineComparison) config.baselineComparisonId = cliOverrides.baselineComparison;
|
|
100
|
+
|
|
101
|
+
// Behavior flags
|
|
102
|
+
if (cliOverrides.eager !== undefined) config.eager = cliOverrides.eager;
|
|
103
|
+
if (cliOverrides.wait !== undefined) config.wait = cliOverrides.wait;
|
|
104
|
+
if (cliOverrides.allowNoToken !== undefined) config.allowNoToken = cliOverrides.allowNoToken;
|
|
105
|
+
}
|
|
106
|
+
function mergeConfig(target, source) {
|
|
107
|
+
for (const key in source) {
|
|
108
|
+
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
|
|
109
|
+
if (!target[key]) target[key] = {};
|
|
110
|
+
mergeConfig(target[key], source[key]);
|
|
111
|
+
} else {
|
|
112
|
+
target[key] = source[key];
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
export function getScreenshotPaths(config) {
|
|
117
|
+
const screenshotsDir = config.upload?.screenshotsDir || './screenshots';
|
|
118
|
+
const paths = Array.isArray(screenshotsDir) ? screenshotsDir : [screenshotsDir];
|
|
119
|
+
return paths.map(p => resolve(process.cwd(), p));
|
|
120
|
+
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { createColors } from './colors.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Simple console UI utilities for CLI output
|
|
5
|
+
*/
|
|
6
|
+
export class ConsoleUI {
|
|
7
|
+
constructor(options = {}) {
|
|
8
|
+
this.colors = createColors({
|
|
9
|
+
useColor: options.color !== false
|
|
10
|
+
});
|
|
11
|
+
this.json = options.json || false;
|
|
12
|
+
this.verbose = options.verbose || false;
|
|
13
|
+
this.spinner = null;
|
|
14
|
+
this.lastLine = '';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Show a success message
|
|
19
|
+
*/
|
|
20
|
+
success(message, data = {}) {
|
|
21
|
+
this.stopSpinner();
|
|
22
|
+
if (this.json) {
|
|
23
|
+
console.log(JSON.stringify({
|
|
24
|
+
status: 'success',
|
|
25
|
+
message,
|
|
26
|
+
timestamp: new Date().toISOString(),
|
|
27
|
+
...data
|
|
28
|
+
}));
|
|
29
|
+
} else {
|
|
30
|
+
console.log(this.colors.green(`✓ ${message}`));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Show an error message and exit
|
|
36
|
+
*/
|
|
37
|
+
error(message, error = {}, exitCode = 1) {
|
|
38
|
+
this.stopSpinner();
|
|
39
|
+
if (this.json) {
|
|
40
|
+
const errorData = {
|
|
41
|
+
status: 'error',
|
|
42
|
+
message,
|
|
43
|
+
timestamp: new Date().toISOString()
|
|
44
|
+
};
|
|
45
|
+
if (error instanceof Error) {
|
|
46
|
+
errorData.error = {
|
|
47
|
+
name: error.name,
|
|
48
|
+
message: error.message,
|
|
49
|
+
...(this.verbose && {
|
|
50
|
+
stack: error.stack
|
|
51
|
+
})
|
|
52
|
+
};
|
|
53
|
+
} else if (typeof error === 'object') {
|
|
54
|
+
errorData.error = error;
|
|
55
|
+
}
|
|
56
|
+
console.error(JSON.stringify(errorData));
|
|
57
|
+
} else {
|
|
58
|
+
console.error(this.colors.red(`✖ ${message}`));
|
|
59
|
+
if (this.verbose && error.stack) {
|
|
60
|
+
console.error(this.colors.dim(error.stack));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (exitCode > 0) {
|
|
64
|
+
process.exit(exitCode);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Show an info message
|
|
70
|
+
*/
|
|
71
|
+
info(message, data = {}) {
|
|
72
|
+
if (this.json) {
|
|
73
|
+
console.log(JSON.stringify({
|
|
74
|
+
status: 'info',
|
|
75
|
+
message,
|
|
76
|
+
timestamp: new Date().toISOString(),
|
|
77
|
+
...data
|
|
78
|
+
}));
|
|
79
|
+
} else {
|
|
80
|
+
console.log(this.colors.cyan(`ℹ ${message}`));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Show a warning message
|
|
86
|
+
*/
|
|
87
|
+
warning(message, data = {}) {
|
|
88
|
+
if (this.json) {
|
|
89
|
+
console.log(JSON.stringify({
|
|
90
|
+
status: 'warning',
|
|
91
|
+
message,
|
|
92
|
+
timestamp: new Date().toISOString(),
|
|
93
|
+
...data
|
|
94
|
+
}));
|
|
95
|
+
} else {
|
|
96
|
+
console.log(this.colors.yellow(`⚠ ${message}`));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Show progress with spinner
|
|
102
|
+
*/
|
|
103
|
+
progress(message, current = 0, total = 0) {
|
|
104
|
+
if (this.json) {
|
|
105
|
+
console.log(JSON.stringify({
|
|
106
|
+
status: 'progress',
|
|
107
|
+
message,
|
|
108
|
+
progress: {
|
|
109
|
+
current,
|
|
110
|
+
total
|
|
111
|
+
},
|
|
112
|
+
timestamp: new Date().toISOString()
|
|
113
|
+
}));
|
|
114
|
+
} else {
|
|
115
|
+
this.updateSpinner(message, current, total);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Update a status line in place (for dynamic updates)
|
|
121
|
+
*/
|
|
122
|
+
updateStatus(line, message) {
|
|
123
|
+
if (this.json) {
|
|
124
|
+
console.log(JSON.stringify({
|
|
125
|
+
status: 'update',
|
|
126
|
+
line,
|
|
127
|
+
message,
|
|
128
|
+
timestamp: new Date().toISOString()
|
|
129
|
+
}));
|
|
130
|
+
} else {
|
|
131
|
+
// Move cursor up to the target line and overwrite it
|
|
132
|
+
process.stdout.write(`\u001b[${line}A`); // Move up
|
|
133
|
+
process.stdout.write('\u001b[2K'); // Clear line
|
|
134
|
+
process.stdout.write('\r'); // Move to beginning
|
|
135
|
+
console.log(this.colors.blue(`ℹ ${message}`));
|
|
136
|
+
process.stdout.write(`\u001b[${line}B`); // Move back down
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Output structured data
|
|
142
|
+
*/
|
|
143
|
+
data(data) {
|
|
144
|
+
if (this.json) {
|
|
145
|
+
console.log(JSON.stringify({
|
|
146
|
+
status: 'data',
|
|
147
|
+
data,
|
|
148
|
+
timestamp: new Date().toISOString()
|
|
149
|
+
}));
|
|
150
|
+
} else {
|
|
151
|
+
console.log(JSON.stringify(data, null, 2));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Start a spinner with message
|
|
157
|
+
*/
|
|
158
|
+
startSpinner(message) {
|
|
159
|
+
if (this.json || !process.stdout.isTTY) return;
|
|
160
|
+
this.stopSpinner();
|
|
161
|
+
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
162
|
+
let i = 0;
|
|
163
|
+
this.spinner = setInterval(() => {
|
|
164
|
+
const frame = frames[i++ % frames.length];
|
|
165
|
+
const line = `${this.colors.blue(frame)} ${message}`;
|
|
166
|
+
|
|
167
|
+
// Clear previous line and write new one
|
|
168
|
+
process.stdout.write('\r' + ' '.repeat(this.lastLine.length) + '\r');
|
|
169
|
+
process.stdout.write(line);
|
|
170
|
+
this.lastLine = line;
|
|
171
|
+
}, 80);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Update spinner message and progress
|
|
176
|
+
*/
|
|
177
|
+
updateSpinner(message, current = 0, total = 0) {
|
|
178
|
+
if (this.json || !process.stdout.isTTY) return;
|
|
179
|
+
if (!this.spinner) {
|
|
180
|
+
this.startSpinner(message);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const progressText = total > 0 ? ` (${current}/${total})` : '';
|
|
184
|
+
const fullMessage = `${message}${progressText}`;
|
|
185
|
+
|
|
186
|
+
// The spinner will pick up the new message on next frame
|
|
187
|
+
this.currentMessage = fullMessage;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Stop the spinner
|
|
192
|
+
*/
|
|
193
|
+
stopSpinner() {
|
|
194
|
+
if (this.spinner) {
|
|
195
|
+
clearInterval(this.spinner);
|
|
196
|
+
this.spinner = null;
|
|
197
|
+
|
|
198
|
+
// Clear the spinner line
|
|
199
|
+
if (process.stdout.isTTY) {
|
|
200
|
+
process.stdout.write('\r' + ' '.repeat(this.lastLine.length) + '\r');
|
|
201
|
+
}
|
|
202
|
+
this.lastLine = '';
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Clean up on exit
|
|
208
|
+
*/
|
|
209
|
+
cleanup() {
|
|
210
|
+
this.stopSpinner();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Ensure spinner is cleaned up on process exit
|
|
215
|
+
process.on('exit', () => {
|
|
216
|
+
// Clear any remaining spinner
|
|
217
|
+
if (process.stdout.isTTY) {
|
|
218
|
+
process.stdout.write('\r' + ' '.repeat(80) + '\r');
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
process.on('SIGINT', () => {
|
|
222
|
+
if (process.stdout.isTTY) {
|
|
223
|
+
process.stdout.write('\r' + ' '.repeat(80) + '\r');
|
|
224
|
+
}
|
|
225
|
+
process.exit(1);
|
|
226
|
+
});
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Diagnostics utilities for the doctor command
|
|
3
|
+
*/
|
|
4
|
+
import { spawn } from 'child_process';
|
|
5
|
+
import { access, constants } from 'fs/promises';
|
|
6
|
+
import { getApiToken } from './environment-config.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Check if required dependencies are available
|
|
10
|
+
*/
|
|
11
|
+
export async function checkDependencies() {
|
|
12
|
+
const dependencies = {
|
|
13
|
+
node: await checkNodeVersion(),
|
|
14
|
+
npm: await checkCommandAvailable('npm'),
|
|
15
|
+
git: await checkCommandAvailable('git'),
|
|
16
|
+
odiff: await checkOdiffBinary()
|
|
17
|
+
};
|
|
18
|
+
const allOk = Object.values(dependencies).every(dep => dep.available);
|
|
19
|
+
return {
|
|
20
|
+
all_ok: allOk,
|
|
21
|
+
details: dependencies
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Check Node.js version
|
|
27
|
+
*/
|
|
28
|
+
async function checkNodeVersion() {
|
|
29
|
+
try {
|
|
30
|
+
const version = process.version;
|
|
31
|
+
const majorVersion = parseInt(version.slice(1).split('.')[0]);
|
|
32
|
+
const supported = majorVersion >= 20;
|
|
33
|
+
return {
|
|
34
|
+
available: true,
|
|
35
|
+
version,
|
|
36
|
+
supported,
|
|
37
|
+
message: supported ? 'OK' : 'Node.js 20+ required'
|
|
38
|
+
};
|
|
39
|
+
} catch (error) {
|
|
40
|
+
return {
|
|
41
|
+
available: false,
|
|
42
|
+
error: error.message
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Check if a command is available
|
|
49
|
+
*/
|
|
50
|
+
async function checkCommandAvailable(command) {
|
|
51
|
+
return new Promise(resolve => {
|
|
52
|
+
const child = spawn(command, ['--version'], {
|
|
53
|
+
stdio: 'pipe'
|
|
54
|
+
});
|
|
55
|
+
let output = '';
|
|
56
|
+
child.stdout.on('data', data => {
|
|
57
|
+
output += data.toString();
|
|
58
|
+
});
|
|
59
|
+
child.on('close', code => {
|
|
60
|
+
resolve({
|
|
61
|
+
available: code === 0,
|
|
62
|
+
version: output.trim().split('\n')[0] || 'Unknown',
|
|
63
|
+
message: code === 0 ? 'OK' : `Command '${command}' not found`
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
child.on('error', () => {
|
|
67
|
+
resolve({
|
|
68
|
+
available: false,
|
|
69
|
+
message: `Command '${command}' not found`
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Check if odiff binary is available
|
|
77
|
+
*/
|
|
78
|
+
async function checkOdiffBinary() {
|
|
79
|
+
try {
|
|
80
|
+
// Check if odiff-bin package is installed and binary is accessible
|
|
81
|
+
const {
|
|
82
|
+
findOdiffBin
|
|
83
|
+
} = await import('odiff-bin');
|
|
84
|
+
const odiffPath = findOdiffBin();
|
|
85
|
+
await access(odiffPath, constants.F_OK | constants.X_OK);
|
|
86
|
+
return {
|
|
87
|
+
available: true,
|
|
88
|
+
path: odiffPath,
|
|
89
|
+
message: 'OK'
|
|
90
|
+
};
|
|
91
|
+
} catch (error) {
|
|
92
|
+
return {
|
|
93
|
+
available: false,
|
|
94
|
+
error: error.message,
|
|
95
|
+
message: 'odiff binary not found or not executable'
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Check API connectivity
|
|
102
|
+
*/
|
|
103
|
+
|
|
104
|
+
export async function checkApiConnectivity(config) {
|
|
105
|
+
try {
|
|
106
|
+
const apiUrl = config?.apiUrl || 'https://vizzly.dev';
|
|
107
|
+
const apiKey = config?.apiKey || getApiToken();
|
|
108
|
+
|
|
109
|
+
// Basic URL validation
|
|
110
|
+
try {
|
|
111
|
+
new globalThis.URL(apiUrl);
|
|
112
|
+
} catch {
|
|
113
|
+
throw new Error(`Invalid API URL: ${apiUrl}`);
|
|
114
|
+
}
|
|
115
|
+
const result = {
|
|
116
|
+
url: apiUrl,
|
|
117
|
+
has_token: !!apiKey,
|
|
118
|
+
reachable: false,
|
|
119
|
+
authenticated: false
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// Try to reach the API
|
|
123
|
+
try {
|
|
124
|
+
const response = await fetch(`${apiUrl}/health`, {
|
|
125
|
+
method: 'GET',
|
|
126
|
+
timeout: 5000
|
|
127
|
+
});
|
|
128
|
+
result.reachable = response.ok;
|
|
129
|
+
result.status_code = response.status;
|
|
130
|
+
|
|
131
|
+
// Test authentication if we have a token
|
|
132
|
+
if (apiKey && result.reachable) {
|
|
133
|
+
const authResponse = await fetch(`${apiUrl}/api/user`, {
|
|
134
|
+
method: 'GET',
|
|
135
|
+
headers: {
|
|
136
|
+
Authorization: `Bearer ${apiKey}`,
|
|
137
|
+
'Content-Type': 'application/json'
|
|
138
|
+
},
|
|
139
|
+
timeout: 5000
|
|
140
|
+
});
|
|
141
|
+
result.authenticated = authResponse.ok;
|
|
142
|
+
result.auth_status_code = authResponse.status;
|
|
143
|
+
}
|
|
144
|
+
} catch (fetchError) {
|
|
145
|
+
result.reachable = false;
|
|
146
|
+
result.error = fetchError.message;
|
|
147
|
+
}
|
|
148
|
+
return result;
|
|
149
|
+
} catch (error) {
|
|
150
|
+
return {
|
|
151
|
+
error: error.message,
|
|
152
|
+
reachable: false,
|
|
153
|
+
authenticated: false
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Check terminal capabilities
|
|
160
|
+
*/
|
|
161
|
+
export function checkTerminalCapabilities() {
|
|
162
|
+
return {
|
|
163
|
+
stdout_is_tty: Boolean(process.stdout.isTTY),
|
|
164
|
+
stdin_is_tty: Boolean(process.stdin.isTTY),
|
|
165
|
+
supports_color: Boolean(process.stdout.isTTY && process.env.TERM !== 'dumb'),
|
|
166
|
+
terminal_type: process.env.TERM || 'unknown',
|
|
167
|
+
columns: process.stdout.columns || 0,
|
|
168
|
+
rows: process.stdout.rows || 0
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Get system information
|
|
174
|
+
*/
|
|
175
|
+
export function getSystemInfo() {
|
|
176
|
+
return {
|
|
177
|
+
platform: process.platform,
|
|
178
|
+
arch: process.arch,
|
|
179
|
+
node_version: process.version,
|
|
180
|
+
memory_usage: process.memoryUsage(),
|
|
181
|
+
uptime: process.uptime(),
|
|
182
|
+
working_directory: process.cwd()
|
|
183
|
+
};
|
|
184
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment Configuration Utility
|
|
3
|
+
* Centralized access to environment variables with proper defaults
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Get API token from environment
|
|
8
|
+
* @returns {string|undefined} API token
|
|
9
|
+
*/
|
|
10
|
+
export function getApiToken() {
|
|
11
|
+
return process.env.VIZZLY_TOKEN;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get API URL from environment
|
|
16
|
+
* @returns {string} API URL with default
|
|
17
|
+
*/
|
|
18
|
+
export function getApiUrl() {
|
|
19
|
+
return process.env.VIZZLY_API_URL || 'https://vizzly.dev';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Get log level from environment
|
|
24
|
+
* @returns {string} Log level with default
|
|
25
|
+
*/
|
|
26
|
+
export function getLogLevel() {
|
|
27
|
+
return process.env.VIZZLY_LOG_LEVEL || 'info';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Get user agent from environment
|
|
32
|
+
* @returns {string|undefined} User agent string
|
|
33
|
+
*/
|
|
34
|
+
export function getUserAgent() {
|
|
35
|
+
return process.env.VIZZLY_USER_AGENT;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Check if Vizzly is enabled in client
|
|
40
|
+
* @returns {boolean} Whether Vizzly is enabled
|
|
41
|
+
*/
|
|
42
|
+
export function isVizzlyEnabled() {
|
|
43
|
+
return process.env.VIZZLY_ENABLED === 'true';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get server URL from environment
|
|
48
|
+
* @returns {string|undefined} Server URL
|
|
49
|
+
*/
|
|
50
|
+
export function getServerUrl() {
|
|
51
|
+
return process.env.VIZZLY_SERVER_URL;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Get build ID from environment
|
|
56
|
+
* @returns {string|undefined} Build ID
|
|
57
|
+
*/
|
|
58
|
+
export function getBuildId() {
|
|
59
|
+
return process.env.VIZZLY_BUILD_ID;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Check if TDD mode is enabled
|
|
64
|
+
* @returns {boolean} Whether TDD mode is enabled
|
|
65
|
+
*/
|
|
66
|
+
export function isTddMode() {
|
|
67
|
+
return process.env.VIZZLY_TDD === 'true';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Set Vizzly enabled state (for client)
|
|
72
|
+
* @param {boolean} enabled - Whether to enable Vizzly
|
|
73
|
+
*/
|
|
74
|
+
export function setVizzlyEnabled(enabled) {
|
|
75
|
+
process.env.VIZZLY_ENABLED = enabled ? 'true' : 'false';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Get all Vizzly environment variables
|
|
80
|
+
* @returns {Object} All environment configuration
|
|
81
|
+
*/
|
|
82
|
+
export function getAllEnvironmentConfig() {
|
|
83
|
+
return {
|
|
84
|
+
apiToken: getApiToken(),
|
|
85
|
+
apiUrl: getApiUrl(),
|
|
86
|
+
logLevel: getLogLevel(),
|
|
87
|
+
userAgent: getUserAgent(),
|
|
88
|
+
enabled: isVizzlyEnabled(),
|
|
89
|
+
serverUrl: getServerUrl(),
|
|
90
|
+
buildId: getBuildId(),
|
|
91
|
+
tddMode: isTddMode()
|
|
92
|
+
};
|
|
93
|
+
}
|