@vizzly-testing/cli 0.10.0 → 0.10.2

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/dist/cli.js CHANGED
@@ -34,8 +34,9 @@ let logger = createComponentLogger('CLI', {
34
34
  verbose: verboseMode || false
35
35
  });
36
36
  let container = await createServiceContainer(config);
37
+ let plugins = [];
37
38
  try {
38
- let plugins = await loadPlugins(configPath, config, logger);
39
+ plugins = await loadPlugins(configPath, config, logger);
39
40
  for (let plugin of plugins) {
40
41
  try {
41
42
  // Add timeout protection for plugin registration (5 seconds)
@@ -58,7 +59,8 @@ program.command('init').description('Initialize Vizzly in your project').option(
58
59
  const globalOptions = program.opts();
59
60
  await init({
60
61
  ...globalOptions,
61
- ...options
62
+ ...options,
63
+ plugins
62
64
  });
63
65
  });
64
66
  program.command('upload').description('Upload screenshots to Vizzly').argument('<path>', 'Path to screenshots directory or file').option('-b, --build-name <name>', 'Build name for grouping').option('-m, --metadata <json>', 'Additional metadata as JSON').option('--batch-size <n>', 'Upload batch size', v => parseInt(v, 10)).option('--upload-timeout <ms>', 'Upload timeout in milliseconds', v => parseInt(v, 10)).option('--branch <branch>', 'Git branch').option('--commit <sha>', 'Git commit SHA').option('--message <msg>', 'Commit message').option('--environment <env>', 'Environment name', 'test').option('--threshold <number>', 'Comparison threshold', parseFloat).option('--token <token>', 'API token override').option('--wait', 'Wait for build completion').option('--upload-all', 'Upload all screenshots without SHA deduplication').option('--parallel-id <id>', 'Unique identifier for parallel test execution').action(async (path, options) => {
@@ -3,15 +3,19 @@ import fs from 'fs/promises';
3
3
  import path from 'path';
4
4
  import { VizzlyError } from '../errors/vizzly-error.js';
5
5
  import { createComponentLogger } from '../utils/logger-factory.js';
6
+ import { loadPlugins } from '../plugin-loader.js';
7
+ import { loadConfig } from '../utils/config-loader.js';
8
+ import { z } from 'zod';
6
9
 
7
10
  /**
8
11
  * Simple configuration setup for Vizzly CLI
9
12
  */
10
13
  export class InitCommand {
11
- constructor(logger) {
14
+ constructor(logger, plugins = []) {
12
15
  this.logger = logger || createComponentLogger('INIT', {
13
16
  level: 'info'
14
17
  });
18
+ this.plugins = plugins;
15
19
  }
16
20
  async run(options = {}) {
17
21
  this.logger.info('🎯 Initializing Vizzly configuration...\n');
@@ -37,7 +41,7 @@ export class InitCommand {
37
41
  }
38
42
  }
39
43
  async generateConfigFile(configPath) {
40
- const configContent = `export default {
44
+ let coreConfig = `export default {
41
45
  // Server configuration (for run command)
42
46
  server: {
43
47
  port: 47392,
@@ -65,11 +69,103 @@ export class InitCommand {
65
69
  // TDD configuration
66
70
  tdd: {
67
71
  openReport: false // Whether to auto-open HTML report in browser
68
- }
69
- };
70
- `;
71
- await fs.writeFile(configPath, configContent, 'utf8');
72
+ }`;
73
+
74
+ // Add plugin configurations
75
+ let pluginConfigs = this.generatePluginConfigs();
76
+ if (pluginConfigs) {
77
+ coreConfig += ',\n\n' + pluginConfigs;
78
+ }
79
+ coreConfig += '\n};\n';
80
+ await fs.writeFile(configPath, coreConfig, 'utf8');
72
81
  this.logger.info(`📄 Created vizzly.config.js`);
82
+
83
+ // Log discovered plugins
84
+ let pluginsWithConfig = this.plugins.filter(p => p.configSchema);
85
+ if (pluginsWithConfig.length > 0) {
86
+ this.logger.info(` Added config for ${pluginsWithConfig.length} plugin(s):`);
87
+ pluginsWithConfig.forEach(p => {
88
+ this.logger.info(` - ${p.name}`);
89
+ });
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Generate configuration sections for plugins
95
+ * @returns {string} Plugin config sections as formatted string
96
+ */
97
+ generatePluginConfigs() {
98
+ let sections = [];
99
+ for (let plugin of this.plugins) {
100
+ if (plugin.configSchema) {
101
+ let configStr = this.formatPluginConfig(plugin);
102
+ if (configStr) {
103
+ sections.push(configStr);
104
+ }
105
+ }
106
+ }
107
+ return sections.length > 0 ? sections.join(',\n\n') : '';
108
+ }
109
+
110
+ /**
111
+ * Format a plugin's config schema as JavaScript code
112
+ * @param {Object} plugin - Plugin with configSchema
113
+ * @returns {string} Formatted config string
114
+ */
115
+ formatPluginConfig(plugin) {
116
+ try {
117
+ // Validate config schema structure with Zod (defensive check)
118
+ let configValueSchema = z.lazy(() => z.union([z.string(), z.number(), z.boolean(), z.null(), z.array(configValueSchema), z.record(configValueSchema)]));
119
+ let configSchemaValidator = z.record(configValueSchema);
120
+ configSchemaValidator.parse(plugin.configSchema);
121
+ let configEntries = [];
122
+ for (let [key, value] of Object.entries(plugin.configSchema)) {
123
+ let formattedValue = this.formatValue(value, 1);
124
+ configEntries.push(` // ${plugin.name} plugin configuration\n ${key}: ${formattedValue}`);
125
+ }
126
+ return configEntries.join(',\n\n');
127
+ } catch (error) {
128
+ if (error instanceof z.ZodError) {
129
+ let messages = error.errors.map(e => `${e.path.join('.')}: ${e.message}`);
130
+ this.logger.warn(`Invalid config schema for plugin ${plugin.name}: ${messages.join(', ')}`);
131
+ } else {
132
+ this.logger.warn(`Failed to format config for plugin ${plugin.name}: ${error.message}`);
133
+ }
134
+ return '';
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Format a JavaScript value with proper indentation
140
+ * @param {*} value - Value to format
141
+ * @param {number} depth - Current indentation depth
142
+ * @returns {string} Formatted value
143
+ */
144
+ formatValue(value, depth = 0) {
145
+ let indent = ' '.repeat(depth);
146
+ let nextIndent = ' '.repeat(depth + 1);
147
+ if (value === null) return 'null';
148
+ if (value === undefined) return 'undefined';
149
+ if (typeof value === 'string') return `'${value.replace(/'/g, "\\'")}'`;
150
+ if (typeof value === 'number' || typeof value === 'boolean') return String(value);
151
+ if (Array.isArray(value)) {
152
+ if (value.length === 0) return '[]';
153
+ let items = value.map(item => {
154
+ let formatted = this.formatValue(item, depth + 1);
155
+ return `${nextIndent}${formatted}`;
156
+ });
157
+ return `[\n${items.join(',\n')}\n${indent}]`;
158
+ }
159
+ if (typeof value === 'object') {
160
+ let entries = Object.entries(value);
161
+ if (entries.length === 0) return '{}';
162
+ let props = entries.map(([k, v]) => {
163
+ let formatted = this.formatValue(v, depth + 1);
164
+ return `${nextIndent}${k}: ${formatted}`;
165
+ });
166
+ return `{\n${props.join(',\n')}\n${indent}}`;
167
+ }
168
+ return String(value);
73
169
  }
74
170
  showNextSteps() {
75
171
  this.logger.info('\n📚 Next steps:');
@@ -92,12 +188,28 @@ export class InitCommand {
92
188
 
93
189
  // Export factory function for CLI
94
190
  export function createInitCommand(options) {
95
- const command = new InitCommand(options.logger);
191
+ const command = new InitCommand(options.logger, options.plugins);
96
192
  return () => command.run(options);
97
193
  }
98
194
 
99
195
  // Simple export for direct CLI usage
100
196
  export async function init(options = {}) {
101
- const command = new InitCommand();
197
+ let plugins = [];
198
+
199
+ // Try to load plugins if not provided
200
+ if (!options.plugins) {
201
+ try {
202
+ let config = await loadConfig(options.config, {});
203
+ let logger = createComponentLogger('INIT', {
204
+ level: 'debug'
205
+ });
206
+ plugins = await loadPlugins(options.config, config, logger);
207
+ } catch {
208
+ // Silent fail - plugins are optional for init
209
+ }
210
+ } else {
211
+ plugins = options.plugins;
212
+ }
213
+ const command = new InitCommand(null, plugins);
102
214
  return await command.run(options);
103
215
  }
@@ -1,4 +1,4 @@
1
- import { writeFileSync, readFileSync, existsSync, unlinkSync, mkdirSync, openSync } from 'fs';
1
+ import { writeFileSync, readFileSync, existsSync, unlinkSync, mkdirSync } from 'fs';
2
2
  import { join } from 'path';
3
3
  import { spawn } from 'child_process';
4
4
  import { ConsoleUI } from '../utils/console-ui.js';
@@ -36,21 +36,56 @@ export async function tddStartCommand(options = {}, globalOptions = {}) {
36
36
  }
37
37
  const port = options.port || 47392;
38
38
 
39
- // Prepare log files for daemon output
40
- const logFile = join(vizzlyDir, 'daemon.log');
41
- const errorFile = join(vizzlyDir, 'daemon-error.log');
39
+ // Show loading indicator if downloading baselines (but not in verbose mode since child shows progress)
40
+ if (options.baselineBuild && !globalOptions.verbose) {
41
+ ui.startSpinner(`Downloading baselines from build ${options.baselineBuild}...`);
42
+ }
42
43
 
43
- // Spawn detached child process to run the server
44
+ // Spawn child process with stdio inherited during init for direct error visibility
44
45
  const child = spawn(process.execPath, [process.argv[1],
45
46
  // CLI entry point
46
47
  'tdd', 'start', '--daemon-child',
47
48
  // Special flag for child process
48
49
  '--port', port.toString(), ...(options.open ? ['--open'] : []), ...(options.baselineBuild ? ['--baseline-build', options.baselineBuild] : []), ...(options.baselineComparison ? ['--baseline-comparison', options.baselineComparison] : []), ...(options.environment ? ['--environment', options.environment] : []), ...(options.threshold !== undefined ? ['--threshold', options.threshold.toString()] : []), ...(options.timeout ? ['--timeout', options.timeout] : []), ...(options.token ? ['--token', options.token] : []), ...(globalOptions.json ? ['--json'] : []), ...(globalOptions.verbose ? ['--verbose'] : []), ...(globalOptions.noColor ? ['--no-color'] : [])], {
49
50
  detached: true,
50
- stdio: ['ignore', openSync(logFile, 'a'), openSync(errorFile, 'a')],
51
+ stdio: ['ignore', 'inherit', 'inherit', 'ipc'],
51
52
  cwd: process.cwd()
52
53
  });
53
54
 
55
+ // Wait for child to signal successful init or exit with error
56
+ let initComplete = false;
57
+ let initFailed = false;
58
+ await new Promise(resolve => {
59
+ // Child disconnects IPC when initialization succeeds
60
+ child.on('disconnect', () => {
61
+ initComplete = true;
62
+ resolve();
63
+ });
64
+
65
+ // Child exits before disconnecting = initialization failed
66
+ child.on('exit', () => {
67
+ if (!initComplete) {
68
+ initFailed = true;
69
+ resolve();
70
+ }
71
+ });
72
+
73
+ // Timeout after 30 seconds to prevent indefinite wait
74
+ setTimeout(() => {
75
+ if (!initComplete && !initFailed) {
76
+ initFailed = true;
77
+ resolve();
78
+ }
79
+ }, 30000);
80
+ });
81
+ if (initFailed) {
82
+ if (options.baselineBuild && !globalOptions.verbose) {
83
+ ui.stopSpinner();
84
+ }
85
+ ui.error('TDD server failed to start');
86
+ process.exit(1);
87
+ }
88
+
54
89
  // Unref so parent can exit
55
90
  child.unref();
56
91
 
@@ -62,6 +97,9 @@ export async function tddStartCommand(options = {}, globalOptions = {}) {
62
97
  await new Promise(resolve => setTimeout(resolve, retryDelay * (i + 1)));
63
98
  running = await isServerRunning(port);
64
99
  }
100
+ if (options.baselineBuild && !globalOptions.verbose) {
101
+ ui.stopSpinner();
102
+ }
65
103
  if (!running) {
66
104
  ui.error('Failed to start TDD server - server not responding to health checks');
67
105
  process.exit(1);
@@ -106,6 +144,11 @@ export async function runDaemonChild(options = {}, globalOptions = {}) {
106
144
  daemon: true
107
145
  }, globalOptions);
108
146
 
147
+ // Disconnect IPC after successful initialization to signal parent
148
+ if (process.send) {
149
+ process.disconnect();
150
+ }
151
+
109
152
  // Store our PID for the stop command
110
153
  const pidFile = join(vizzlyDir, 'server.pid');
111
154
  writeFileSync(pidFile, process.pid.toString());
@@ -139,15 +182,8 @@ export async function runDaemonChild(options = {}, globalOptions = {}) {
139
182
  // Keep process alive
140
183
  process.stdin.resume();
141
184
  } catch (error) {
142
- // Log error to file for debugging
143
- const logFile = join(vizzlyDir, 'daemon-error.log');
144
- try {
145
- writeFileSync(logFile, `[${new Date().toISOString()}] ${error.stack || error}\n`, {
146
- flag: 'a'
147
- });
148
- } catch {
149
- // Silent failure if we can't write log
150
- }
185
+ // Most errors shown via inherited stdio, but catch any that weren't
186
+ console.error(`Fatal error: ${error.message}`);
151
187
  process.exit(1);
152
188
  }
153
189
  }
@@ -53,7 +53,9 @@ export async function tddCommand(testCommand, options = {}, globalOptions = {})
53
53
  // Collect git metadata
54
54
  const branch = await detectBranch(options.branch);
55
55
  const commit = await detectCommit(options.commit);
56
- if (globalOptions.verbose) {
56
+
57
+ // Only show config in verbose mode for non-daemon (daemon shows baseline info instead)
58
+ if (globalOptions.verbose && !options.daemon) {
57
59
  ui.info('TDD Configuration loaded', {
58
60
  testCommand,
59
61
  port: config.server.port,
@@ -97,8 +99,13 @@ export async function tddCommand(testCommand, options = {}, globalOptions = {})
97
99
  ui.info(`TDD screenshot server running on port ${serverInfo.port}`);
98
100
  ui.info(`Dashboard: http://localhost:${serverInfo.port}/dashboard`);
99
101
  }
100
- if (globalOptions.verbose) {
101
- ui.info('Server details', serverInfo);
102
+ // Verbose server details only in non-daemon mode
103
+ if (globalOptions.verbose && !options.daemon) {
104
+ ui.info('Server started', {
105
+ port: serverInfo.port,
106
+ pid: serverInfo.pid,
107
+ uptime: serverInfo.uptime
108
+ });
102
109
  }
103
110
  });
104
111
  testRunner.on('screenshot-captured', screenshotInfo => {
@@ -2,6 +2,7 @@ import { glob } from 'glob';
2
2
  import { readFileSync } from 'fs';
3
3
  import { resolve, dirname } from 'path';
4
4
  import { pathToFileURL } from 'url';
5
+ import { z } from 'zod';
5
6
 
6
7
  /**
7
8
  * Load and register plugins from node_modules and config
@@ -128,23 +129,43 @@ async function loadPlugin(pluginPath) {
128
129
  }
129
130
  }
130
131
 
132
+ /**
133
+ * Zod schema for validating plugin structure
134
+ */
135
+ const pluginSchema = z.object({
136
+ name: z.string().min(1, 'Plugin name is required'),
137
+ version: z.string().optional(),
138
+ register: z.function(z.tuple([z.any(), z.any()]), z.void()),
139
+ configSchema: z.record(z.any()).optional()
140
+ });
141
+
142
+ /**
143
+ * Zod schema for validating plugin config values
144
+ * Supports: string, number, boolean, null, arrays, and nested objects
145
+ */
146
+ const configValueSchema = z.lazy(() => z.union([z.string(), z.number(), z.boolean(), z.null(), z.array(configValueSchema), z.record(configValueSchema)]));
147
+
131
148
  /**
132
149
  * Validate plugin has required structure
133
150
  * @param {Object} plugin - Plugin object
134
151
  * @throws {Error} If plugin structure is invalid
135
152
  */
136
153
  function validatePluginStructure(plugin) {
137
- if (!plugin || typeof plugin !== 'object') {
138
- throw new Error('Plugin must export an object');
139
- }
140
- if (!plugin.name || typeof plugin.name !== 'string') {
141
- throw new Error('Plugin must have a name (string)');
142
- }
143
- if (!plugin.register || typeof plugin.register !== 'function') {
144
- throw new Error('Plugin must have a register function');
145
- }
146
- if (plugin.version && typeof plugin.version !== 'string') {
147
- throw new Error('Plugin version must be a string');
154
+ try {
155
+ // Validate basic plugin structure
156
+ pluginSchema.parse(plugin);
157
+
158
+ // If configSchema exists, validate it contains valid config values
159
+ if (plugin.configSchema) {
160
+ let configSchemaValidator = z.record(configValueSchema);
161
+ configSchemaValidator.parse(plugin.configSchema);
162
+ }
163
+ } catch (error) {
164
+ if (error instanceof z.ZodError) {
165
+ let messages = error.errors.map(e => `${e.path.join('.')}: ${e.message}`);
166
+ throw new Error(`Invalid plugin structure: ${messages.join(', ')}`);
167
+ }
168
+ throw error;
148
169
  }
149
170
  }
150
171
 
@@ -90,8 +90,6 @@ export class TddService {
90
90
  });
91
91
  }
92
92
  async downloadBaselines(environment = 'test', branch = null, buildId = null, comparisonId = null) {
93
- logger.info('🔍 Looking for baseline build...');
94
-
95
93
  // If no branch specified, try to detect the default branch
96
94
  if (!branch) {
97
95
  branch = await getDefaultBranch();
@@ -107,7 +105,6 @@ export class TddService {
107
105
  let baselineBuild;
108
106
  if (buildId) {
109
107
  // Use specific build ID - get it with screenshots in one call
110
- logger.info(`📌 Using specified build: ${buildId}`);
111
108
  const apiResponse = await this.api.getBuild(buildId, 'screenshots');
112
109
 
113
110
  // Debug the full API response (only in debug mode)
@@ -154,7 +151,6 @@ export class TddService {
154
151
  }
155
152
  baselineBuild = builds.data[0];
156
153
  }
157
- logger.info(`📥 Found baseline build: ${colors.cyan(baselineBuild.name || 'Unknown')} (${baselineBuild.id || 'Unknown ID'})`);
158
154
 
159
155
  // For specific buildId, we already have screenshots, otherwise get build details
160
156
  let buildDetails = baselineBuild;
@@ -167,7 +163,8 @@ export class TddService {
167
163
  logger.warn('⚠️ No screenshots found in baseline build');
168
164
  return null;
169
165
  }
170
- logger.info(`📸 Downloading ${colors.cyan(buildDetails.screenshots.length)} baseline screenshots...`);
166
+ logger.info(`Using baseline from build: ${colors.cyan(baselineBuild.name || 'Unknown')} (${baselineBuild.id || 'Unknown ID'})`);
167
+ logger.info(`Checking ${colors.cyan(buildDetails.screenshots.length)} baseline screenshots...`);
171
168
 
172
169
  // Debug screenshots structure (only in debug mode)
173
170
  logger.debug(`📊 Screenshots array structure:`, {
@@ -352,17 +349,37 @@ export class TddService {
352
349
  const metadataPath = join(this.baselinePath, 'metadata.json');
353
350
  writeFileSync(metadataPath, JSON.stringify(this.baselineData, null, 2));
354
351
 
352
+ // Save baseline build metadata for MCP plugin
353
+ const baselineMetadataPath = safePath(this.workingDir, '.vizzly', 'baseline-metadata.json');
354
+ const buildMetadata = {
355
+ buildId: baselineBuild.id,
356
+ buildName: baselineBuild.name,
357
+ branch: branch,
358
+ environment: environment,
359
+ commitSha: baselineBuild.commit_sha,
360
+ commitMessage: baselineBuild.commit_message,
361
+ approvalStatus: baselineBuild.approval_status,
362
+ completedAt: baselineBuild.completed_at,
363
+ downloadedAt: new Date().toISOString()
364
+ };
365
+ writeFileSync(baselineMetadataPath, JSON.stringify(buildMetadata, null, 2));
366
+
355
367
  // Final summary
356
368
  const actualDownloads = downloadedCount - skippedCount;
357
- const totalAttempted = downloadedCount + errorCount;
358
- if (skippedCount > 0 || errorCount > 0) {
359
- let summaryParts = [];
360
- if (actualDownloads > 0) summaryParts.push(`${actualDownloads} downloaded`);
361
- if (skippedCount > 0) summaryParts.push(`${skippedCount} skipped (matching SHA)`);
362
- if (errorCount > 0) summaryParts.push(`${errorCount} failed`);
363
- logger.info(`✅ Baseline ready - ${summaryParts.join(', ')} - ${totalAttempted}/${buildDetails.screenshots.length} total`);
369
+ if (skippedCount > 0) {
370
+ // All skipped (up-to-date)
371
+ if (actualDownloads === 0) {
372
+ logger.info(`✅ All ${skippedCount} baselines up-to-date (matching local SHA)`);
373
+ } else {
374
+ // Mixed: some downloaded, some skipped
375
+ logger.info(`✅ Downloaded ${actualDownloads} new screenshots, ${skippedCount} already up-to-date`);
376
+ }
364
377
  } else {
365
- logger.info(`✅ Baseline downloaded successfully - ${downloadedCount}/${buildDetails.screenshots.length} screenshots`);
378
+ // Fresh download
379
+ logger.info(`✅ Downloaded ${downloadedCount}/${buildDetails.screenshots.length} screenshots successfully`);
380
+ }
381
+ if (errorCount > 0) {
382
+ logger.warn(`⚠️ ${errorCount} screenshots failed to download`);
366
383
  }
367
384
  return this.baselineData;
368
385
  } catch (error) {
@@ -5,10 +5,29 @@ export function init(options?: {}): Promise<void>;
5
5
  * Simple configuration setup for Vizzly CLI
6
6
  */
7
7
  export class InitCommand {
8
- constructor(logger: any);
8
+ constructor(logger: any, plugins?: any[]);
9
9
  logger: any;
10
+ plugins: any[];
10
11
  run(options?: {}): Promise<void>;
11
12
  generateConfigFile(configPath: any): Promise<void>;
13
+ /**
14
+ * Generate configuration sections for plugins
15
+ * @returns {string} Plugin config sections as formatted string
16
+ */
17
+ generatePluginConfigs(): string;
18
+ /**
19
+ * Format a plugin's config schema as JavaScript code
20
+ * @param {Object} plugin - Plugin with configSchema
21
+ * @returns {string} Formatted config string
22
+ */
23
+ formatPluginConfig(plugin: any): string;
24
+ /**
25
+ * Format a JavaScript value with proper indentation
26
+ * @param {*} value - Value to format
27
+ * @param {number} depth - Current indentation depth
28
+ * @returns {string} Formatted value
29
+ */
30
+ formatValue(value: any, depth?: number): string;
12
31
  showNextSteps(): void;
13
32
  fileExists(filePath: any): Promise<boolean>;
14
33
  }
@@ -35,14 +35,14 @@ export let vizzlyConfigSchema: z.ZodDefault<z.ZodObject<{
35
35
  commit: z.ZodOptional<z.ZodString>;
36
36
  message: z.ZodOptional<z.ZodString>;
37
37
  }, "strip", z.ZodTypeAny, {
38
- message?: string;
39
38
  name?: string;
39
+ message?: string;
40
40
  environment?: string;
41
41
  branch?: string;
42
42
  commit?: string;
43
43
  }, {
44
- message?: string;
45
44
  name?: string;
45
+ message?: string;
46
46
  environment?: string;
47
47
  branch?: string;
48
48
  commit?: string;
@@ -101,14 +101,14 @@ export let vizzlyConfigSchema: z.ZodDefault<z.ZodObject<{
101
101
  commit: z.ZodOptional<z.ZodString>;
102
102
  message: z.ZodOptional<z.ZodString>;
103
103
  }, "strip", z.ZodTypeAny, {
104
- message?: string;
105
104
  name?: string;
105
+ message?: string;
106
106
  environment?: string;
107
107
  branch?: string;
108
108
  commit?: string;
109
109
  }, {
110
- message?: string;
111
110
  name?: string;
111
+ message?: string;
112
112
  environment?: string;
113
113
  branch?: string;
114
114
  commit?: string;
@@ -167,14 +167,14 @@ export let vizzlyConfigSchema: z.ZodDefault<z.ZodObject<{
167
167
  commit: z.ZodOptional<z.ZodString>;
168
168
  message: z.ZodOptional<z.ZodString>;
169
169
  }, "strip", z.ZodTypeAny, {
170
- message?: string;
171
170
  name?: string;
171
+ message?: string;
172
172
  environment?: string;
173
173
  branch?: string;
174
174
  commit?: string;
175
175
  }, {
176
- message?: string;
177
176
  name?: string;
177
+ message?: string;
178
178
  environment?: string;
179
179
  branch?: string;
180
180
  commit?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vizzly-testing/cli",
3
- "version": "0.10.0",
3
+ "version": "0.10.2",
4
4
  "description": "Visual review platform for UI developers and designers",
5
5
  "keywords": [
6
6
  "visual-testing",
@@ -84,7 +84,7 @@
84
84
  "form-data": "^4.0.0",
85
85
  "glob": "^11.0.3",
86
86
  "odiff-bin": "^3.2.1",
87
- "zod": "^3.24.1"
87
+ "zod": "^3.25.76"
88
88
  },
89
89
  "devDependencies": {
90
90
  "@babel/cli": "^7.28.0",