@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.
Files changed (90) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +363 -0
  3. package/bin/vizzly.js +3 -0
  4. package/dist/cli.js +104 -0
  5. package/dist/client/index.js +237 -0
  6. package/dist/commands/doctor.js +158 -0
  7. package/dist/commands/init.js +102 -0
  8. package/dist/commands/run.js +224 -0
  9. package/dist/commands/status.js +164 -0
  10. package/dist/commands/tdd.js +212 -0
  11. package/dist/commands/upload.js +181 -0
  12. package/dist/container/index.js +184 -0
  13. package/dist/errors/vizzly-error.js +149 -0
  14. package/dist/index.js +31 -0
  15. package/dist/screenshot-wrapper.js +68 -0
  16. package/dist/sdk/index.js +364 -0
  17. package/dist/server/index.js +522 -0
  18. package/dist/services/api-service.js +215 -0
  19. package/dist/services/base-service.js +154 -0
  20. package/dist/services/build-manager.js +214 -0
  21. package/dist/services/screenshot-server.js +96 -0
  22. package/dist/services/server-manager.js +61 -0
  23. package/dist/services/service-utils.js +171 -0
  24. package/dist/services/tdd-service.js +444 -0
  25. package/dist/services/test-runner.js +210 -0
  26. package/dist/services/uploader.js +413 -0
  27. package/dist/types/cli.d.ts +2 -0
  28. package/dist/types/client/index.d.ts +76 -0
  29. package/dist/types/commands/doctor.d.ts +11 -0
  30. package/dist/types/commands/init.d.ts +14 -0
  31. package/dist/types/commands/run.d.ts +13 -0
  32. package/dist/types/commands/status.d.ts +13 -0
  33. package/dist/types/commands/tdd.d.ts +13 -0
  34. package/dist/types/commands/upload.d.ts +13 -0
  35. package/dist/types/container/index.d.ts +61 -0
  36. package/dist/types/errors/vizzly-error.d.ts +75 -0
  37. package/dist/types/index.d.ts +10 -0
  38. package/dist/types/index.js +153 -0
  39. package/dist/types/screenshot-wrapper.d.ts +27 -0
  40. package/dist/types/sdk/index.d.ts +108 -0
  41. package/dist/types/server/index.d.ts +38 -0
  42. package/dist/types/services/api-service.d.ts +77 -0
  43. package/dist/types/services/base-service.d.ts +72 -0
  44. package/dist/types/services/build-manager.d.ts +68 -0
  45. package/dist/types/services/screenshot-server.d.ts +10 -0
  46. package/dist/types/services/server-manager.d.ts +8 -0
  47. package/dist/types/services/service-utils.d.ts +45 -0
  48. package/dist/types/services/tdd-service.d.ts +55 -0
  49. package/dist/types/services/test-runner.d.ts +25 -0
  50. package/dist/types/services/uploader.d.ts +34 -0
  51. package/dist/types/types/index.d.ts +373 -0
  52. package/dist/types/utils/colors.d.ts +12 -0
  53. package/dist/types/utils/config-helpers.d.ts +6 -0
  54. package/dist/types/utils/config-loader.d.ts +22 -0
  55. package/dist/types/utils/console-ui.d.ts +61 -0
  56. package/dist/types/utils/diagnostics.d.ts +69 -0
  57. package/dist/types/utils/environment-config.d.ts +54 -0
  58. package/dist/types/utils/environment.d.ts +36 -0
  59. package/dist/types/utils/error-messages.d.ts +42 -0
  60. package/dist/types/utils/fetch-utils.d.ts +1 -0
  61. package/dist/types/utils/framework-detector.d.ts +5 -0
  62. package/dist/types/utils/git.d.ts +44 -0
  63. package/dist/types/utils/help.d.ts +11 -0
  64. package/dist/types/utils/image-comparison.d.ts +42 -0
  65. package/dist/types/utils/logger-factory.d.ts +26 -0
  66. package/dist/types/utils/logger.d.ts +79 -0
  67. package/dist/types/utils/package-info.d.ts +15 -0
  68. package/dist/types/utils/package.d.ts +1 -0
  69. package/dist/types/utils/project-detection.d.ts +19 -0
  70. package/dist/types/utils/ui-helpers.d.ts +23 -0
  71. package/dist/utils/colors.js +66 -0
  72. package/dist/utils/config-helpers.js +8 -0
  73. package/dist/utils/config-loader.js +120 -0
  74. package/dist/utils/console-ui.js +226 -0
  75. package/dist/utils/diagnostics.js +184 -0
  76. package/dist/utils/environment-config.js +93 -0
  77. package/dist/utils/environment.js +109 -0
  78. package/dist/utils/error-messages.js +34 -0
  79. package/dist/utils/fetch-utils.js +9 -0
  80. package/dist/utils/framework-detector.js +40 -0
  81. package/dist/utils/git.js +226 -0
  82. package/dist/utils/help.js +66 -0
  83. package/dist/utils/image-comparison.js +172 -0
  84. package/dist/utils/logger-factory.js +76 -0
  85. package/dist/utils/logger.js +231 -0
  86. package/dist/utils/package-info.js +38 -0
  87. package/dist/utils/package.js +9 -0
  88. package/dist/utils/project-detection.js +145 -0
  89. package/dist/utils/ui-helpers.js +86 -0
  90. 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
+ }