@vizzly-testing/cli 0.35.0 → 0.35.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/README.md +31 -7
- package/dist/api/core.js +16 -3
- package/dist/api/endpoints.js +38 -15
- package/dist/api/index.js +1 -1
- package/dist/cli.js +252 -189
- package/dist/commands/preview.js +19 -17
- package/dist/commands/run.js +7 -4
- package/dist/commands/status.js +14 -1
- package/dist/commands/tdd-daemon-process.js +5 -1
- package/dist/commands/tdd-daemon.js +217 -144
- package/dist/commands/tdd.js +2 -1
- package/dist/reporter/reporter-bundle.iife.js +5 -5
- package/dist/tdd/tdd-service.js +6 -2
- package/dist/test-runner/core.js +4 -2
- package/dist/test-runner/operations.js +17 -2
- package/dist/utils/config-loader.js +1 -0
- package/dist/utils/output.js +3 -0
- package/package.json +2 -2
- package/skills/vizzly/SKILL.md +12 -4
- package/skills/vizzly/references/cli-context.md +8 -2
- package/skills/vizzly/references/setup-ci.md +3 -1
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import 'dotenv/config';
|
|
3
|
-
import {
|
|
3
|
+
import { existsSync, statSync } from 'node:fs';
|
|
4
|
+
import { Option, program } from 'commander';
|
|
4
5
|
import { apiCommand, validateApiOptions } from './commands/api.js';
|
|
5
6
|
import { baselinesCommand, validateBaselinesOptions } from './commands/baselines.js';
|
|
6
7
|
import { buildsCommand, validateBuildsOptions } from './commands/builds.js';
|
|
@@ -52,7 +53,7 @@ const formatHelp = (cmd, helper) => {
|
|
|
52
53
|
lines.push(` ${c.gray('Visual regression testing from your terminal')}`);
|
|
53
54
|
} else {
|
|
54
55
|
// Compact header for subcommands
|
|
55
|
-
lines.push(` ${c.brand.amber(c.bold(
|
|
56
|
+
lines.push(` ${c.brand.amber(c.bold(commandPath(cmd)))}`);
|
|
56
57
|
let desc = cmd.description();
|
|
57
58
|
if (desc) {
|
|
58
59
|
lines.push(` ${c.gray(desc)}`);
|
|
@@ -84,7 +85,7 @@ const formatHelp = (cmd, helper) => {
|
|
|
84
85
|
key: 'setup',
|
|
85
86
|
icon: '▸',
|
|
86
87
|
title: 'Setup',
|
|
87
|
-
names: ['init', 'doctor', 'config', 'baselines']
|
|
88
|
+
names: ['init', 'doctor', 'config', 'baselines', 'project']
|
|
88
89
|
}, {
|
|
89
90
|
key: 'advanced',
|
|
90
91
|
icon: '▸',
|
|
@@ -174,7 +175,7 @@ const formatHelp = (cmd, helper) => {
|
|
|
174
175
|
lines.push(` ${c.brand.amber('▸')} ${c.bold('Quick Start')}`);
|
|
175
176
|
lines.push('');
|
|
176
177
|
lines.push(` ${c.dim('# Local visual review')}`);
|
|
177
|
-
lines.push(` ${c.gray('$')} ${c.white('vizzly tdd start')}`);
|
|
178
|
+
lines.push(` ${c.gray('$')} ${c.white('vizzly tdd start --open')}`);
|
|
178
179
|
lines.push('');
|
|
179
180
|
lines.push(` ${c.dim('# CI pipeline')}`);
|
|
180
181
|
lines.push(` ${c.gray('$')} ${c.white('vizzly run "pnpm test" --wait')}`);
|
|
@@ -205,6 +206,18 @@ const formatHelp = (cmd, helper) => {
|
|
|
205
206
|
lines.push('');
|
|
206
207
|
return lines.join('\n');
|
|
207
208
|
};
|
|
209
|
+
function commandPath(cmd) {
|
|
210
|
+
let names = [];
|
|
211
|
+
let current = cmd;
|
|
212
|
+
while (current) {
|
|
213
|
+
let name = current.name();
|
|
214
|
+
if (name) {
|
|
215
|
+
names.unshift(name);
|
|
216
|
+
}
|
|
217
|
+
current = current.parent;
|
|
218
|
+
}
|
|
219
|
+
return names.join(' ');
|
|
220
|
+
}
|
|
208
221
|
function extractGlobalOptionsFromArgv(argv, commandNames = null) {
|
|
209
222
|
let configPath = null;
|
|
210
223
|
let tokenArg = null;
|
|
@@ -259,9 +272,133 @@ function normalizeJsonArgv(argv, commandNames) {
|
|
|
259
272
|
}
|
|
260
273
|
return normalizedArgv;
|
|
261
274
|
}
|
|
275
|
+
function findNestedCommandRequest(args, startIndex, subcommandNames, parentName) {
|
|
276
|
+
if (!subcommandNames?.size) {
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
let valueOptions = new Set(['-c', '--config', '--token', '--log-level']);
|
|
280
|
+
for (let i = startIndex; i < args.length; i++) {
|
|
281
|
+
let arg = args[i];
|
|
282
|
+
if (arg === '--') {
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
if (valueOptions.has(arg)) {
|
|
286
|
+
i++;
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
if (arg.startsWith('--')) {
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
if (arg.startsWith('-')) {
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
return {
|
|
296
|
+
name: arg,
|
|
297
|
+
known: subcommandNames.has(arg),
|
|
298
|
+
helpCommand: `vizzly ${parentName} --help`
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
function findRequestedCommand(argv, commandNames, nestedCommandNames = new Map()) {
|
|
304
|
+
let valueOptions = new Set(['-c', '--config', '--token', '--log-level']);
|
|
305
|
+
let args = argv.slice(2);
|
|
306
|
+
let jsonValueCandidate = null;
|
|
307
|
+
for (let i = 0; i < args.length; i++) {
|
|
308
|
+
let arg = args[i];
|
|
309
|
+
if (arg === '--') {
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
if (arg === '--json') {
|
|
313
|
+
let nextArg = args[i + 1];
|
|
314
|
+
if (nextArg && !nextArg.startsWith('-') && !commandNames.has(nextArg)) {
|
|
315
|
+
jsonValueCandidate = jsonValueCandidate || nextArg;
|
|
316
|
+
i++;
|
|
317
|
+
}
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
if (valueOptions.has(arg)) {
|
|
321
|
+
i++;
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
if (arg.startsWith('--json=')) {
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
if (arg.startsWith('--')) {
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
if (arg.startsWith('-')) {
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
let known = commandNames.has(arg);
|
|
334
|
+
if (known) {
|
|
335
|
+
let nestedRequest = findNestedCommandRequest(args, i + 1, nestedCommandNames.get(arg), arg);
|
|
336
|
+
if (nestedRequest && !nestedRequest.known) {
|
|
337
|
+
return nestedRequest;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return {
|
|
341
|
+
name: arg,
|
|
342
|
+
known
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
if (jsonValueCandidate) {
|
|
346
|
+
return {
|
|
347
|
+
name: jsonValueCandidate,
|
|
348
|
+
known: false
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
function getGlobalOptions() {
|
|
354
|
+
let options = program.opts();
|
|
355
|
+
return {
|
|
356
|
+
...options,
|
|
357
|
+
noColor: options.noColor || options.color === false
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
function reportValidationErrors(errors) {
|
|
361
|
+
if (output.isJson()) {
|
|
362
|
+
output.error('Validation errors', null, {
|
|
363
|
+
errors
|
|
364
|
+
});
|
|
365
|
+
} else {
|
|
366
|
+
output.error('Validation errors:');
|
|
367
|
+
for (let error of errors) {
|
|
368
|
+
output.printErr(` - ${error}`);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
process.exit(1);
|
|
372
|
+
}
|
|
373
|
+
function formatCommanderError(message) {
|
|
374
|
+
return message.replace(/^error:\s*/, '').trim();
|
|
375
|
+
}
|
|
376
|
+
function writeCommanderError(text) {
|
|
377
|
+
let message = formatCommanderError(text);
|
|
378
|
+
if (!message) {
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
if (output.isJson()) {
|
|
382
|
+
if (message.startsWith('(')) {
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
output.error(message);
|
|
386
|
+
} else {
|
|
387
|
+
process.stderr.write(text);
|
|
388
|
+
if (!text.endsWith('\n')) {
|
|
389
|
+
process.stderr.write('\n');
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
262
393
|
program.name('vizzly').description('Vizzly CLI for visual regression testing').version(getPackageVersion()).option('-c, --config <path>', 'Config file path').option('--token <token>', 'Vizzly API token').option('-v, --verbose', 'Verbose output (shorthand for --log-level debug)').option('--log-level <level>', 'Log level: debug, info, warn, error (default: info, or VIZZLY_LOG_LEVEL env var)').option('--json [fields]', 'JSON output, optionally specify fields (e.g., --json id,status,branch)').option('--color', 'Force colored output (even in non-TTY)').option('--no-color', 'Disable colored output').option('--strict', 'Fail on any error (default: be resilient, warn on non-critical issues)').configureHelp({
|
|
263
394
|
formatHelp
|
|
264
395
|
});
|
|
396
|
+
program.configureOutput({
|
|
397
|
+
writeErr: writeCommanderError
|
|
398
|
+
});
|
|
399
|
+
program.showHelpAfterError('(Run vizzly --help for available commands)');
|
|
400
|
+
program.showSuggestionAfterError();
|
|
401
|
+
program.exitOverride();
|
|
265
402
|
|
|
266
403
|
// Load plugins before defining commands
|
|
267
404
|
// We need to manually parse to get the config option early
|
|
@@ -314,7 +451,7 @@ try {
|
|
|
314
451
|
output.debug(`Plugin loading failed: ${error.message}`);
|
|
315
452
|
}
|
|
316
453
|
program.command('init').description('Initialize Vizzly in your project').option('--force', 'Overwrite existing configuration').option('--agent-skill', 'Install the repo-local Vizzly agent skill').option('--agent-guidance', 'Add Vizzly guidance to this project AGENTS.md and install the agent skill').option('--skip-agent-skill', 'Skip the Vizzly agent skill prompt').action(async options => {
|
|
317
|
-
let globalOptions =
|
|
454
|
+
let globalOptions = getGlobalOptions();
|
|
318
455
|
await init({
|
|
319
456
|
...globalOptions,
|
|
320
457
|
...options,
|
|
@@ -322,15 +459,19 @@ program.command('init').description('Initialize Vizzly in your project').option(
|
|
|
322
459
|
});
|
|
323
460
|
});
|
|
324
461
|
program.command('upload').description('Upload screenshots to Vizzly').argument('<path>', 'Path to screenshots directory').option('-b, --build-name <name>', 'Build name for grouping').option('-m, --metadata <json>', 'Additional metadata as JSON').option('--batch-size <n>', 'Upload batch size', Number).option('--upload-timeout <ms>', 'Upload timeout in milliseconds', Number).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', Number).option('--min-cluster-size <pixels>', 'Minimum changed-pixel cluster size', Number).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) => {
|
|
325
|
-
let globalOptions =
|
|
462
|
+
let globalOptions = getGlobalOptions();
|
|
326
463
|
|
|
327
464
|
// Validate options
|
|
328
465
|
const validationErrors = validateUploadOptions(path, options);
|
|
329
466
|
if (validationErrors.length > 0) {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
}
|
|
467
|
+
reportValidationErrors(validationErrors);
|
|
468
|
+
}
|
|
469
|
+
if (!existsSync(path)) {
|
|
470
|
+
output.error(`Path does not exist: ${path}`);
|
|
471
|
+
process.exit(1);
|
|
472
|
+
}
|
|
473
|
+
if (!statSync(path).isDirectory()) {
|
|
474
|
+
output.error(`Path is not a directory: ${path}`);
|
|
334
475
|
process.exit(1);
|
|
335
476
|
}
|
|
336
477
|
let result = await uploadCommand(path, options, globalOptions);
|
|
@@ -340,11 +481,11 @@ program.command('upload').description('Upload screenshots to Vizzly').argument('
|
|
|
340
481
|
});
|
|
341
482
|
|
|
342
483
|
// TDD command with subcommands - local visual review with an interactive dashboard
|
|
343
|
-
const tddCmd = program.command('tdd').description('Run tests in TDD mode with local visual review');
|
|
484
|
+
const tddCmd = program.command('tdd').description('Run tests in TDD mode with local visual review').showHelpAfterError('(Run vizzly tdd --help for available commands)').showSuggestionAfterError();
|
|
344
485
|
|
|
345
486
|
// TDD Start - Background server
|
|
346
|
-
tddCmd.command('start').description('Start background TDD server with dashboard').option('--port <port>', 'Port for TDD server'
|
|
347
|
-
|
|
487
|
+
tddCmd.command('start').description('Start background TDD server with dashboard').option('--port <port>', 'Port for TDD server').option('--open', 'Open dashboard in browser').option('--baseline-build <id>', 'Use specific build as baseline').option('--baseline-comparison <id>', 'Use specific comparison as baseline').option('--environment <env>', 'Environment name', 'test').option('--threshold <number>', 'Comparison threshold', Number).option('--min-cluster-size <pixels>', 'Minimum changed-pixel cluster size', Number).option('--timeout <ms>', 'Server timeout in milliseconds', '30000').option('--fail-on-diff', 'Fail tests when visual differences are detected').option('--token <token>', 'API token override').addOption(new Option('--daemon-child', 'Internal: run as daemon child process').hideHelp()).action(async options => {
|
|
488
|
+
let globalOptions = getGlobalOptions();
|
|
348
489
|
|
|
349
490
|
// If this is a daemon child process, run the server directly
|
|
350
491
|
if (options.daemonChild) {
|
|
@@ -353,45 +494,45 @@ tddCmd.command('start').description('Start background TDD server with dashboard'
|
|
|
353
494
|
}
|
|
354
495
|
let validationErrors = validateTddStartOptions(options);
|
|
355
496
|
if (validationErrors.length > 0) {
|
|
356
|
-
|
|
357
|
-
for (let error of validationErrors) {
|
|
358
|
-
output.printErr(` - ${error}`);
|
|
359
|
-
}
|
|
360
|
-
process.exit(1);
|
|
497
|
+
reportValidationErrors(validationErrors);
|
|
361
498
|
}
|
|
362
499
|
await tddStartCommand(options, globalOptions);
|
|
363
500
|
});
|
|
364
501
|
|
|
365
502
|
// TDD Stop - Kill background server
|
|
366
|
-
tddCmd.command('stop').description('Stop background TDD server').action(async options => {
|
|
367
|
-
|
|
503
|
+
tddCmd.command('stop').description('Stop background TDD server').option('--port <port>', 'Port for TDD server').action(async options => {
|
|
504
|
+
let globalOptions = getGlobalOptions();
|
|
505
|
+
let validationErrors = validateTddStartOptions(options);
|
|
506
|
+
if (validationErrors.length > 0) {
|
|
507
|
+
reportValidationErrors(validationErrors);
|
|
508
|
+
}
|
|
368
509
|
await tddStopCommand(options, globalOptions);
|
|
369
510
|
});
|
|
370
511
|
|
|
371
512
|
// TDD Status - Check server status
|
|
372
|
-
tddCmd.command('status').description('Check TDD server status').action(async options => {
|
|
373
|
-
|
|
513
|
+
tddCmd.command('status').description('Check TDD server status').option('--port <port>', 'Port for TDD server').action(async options => {
|
|
514
|
+
let globalOptions = getGlobalOptions();
|
|
515
|
+
let validationErrors = validateTddStartOptions(options);
|
|
516
|
+
if (validationErrors.length > 0) {
|
|
517
|
+
reportValidationErrors(validationErrors);
|
|
518
|
+
}
|
|
374
519
|
await tddStatusCommand(options, globalOptions);
|
|
375
520
|
});
|
|
376
521
|
|
|
377
522
|
// TDD List - List all running servers (for menubar app integration)
|
|
378
523
|
tddCmd.command('list').description('List all running TDD servers').action(async options => {
|
|
379
|
-
|
|
524
|
+
let globalOptions = getGlobalOptions();
|
|
380
525
|
await tddListCommand(options, globalOptions);
|
|
381
526
|
});
|
|
382
527
|
|
|
383
528
|
// TDD Run - One-off test run with ephemeral server (generates static report)
|
|
384
|
-
tddCmd.command('run <command>').description('Run tests once in TDD mode with local visual review').option('--port <port>', 'Port for TDD server'
|
|
385
|
-
|
|
529
|
+
tddCmd.command('run <command>').description('Run tests once in TDD mode with local visual review').option('--port <port>', 'Port for TDD server').option('--branch <branch>', 'Git branch override').option('--environment <env>', 'Environment name', 'test').option('--threshold <number>', 'Comparison threshold', Number).option('--min-cluster-size <pixels>', 'Minimum changed-pixel cluster size', Number).option('--token <token>', 'API token override').option('--timeout <ms>', 'Server timeout in milliseconds', '30000').option('--baseline-build <id>', 'Use specific build as baseline').option('--baseline-comparison <id>', 'Use specific comparison as baseline').option('--set-baseline', 'Accept current screenshots as new baseline (overwrites existing)').option('--fail-on-diff', 'Fail tests when visual differences are detected').option('--no-open', 'Skip opening report in browser').action(async (command, options) => {
|
|
530
|
+
let globalOptions = getGlobalOptions();
|
|
386
531
|
|
|
387
532
|
// Validate options
|
|
388
533
|
const validationErrors = validateTddOptions(command, options);
|
|
389
534
|
if (validationErrors.length > 0) {
|
|
390
|
-
|
|
391
|
-
for (let error of validationErrors) {
|
|
392
|
-
output.printErr(` - ${error}`);
|
|
393
|
-
}
|
|
394
|
-
process.exit(1);
|
|
535
|
+
reportValidationErrors(validationErrors);
|
|
395
536
|
}
|
|
396
537
|
const {
|
|
397
538
|
result,
|
|
@@ -416,8 +557,8 @@ tddCmd.command('run <command>').description('Run tests once in TDD mode with loc
|
|
|
416
557
|
process.once('SIGINT', sigintHandler);
|
|
417
558
|
process.once('SIGTERM', sigtermHandler);
|
|
418
559
|
|
|
419
|
-
// If
|
|
420
|
-
|
|
560
|
+
// If the run captured screenshots, generate a static report for review.
|
|
561
|
+
let hasComparisons = result?.screenshotsCaptured > 0 || result?.comparisons?.length > 0 || result?.summary?.total > 0;
|
|
421
562
|
if (hasComparisons) {
|
|
422
563
|
// Note: Tests have completed at this point, so report-data.json is stable.
|
|
423
564
|
// The report reflects the final state of all comparisons.
|
|
@@ -443,16 +584,12 @@ tddCmd.command('run <command>').description('Run tests once in TDD mode with loc
|
|
|
443
584
|
process.exit(result?.exitCode || 0);
|
|
444
585
|
});
|
|
445
586
|
program.command('run').description('Run tests with Vizzly integration').argument('<command>', 'Test command to run').option('--port <port>', 'Port for screenshot server', '47392').option('-b, --build-name <name>', 'Custom build name').option('--branch <branch>', 'Git branch override').option('--commit <sha>', 'Git commit SHA').option('--message <msg>', 'Commit message').option('--environment <env>', 'Environment name', 'test').option('--threshold <number>', 'Comparison threshold', Number).option('--min-cluster-size <pixels>', 'Minimum changed-pixel cluster size', Number).option('--batch-size <n>', 'Upload batch size', Number).option('--upload-timeout <ms>', 'Upload timeout in milliseconds', Number).option('--token <token>', 'API token override').option('--wait', 'Wait for build completion').option('--timeout <ms>', 'Server timeout in milliseconds', '30000').option('--allow-no-token', 'Allow running without API token').option('--upload-all', 'Upload all screenshots without SHA deduplication').option('--parallel-id <id>', 'Unique identifier for parallel test execution').action(async (command, options) => {
|
|
446
|
-
|
|
587
|
+
let globalOptions = getGlobalOptions();
|
|
447
588
|
|
|
448
589
|
// Validate options
|
|
449
590
|
const validationErrors = validateRunOptions(command, options);
|
|
450
591
|
if (validationErrors.length > 0) {
|
|
451
|
-
|
|
452
|
-
for (let error of validationErrors) {
|
|
453
|
-
output.printErr(` - ${error}`);
|
|
454
|
-
}
|
|
455
|
-
process.exit(1);
|
|
592
|
+
reportValidationErrors(validationErrors);
|
|
456
593
|
}
|
|
457
594
|
try {
|
|
458
595
|
const result = await runCommand(command, options, globalOptions);
|
|
@@ -465,16 +602,12 @@ program.command('run').description('Run tests with Vizzly integration').argument
|
|
|
465
602
|
}
|
|
466
603
|
});
|
|
467
604
|
program.command('status').description('Check the status of a build').argument('<build-id>', 'Build ID to check status for').action(async (buildId, options) => {
|
|
468
|
-
|
|
605
|
+
let globalOptions = getGlobalOptions();
|
|
469
606
|
|
|
470
607
|
// Validate options
|
|
471
608
|
const validationErrors = validateStatusOptions(buildId, options);
|
|
472
609
|
if (validationErrors.length > 0) {
|
|
473
|
-
|
|
474
|
-
for (let error of validationErrors) {
|
|
475
|
-
output.printErr(` - ${error}`);
|
|
476
|
-
}
|
|
477
|
-
process.exit(1);
|
|
610
|
+
reportValidationErrors(validationErrors);
|
|
478
611
|
}
|
|
479
612
|
await statusCommand(buildId, options, globalOptions);
|
|
480
613
|
});
|
|
@@ -489,16 +622,12 @@ Examples:
|
|
|
489
622
|
$ vizzly builds -b abc123 --comparisons # Include comparisons
|
|
490
623
|
$ vizzly builds --json # Output as JSON for scripting
|
|
491
624
|
`).action(async options => {
|
|
492
|
-
|
|
625
|
+
let globalOptions = getGlobalOptions();
|
|
493
626
|
|
|
494
627
|
// Validate options
|
|
495
628
|
const validationErrors = validateBuildsOptions(options);
|
|
496
629
|
if (validationErrors.length > 0) {
|
|
497
|
-
|
|
498
|
-
for (let error of validationErrors) {
|
|
499
|
-
output.printErr(` - ${error}`);
|
|
500
|
-
}
|
|
501
|
-
process.exit(1);
|
|
630
|
+
reportValidationErrors(validationErrors);
|
|
502
631
|
}
|
|
503
632
|
await buildsCommand(options, globalOptions);
|
|
504
633
|
});
|
|
@@ -512,20 +641,16 @@ Examples:
|
|
|
512
641
|
$ vizzly comparisons --status changed # Only changed comparisons
|
|
513
642
|
$ vizzly comparisons --json # Output as JSON for scripting
|
|
514
643
|
`).action(async options => {
|
|
515
|
-
|
|
644
|
+
let globalOptions = getGlobalOptions();
|
|
516
645
|
|
|
517
646
|
// Validate options
|
|
518
647
|
const validationErrors = validateComparisonsOptions(options);
|
|
519
648
|
if (validationErrors.length > 0) {
|
|
520
|
-
|
|
521
|
-
for (let error of validationErrors) {
|
|
522
|
-
output.printErr(` - ${error}`);
|
|
523
|
-
}
|
|
524
|
-
process.exit(1);
|
|
649
|
+
reportValidationErrors(validationErrors);
|
|
525
650
|
}
|
|
526
651
|
await comparisonsCommand(options, globalOptions);
|
|
527
652
|
});
|
|
528
|
-
let contextCmd = program.command('context').description('Fetch build and diff context');
|
|
653
|
+
let contextCmd = program.command('context').description('Fetch build and diff context').showHelpAfterError('(Run vizzly context --help for available commands)').showSuggestionAfterError();
|
|
529
654
|
contextCmd.command('build').description('Fetch build context').argument('<build-id>', 'Build ID to fetch context for').option('--source <source>', 'Context source: auto, cloud, or local', 'auto').option('--agent', 'Output compact context for LLM agents').option('--full', 'Return the full build context payload with --agent --json').option('--include <items>', 'Add detail to compact agent JSON: screenshots,diffs,comments').addHelpText('after', `
|
|
530
655
|
Examples:
|
|
531
656
|
$ vizzly context build abc123
|
|
@@ -535,14 +660,10 @@ Examples:
|
|
|
535
660
|
$ vizzly context build abc123 --agent --json --include diffs,comments
|
|
536
661
|
$ vizzly context build abc123 --agent --json --full
|
|
537
662
|
`).action(async (buildId, options) => {
|
|
538
|
-
|
|
663
|
+
let globalOptions = getGlobalOptions();
|
|
539
664
|
const validationErrors = validateContextBuildOptions(options);
|
|
540
665
|
if (validationErrors.length > 0) {
|
|
541
|
-
|
|
542
|
-
for (let error of validationErrors) {
|
|
543
|
-
output.printErr(` - ${error}`);
|
|
544
|
-
}
|
|
545
|
-
process.exit(1);
|
|
666
|
+
reportValidationErrors(validationErrors);
|
|
546
667
|
}
|
|
547
668
|
await contextBuildCommand(buildId, options, globalOptions);
|
|
548
669
|
});
|
|
@@ -553,14 +674,10 @@ Examples:
|
|
|
553
674
|
$ vizzly context comparison def456 --similar-limit 5 --recent-limit 5
|
|
554
675
|
$ vizzly context comparison def456 --json
|
|
555
676
|
`).action(async (comparisonId, options) => {
|
|
556
|
-
|
|
677
|
+
let globalOptions = getGlobalOptions();
|
|
557
678
|
const validationErrors = validateContextComparisonOptions(options);
|
|
558
679
|
if (validationErrors.length > 0) {
|
|
559
|
-
|
|
560
|
-
for (let error of validationErrors) {
|
|
561
|
-
output.printErr(` - ${error}`);
|
|
562
|
-
}
|
|
563
|
-
process.exit(1);
|
|
680
|
+
reportValidationErrors(validationErrors);
|
|
564
681
|
}
|
|
565
682
|
await contextComparisonCommand(comparisonId, options, globalOptions);
|
|
566
683
|
});
|
|
@@ -571,14 +688,10 @@ Examples:
|
|
|
571
688
|
$ vizzly context screenshot Dashboard --project storybook --org acme
|
|
572
689
|
$ vizzly context screenshot Dashboard --json
|
|
573
690
|
`).action(async (name, options) => {
|
|
574
|
-
|
|
691
|
+
let globalOptions = getGlobalOptions();
|
|
575
692
|
const validationErrors = validateContextScreenshotOptions(options);
|
|
576
693
|
if (validationErrors.length > 0) {
|
|
577
|
-
|
|
578
|
-
for (let error of validationErrors) {
|
|
579
|
-
output.printErr(` - ${error}`);
|
|
580
|
-
}
|
|
581
|
-
process.exit(1);
|
|
694
|
+
reportValidationErrors(validationErrors);
|
|
582
695
|
}
|
|
583
696
|
await contextScreenshotCommand(name, options, globalOptions);
|
|
584
697
|
});
|
|
@@ -588,14 +701,10 @@ Examples:
|
|
|
588
701
|
$ vizzly context similar fp-dashboard --project storybook --org acme
|
|
589
702
|
$ vizzly context similar fp-dashboard --json
|
|
590
703
|
`).action(async (fingerprintHash, options) => {
|
|
591
|
-
|
|
704
|
+
let globalOptions = getGlobalOptions();
|
|
592
705
|
const validationErrors = validateContextSimilarOptions(options);
|
|
593
706
|
if (validationErrors.length > 0) {
|
|
594
|
-
|
|
595
|
-
for (let error of validationErrors) {
|
|
596
|
-
output.printErr(` - ${error}`);
|
|
597
|
-
}
|
|
598
|
-
process.exit(1);
|
|
707
|
+
reportValidationErrors(validationErrors);
|
|
599
708
|
}
|
|
600
709
|
await contextSimilarCommand(fingerprintHash, options, globalOptions);
|
|
601
710
|
});
|
|
@@ -606,28 +715,20 @@ Examples:
|
|
|
606
715
|
$ vizzly context review-queue --limit 10
|
|
607
716
|
$ vizzly context review-queue --json
|
|
608
717
|
`).action(async options => {
|
|
609
|
-
|
|
718
|
+
let globalOptions = getGlobalOptions();
|
|
610
719
|
const validationErrors = validateContextReviewQueueOptions(options);
|
|
611
720
|
if (validationErrors.length > 0) {
|
|
612
|
-
|
|
613
|
-
for (let error of validationErrors) {
|
|
614
|
-
output.printErr(` - ${error}`);
|
|
615
|
-
}
|
|
616
|
-
process.exit(1);
|
|
721
|
+
reportValidationErrors(validationErrors);
|
|
617
722
|
}
|
|
618
723
|
await contextReviewQueueCommand(options, globalOptions);
|
|
619
724
|
});
|
|
620
725
|
program.command('config').description('Display current configuration').argument('[key]', 'Specific config key to get (dot notation, e.g., comparison.threshold)').action(async (key, options) => {
|
|
621
|
-
|
|
726
|
+
let globalOptions = getGlobalOptions();
|
|
622
727
|
|
|
623
728
|
// Validate options
|
|
624
729
|
const validationErrors = validateConfigOptions(options);
|
|
625
730
|
if (validationErrors.length > 0) {
|
|
626
|
-
|
|
627
|
-
for (let error of validationErrors) {
|
|
628
|
-
output.printErr(` - ${error}`);
|
|
629
|
-
}
|
|
630
|
-
process.exit(1);
|
|
731
|
+
reportValidationErrors(validationErrors);
|
|
631
732
|
}
|
|
632
733
|
await configCommand(key, options, globalOptions);
|
|
633
734
|
});
|
|
@@ -640,16 +741,12 @@ Examples:
|
|
|
640
741
|
|
|
641
742
|
Note: Baselines are stored locally in .vizzly/baselines/ during TDD mode.
|
|
642
743
|
`).action(async options => {
|
|
643
|
-
|
|
744
|
+
let globalOptions = getGlobalOptions();
|
|
644
745
|
|
|
645
746
|
// Validate options
|
|
646
747
|
const validationErrors = validateBaselinesOptions(options);
|
|
647
748
|
if (validationErrors.length > 0) {
|
|
648
|
-
|
|
649
|
-
for (let error of validationErrors) {
|
|
650
|
-
output.printErr(` - ${error}`);
|
|
651
|
-
}
|
|
652
|
-
process.exit(1);
|
|
749
|
+
reportValidationErrors(validationErrors);
|
|
653
750
|
}
|
|
654
751
|
await baselinesCommand(options, globalOptions);
|
|
655
752
|
});
|
|
@@ -664,20 +761,16 @@ Examples:
|
|
|
664
761
|
Note: POST is restricted to approve, reject, and comment endpoints.
|
|
665
762
|
Most operations have dedicated commands (builds, comparisons, approve, etc.).
|
|
666
763
|
`).action(async (endpoint, options) => {
|
|
667
|
-
|
|
764
|
+
let globalOptions = getGlobalOptions();
|
|
668
765
|
|
|
669
766
|
// Validate options
|
|
670
767
|
const validationErrors = validateApiOptions(endpoint, options);
|
|
671
768
|
if (validationErrors.length > 0) {
|
|
672
|
-
|
|
673
|
-
for (let error of validationErrors) {
|
|
674
|
-
output.printErr(` - ${error}`);
|
|
675
|
-
}
|
|
676
|
-
process.exit(1);
|
|
769
|
+
reportValidationErrors(validationErrors);
|
|
677
770
|
}
|
|
678
771
|
await apiCommand(endpoint, options, globalOptions);
|
|
679
772
|
});
|
|
680
|
-
program.command('approve').description('Approve a comparison').argument('<comparison-id>', 'Comparison ID to approve
|
|
773
|
+
program.command('approve').description('Approve a comparison').argument('<comparison-id>', 'Comparison ID to approve').option('-m, --comment <message>', 'Optional comment explaining the approval').addHelpText('after', `
|
|
681
774
|
Examples:
|
|
682
775
|
$ vizzly approve abc123-def456-7890 # Approve a comparison
|
|
683
776
|
$ vizzly approve abc123 -m "LGTM" # Approve with comment
|
|
@@ -688,18 +781,14 @@ Workflow:
|
|
|
688
781
|
2. Review the changes in the web UI or via URLs in the output
|
|
689
782
|
3. Approve: vizzly approve <comparison-id>
|
|
690
783
|
`).action(async (comparisonId, options) => {
|
|
691
|
-
|
|
784
|
+
let globalOptions = getGlobalOptions();
|
|
692
785
|
const validationErrors = validateApproveOptions(comparisonId, options);
|
|
693
786
|
if (validationErrors.length > 0) {
|
|
694
|
-
|
|
695
|
-
for (let error of validationErrors) {
|
|
696
|
-
output.printErr(` - ${error}`);
|
|
697
|
-
}
|
|
698
|
-
process.exit(1);
|
|
787
|
+
reportValidationErrors(validationErrors);
|
|
699
788
|
}
|
|
700
789
|
await approveCommand(comparisonId, options, globalOptions);
|
|
701
790
|
});
|
|
702
|
-
program.command('reject').description('Reject a comparison').argument('<comparison-id>', 'Comparison ID to reject
|
|
791
|
+
program.command('reject').description('Reject a comparison').argument('<comparison-id>', 'Comparison ID to reject').option('-r, --reason <message>', 'Required reason for rejection').addHelpText('after', `
|
|
703
792
|
Examples:
|
|
704
793
|
$ vizzly reject abc123 -r "Button color is wrong"
|
|
705
794
|
$ vizzly reject abc123 --reason "Needs design review"
|
|
@@ -710,18 +799,14 @@ Workflow:
|
|
|
710
799
|
2. Review the changes in the web UI or via URLs in the output
|
|
711
800
|
3. Reject with reason: vizzly reject <comparison-id> -r "reason"
|
|
712
801
|
`).action(async (comparisonId, options) => {
|
|
713
|
-
|
|
802
|
+
let globalOptions = getGlobalOptions();
|
|
714
803
|
const validationErrors = validateRejectOptions(comparisonId, options);
|
|
715
804
|
if (validationErrors.length > 0) {
|
|
716
|
-
|
|
717
|
-
for (let error of validationErrors) {
|
|
718
|
-
output.printErr(` - ${error}`);
|
|
719
|
-
}
|
|
720
|
-
process.exit(1);
|
|
805
|
+
reportValidationErrors(validationErrors);
|
|
721
806
|
}
|
|
722
807
|
await rejectCommand(comparisonId, options, globalOptions);
|
|
723
808
|
});
|
|
724
|
-
program.command('comment').description('Add a comment to a build').argument('<build-id>', 'Build ID to comment on
|
|
809
|
+
program.command('comment').description('Add a comment to a build').argument('<build-id>', 'Build ID to comment on').argument('<message>', 'Comment message').option('-t, --type <type>', 'Comment type: general, approval, rejection', 'general').addHelpText('after', `
|
|
725
810
|
Examples:
|
|
726
811
|
$ vizzly comment abc123 "Looks good overall"
|
|
727
812
|
$ vizzly comment abc123 "Approved" -t approval
|
|
@@ -732,14 +817,10 @@ Workflow:
|
|
|
732
817
|
1. Get build ID: vizzly builds --branch main
|
|
733
818
|
2. Add comment: vizzly comment <build-id> "Your message"
|
|
734
819
|
`).action(async (buildId, message, options) => {
|
|
735
|
-
|
|
820
|
+
let globalOptions = getGlobalOptions();
|
|
736
821
|
const validationErrors = validateCommentOptions(buildId, message, options);
|
|
737
822
|
if (validationErrors.length > 0) {
|
|
738
|
-
|
|
739
|
-
for (let error of validationErrors) {
|
|
740
|
-
output.printErr(` - ${error}`);
|
|
741
|
-
}
|
|
742
|
-
process.exit(1);
|
|
823
|
+
reportValidationErrors(validationErrors);
|
|
743
824
|
}
|
|
744
825
|
await commentCommand(buildId, message, options, globalOptions);
|
|
745
826
|
});
|
|
@@ -751,14 +832,10 @@ Examples:
|
|
|
751
832
|
Note: Shows organizations from your user account (via vizzly login)
|
|
752
833
|
or the single organization for a project token.
|
|
753
834
|
`).action(async options => {
|
|
754
|
-
|
|
835
|
+
let globalOptions = getGlobalOptions();
|
|
755
836
|
const validationErrors = validateOrgsOptions(options);
|
|
756
837
|
if (validationErrors.length > 0) {
|
|
757
|
-
|
|
758
|
-
for (let error of validationErrors) {
|
|
759
|
-
output.printErr(` - ${error}`);
|
|
760
|
-
}
|
|
761
|
-
process.exit(1);
|
|
838
|
+
reportValidationErrors(validationErrors);
|
|
762
839
|
}
|
|
763
840
|
await orgsCommand(options, globalOptions);
|
|
764
841
|
});
|
|
@@ -773,18 +850,14 @@ Workflow:
|
|
|
773
850
|
2. List projects: vizzly projects --org <org-slug>
|
|
774
851
|
3. Query builds: vizzly builds
|
|
775
852
|
`).action(async options => {
|
|
776
|
-
|
|
853
|
+
let globalOptions = getGlobalOptions();
|
|
777
854
|
const validationErrors = validateProjectsOptions(options);
|
|
778
855
|
if (validationErrors.length > 0) {
|
|
779
|
-
|
|
780
|
-
for (let error of validationErrors) {
|
|
781
|
-
output.printErr(` - ${error}`);
|
|
782
|
-
}
|
|
783
|
-
process.exit(1);
|
|
856
|
+
reportValidationErrors(validationErrors);
|
|
784
857
|
}
|
|
785
858
|
await projectsCommand(options, globalOptions);
|
|
786
859
|
});
|
|
787
|
-
let projectCommand = program.command('project').description('Manage the project linked to this local checkout');
|
|
860
|
+
let projectCommand = program.command('project').description('Manage the project linked to this local checkout').showHelpAfterError('(Run vizzly project --help for available commands)').showSuggestionAfterError();
|
|
788
861
|
projectCommand.command('link [selector]').description('Link a Vizzly project for cloud uploads').option('--org <slug>', 'Organization slug').option('--project <slug>', 'Project slug').option('--name <name>', 'Credential name shown in Vizzly').option('--expires-at <iso>', 'Optional token expiration timestamp').addHelpText('after', `
|
|
789
862
|
Examples:
|
|
790
863
|
$ vizzly project link vizzly/storybook
|
|
@@ -793,111 +866,85 @@ Examples:
|
|
|
793
866
|
Note: run "vizzly login" first. The linked credential is project-scoped and is
|
|
794
867
|
used for cloud uploads; your user login remains separate for review actions.
|
|
795
868
|
`).action(async (selector, options) => {
|
|
796
|
-
|
|
869
|
+
let globalOptions = getGlobalOptions();
|
|
797
870
|
const validationErrors = validateProjectLinkOptions(selector, options);
|
|
798
871
|
if (validationErrors.length > 0) {
|
|
799
|
-
|
|
800
|
-
for (let error of validationErrors) {
|
|
801
|
-
output.printErr(` - ${error}`);
|
|
802
|
-
}
|
|
803
|
-
process.exit(1);
|
|
872
|
+
reportValidationErrors(validationErrors);
|
|
804
873
|
}
|
|
805
874
|
await projectLinkCommand(selector, options, globalOptions);
|
|
806
875
|
});
|
|
807
876
|
program.command('finalize').description('Finalize a parallel build after all shards complete').argument('<parallel-id>', 'Parallel ID to finalize').action(async (parallelId, options) => {
|
|
808
|
-
|
|
877
|
+
let globalOptions = getGlobalOptions();
|
|
809
878
|
|
|
810
879
|
// Validate options
|
|
811
880
|
const validationErrors = validateFinalizeOptions(parallelId, options);
|
|
812
881
|
if (validationErrors.length > 0) {
|
|
813
|
-
|
|
814
|
-
for (let error of validationErrors) {
|
|
815
|
-
output.printErr(` - ${error}`);
|
|
816
|
-
}
|
|
817
|
-
process.exit(1);
|
|
882
|
+
reportValidationErrors(validationErrors);
|
|
818
883
|
}
|
|
819
884
|
await finalizeCommand(parallelId, options, globalOptions);
|
|
820
885
|
});
|
|
821
886
|
program.command('preview').description('Upload static files as a preview for a build').argument('[path]', 'Path to static files (dist/, build/, out/)').option('-b, --build <id>', 'Build ID to attach preview to').option('--base <path>', 'Override auto-detected base path').option('--open', 'Open preview URL in browser after upload').option('--dry-run', 'Show what would be uploaded without uploading').option('--public-link', 'Acknowledge that preview URL grants access to anyone with the link (required for private projects)').option('-x, --exclude <pattern>', 'Exclude files/dirs (repeatable, e.g. -x "*.log" -x "temp/")', (val, prev) => prev ? [...prev, val] : [val]).option('-i, --include <pattern>', 'Override default exclusions (repeatable, e.g. -i package.json -i tests/)', (val, prev) => prev ? [...prev, val] : [val]).action(async (path, options) => {
|
|
822
|
-
|
|
887
|
+
let globalOptions = getGlobalOptions();
|
|
823
888
|
|
|
824
889
|
// Show helpful error if path is missing
|
|
825
890
|
if (!path) {
|
|
826
891
|
output.error('Path to static files is required');
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
892
|
+
if (!globalOptions.json) {
|
|
893
|
+
output.blank();
|
|
894
|
+
output.print(' Upload your build output directory:');
|
|
895
|
+
output.blank();
|
|
896
|
+
output.print(' vizzly preview ./dist');
|
|
897
|
+
output.print(' vizzly preview ./build');
|
|
898
|
+
output.print(' vizzly preview ./out');
|
|
899
|
+
output.blank();
|
|
900
|
+
}
|
|
834
901
|
process.exit(1);
|
|
835
902
|
}
|
|
836
903
|
|
|
837
904
|
// Validate options
|
|
838
905
|
const validationErrors = validatePreviewOptions(path, options);
|
|
839
906
|
if (validationErrors.length > 0) {
|
|
840
|
-
|
|
841
|
-
for (let error of validationErrors) {
|
|
842
|
-
output.printErr(` - ${error}`);
|
|
843
|
-
}
|
|
844
|
-
process.exit(1);
|
|
907
|
+
reportValidationErrors(validationErrors);
|
|
845
908
|
}
|
|
846
909
|
await previewCommand(path, options, globalOptions);
|
|
847
910
|
});
|
|
848
911
|
program.command('doctor').description('Run diagnostics to check your environment and configuration').option('--api', 'Include API connectivity checks').action(async options => {
|
|
849
|
-
|
|
912
|
+
let globalOptions = getGlobalOptions();
|
|
850
913
|
|
|
851
914
|
// Validate options
|
|
852
915
|
const validationErrors = validateDoctorOptions(options);
|
|
853
916
|
if (validationErrors.length > 0) {
|
|
854
|
-
|
|
855
|
-
for (let error of validationErrors) {
|
|
856
|
-
output.printErr(` - ${error}`);
|
|
857
|
-
}
|
|
858
|
-
process.exit(1);
|
|
917
|
+
reportValidationErrors(validationErrors);
|
|
859
918
|
}
|
|
860
919
|
await doctorCommand(options, globalOptions);
|
|
861
920
|
});
|
|
862
921
|
program.command('login').description('Authenticate with your Vizzly account').option('--api-url <url>', 'API URL override').action(async options => {
|
|
863
|
-
|
|
922
|
+
let globalOptions = getGlobalOptions();
|
|
864
923
|
|
|
865
924
|
// Validate options
|
|
866
925
|
const validationErrors = validateLoginOptions(options);
|
|
867
926
|
if (validationErrors.length > 0) {
|
|
868
|
-
|
|
869
|
-
for (let error of validationErrors) {
|
|
870
|
-
output.printErr(` - ${error}`);
|
|
871
|
-
}
|
|
872
|
-
process.exit(1);
|
|
927
|
+
reportValidationErrors(validationErrors);
|
|
873
928
|
}
|
|
874
929
|
await loginCommand(options, globalOptions);
|
|
875
930
|
});
|
|
876
931
|
program.command('logout').description('Clear stored authentication tokens').option('--api-url <url>', 'API URL override').action(async options => {
|
|
877
|
-
|
|
932
|
+
let globalOptions = getGlobalOptions();
|
|
878
933
|
|
|
879
934
|
// Validate options
|
|
880
935
|
const validationErrors = validateLogoutOptions(options);
|
|
881
936
|
if (validationErrors.length > 0) {
|
|
882
|
-
|
|
883
|
-
for (let error of validationErrors) {
|
|
884
|
-
output.printErr(` - ${error}`);
|
|
885
|
-
}
|
|
886
|
-
process.exit(1);
|
|
937
|
+
reportValidationErrors(validationErrors);
|
|
887
938
|
}
|
|
888
939
|
await logoutCommand(options, globalOptions);
|
|
889
940
|
});
|
|
890
941
|
program.command('whoami').description('Show current authentication status and user information').option('--api-url <url>', 'API URL override').action(async options => {
|
|
891
|
-
|
|
942
|
+
let globalOptions = getGlobalOptions();
|
|
892
943
|
|
|
893
944
|
// Validate options
|
|
894
945
|
const validationErrors = validateWhoamiOptions(options);
|
|
895
946
|
if (validationErrors.length > 0) {
|
|
896
|
-
|
|
897
|
-
for (let error of validationErrors) {
|
|
898
|
-
output.printErr(` - ${error}`);
|
|
899
|
-
}
|
|
900
|
-
process.exit(1);
|
|
947
|
+
reportValidationErrors(validationErrors);
|
|
901
948
|
}
|
|
902
949
|
await whoamiCommand(options, globalOptions);
|
|
903
950
|
});
|
|
@@ -906,6 +953,7 @@ program.command('whoami').description('Show current authentication status and us
|
|
|
906
953
|
// This auto-configures the menubar app so it can find package runners/node
|
|
907
954
|
saveUserPath().catch(() => {});
|
|
908
955
|
let commandNames = new Set(program.commands.map(command => command.name()));
|
|
956
|
+
let nestedCommandNames = new Map(program.commands.map(command => [command.name(), new Set(command.commands.map(subcommand => subcommand.name()))]));
|
|
909
957
|
let normalizedArgv = normalizeJsonArgv(process.argv, commandNames);
|
|
910
958
|
let normalizedGlobals = extractGlobalOptionsFromArgv(normalizedArgv, commandNames);
|
|
911
959
|
output.configure({
|
|
@@ -915,4 +963,19 @@ output.configure({
|
|
|
915
963
|
json: normalizedGlobals.jsonArg,
|
|
916
964
|
resetTimer: false
|
|
917
965
|
});
|
|
918
|
-
|
|
966
|
+
let requestedCommand = findRequestedCommand(normalizedArgv, commandNames, nestedCommandNames);
|
|
967
|
+
if (requestedCommand && !requestedCommand.known) {
|
|
968
|
+
output.error(`unknown command '${requestedCommand.name}'`);
|
|
969
|
+
if (!output.isJson() && requestedCommand.helpCommand) {
|
|
970
|
+
output.printErr(`(Run ${requestedCommand.helpCommand} for available commands)`);
|
|
971
|
+
}
|
|
972
|
+
process.exit(1);
|
|
973
|
+
}
|
|
974
|
+
try {
|
|
975
|
+
program.parse(normalizedArgv);
|
|
976
|
+
} catch (error) {
|
|
977
|
+
if (error.exitCode == null) {
|
|
978
|
+
output.error('Unexpected CLI error', error);
|
|
979
|
+
}
|
|
980
|
+
process.exit(error.exitCode ?? 1);
|
|
981
|
+
}
|