@vizzly-testing/cli 0.38.1 → 0.39.1
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 +222 -195
- package/dist/cli.js +55 -54
- package/dist/commands/init.js +154 -483
- package/dist/commands/project.js +128 -44
- package/dist/commands/run.js +2 -2
- package/dist/commands/tdd.js +89 -38
- package/dist/commands/upload.js +2 -2
- package/dist/init/agents.js +140 -0
- package/dist/init/core.js +31 -0
- package/dist/init/prompts.js +44 -0
- package/dist/reporter/reporter-bundle.css +1 -1
- package/dist/reporter/reporter-bundle.iife.js +30 -61
- package/dist/reporter-ssr/ssr-entry.js +3 -6
- package/dist/server/handlers/tdd-handler.js +33 -2
- package/dist/services/index.js +8 -2
- package/dist/tdd/services/comparison-service.js +4 -1
- package/dist/tdd/tdd-service.js +14 -2
- package/dist/test-runner/core.js +28 -3
- package/dist/test-runner/operations.js +41 -20
- package/dist/types/index.d.ts +8 -0
- package/dist/uploader/core.js +1 -0
- package/dist/utils/cli-update.js +105 -0
- package/dist/utils/config-discovery.js +73 -0
- package/dist/utils/config-loader.js +29 -8
- package/dist/utils/config-schema.js +5 -0
- package/dist/utils/file-changes.js +105 -0
- package/dist/utils/project-binding.js +96 -0
- package/dist/utils/project-link-store.js +13 -7
- package/dist/utils/update-check.js +2 -0
- package/dist/utils/update-notifications.js +175 -0
- package/package.json +16 -16
- package/skills/vizzly/SKILL.md +4 -3
- package/skills/vizzly/references/ci.md +33 -0
- package/skills/vizzly/references/cli-context.md +81 -71
- package/skills/vizzly/references/dynamic-content.md +16 -17
- package/skills/vizzly/references/sdks.md +22 -22
- package/skills/vizzly/references/setup-ci.md +0 -44
package/dist/cli.js
CHANGED
|
@@ -28,15 +28,15 @@ import { whoamiCommand } from './commands/whoami.js';
|
|
|
28
28
|
import { createPluginServices } from './plugin-api.js';
|
|
29
29
|
import { loadPlugins } from './plugin-loader.js';
|
|
30
30
|
import { createServices } from './services/index.js';
|
|
31
|
-
import { generateStaticReport, getReportFileUrl } from './services/static-report-generator.js';
|
|
32
31
|
import { withTimeout } from './utils/async-utils.js';
|
|
33
|
-
import {
|
|
32
|
+
import { runUpdatedCli } from './utils/cli-update.js';
|
|
34
33
|
import { colors } from './utils/colors.js';
|
|
35
34
|
import { loadConfig } from './utils/config-loader.js';
|
|
36
35
|
import { getContext } from './utils/context.js';
|
|
37
36
|
import { saveUserPath } from './utils/global-config.js';
|
|
38
37
|
import * as output from './utils/output.js';
|
|
39
38
|
import { getPackageVersion } from './utils/package-info.js';
|
|
39
|
+
import { notifyUpdates } from './utils/update-notifications.js';
|
|
40
40
|
|
|
41
41
|
// Custom help formatting with BearDen design system
|
|
42
42
|
const formatHelp = (cmd, helper) => {
|
|
@@ -395,7 +395,7 @@ function writeCommanderError(text) {
|
|
|
395
395
|
}
|
|
396
396
|
}
|
|
397
397
|
}
|
|
398
|
-
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({
|
|
398
|
+
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('--no-update-notifier', 'Skip update checks and notices').option('--strict', 'Fail on any error (default: be resilient, warn on non-critical issues)').configureHelp({
|
|
399
399
|
formatHelp
|
|
400
400
|
});
|
|
401
401
|
program.configureOutput({
|
|
@@ -404,6 +404,13 @@ program.configureOutput({
|
|
|
404
404
|
program.showHelpAfterError('(Run vizzly --help for available commands)');
|
|
405
405
|
program.showSuggestionAfterError();
|
|
406
406
|
program.exitOverride();
|
|
407
|
+
program.hook('preAction', async (_command, actionCommand) => {
|
|
408
|
+
if (actionCommand.name() === 'init') return;
|
|
409
|
+
let updatedCli = await notifyUpdates(actionCommand.optsWithGlobals());
|
|
410
|
+
if (!updatedCli) return;
|
|
411
|
+
let code = await runUpdatedCli(updatedCli, process.argv.slice(2), process.cwd());
|
|
412
|
+
process.exit(code);
|
|
413
|
+
});
|
|
407
414
|
|
|
408
415
|
// Load plugins before defining commands
|
|
409
416
|
// We need to manually parse to get the config option early
|
|
@@ -430,38 +437,49 @@ output.configure({
|
|
|
430
437
|
color: colorOverride,
|
|
431
438
|
json: jsonArg
|
|
432
439
|
});
|
|
433
|
-
let
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
let
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
440
|
+
let setupCommand = findRequestedCommand(process.argv, new Set(['init', 'project']))?.name;
|
|
441
|
+
let needsRuntimeBootstrap = !['init', 'project'].includes(setupCommand);
|
|
442
|
+
if (needsRuntimeBootstrap) {
|
|
443
|
+
let config = await loadConfig(configPath, {
|
|
444
|
+
token: tokenArg
|
|
445
|
+
});
|
|
446
|
+
let services = createServices(config);
|
|
447
|
+
let pluginServices = createPluginServices(services);
|
|
448
|
+
try {
|
|
449
|
+
let plugins = await loadPlugins(configPath, config);
|
|
450
|
+
for (let plugin of plugins) {
|
|
451
|
+
try {
|
|
452
|
+
// Add timeout protection for plugin registration (5 seconds)
|
|
453
|
+
let registerPromise = plugin.register(program, {
|
|
454
|
+
config,
|
|
455
|
+
services: pluginServices,
|
|
456
|
+
output
|
|
457
|
+
});
|
|
458
|
+
await withTimeout(registerPromise, 5000, 'Plugin registration timeout (5s)');
|
|
459
|
+
output.debug(`Registered plugin: ${plugin.name}`);
|
|
460
|
+
} catch (error) {
|
|
461
|
+
output.warn(`Failed to register plugin ${plugin.name}: ${error.message}`);
|
|
462
|
+
}
|
|
453
463
|
}
|
|
464
|
+
} catch (error) {
|
|
465
|
+
output.debug(`Plugin loading failed: ${error.message}`);
|
|
454
466
|
}
|
|
455
|
-
} catch (error) {
|
|
456
|
-
output.debug(`Plugin loading failed: ${error.message}`);
|
|
457
467
|
}
|
|
458
|
-
program.command('init').description('Initialize Vizzly in your project').option('--force', 'Overwrite existing configuration').option('--agent-skill', 'Install or refresh the
|
|
468
|
+
program.command('init').description('Initialize Vizzly in your project').option('--quick', 'Create an editable config without the setup wizard').option('--force', 'Overwrite existing configuration').option('--agent-skill', 'Install or refresh the Vizzly agent skill').option('--agent-guidance', 'Install or refresh the Vizzly skill and AGENTS.md guidance').option('--skip-agent-skill', 'Skip the Vizzly agent skill prompt').action(async options => {
|
|
459
469
|
let globalOptions = getGlobalOptions();
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
470
|
+
try {
|
|
471
|
+
let result = await init({
|
|
472
|
+
...globalOptions,
|
|
473
|
+
...options
|
|
474
|
+
});
|
|
475
|
+
process.exitCode = result.exitCode || 0;
|
|
476
|
+
} catch (error) {
|
|
477
|
+
output.error(error.message, error, {
|
|
478
|
+
changes: error.completed || []
|
|
479
|
+
});
|
|
480
|
+
if (error.completed?.length) output.list(error.completed.map(change => change.path));
|
|
481
|
+
process.exitCode = error.code === 'INIT_INPUT_REQUIRED' ? 2 : 1;
|
|
482
|
+
}
|
|
465
483
|
});
|
|
466
484
|
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) => {
|
|
467
485
|
let globalOptions = getGlobalOptions();
|
|
@@ -555,7 +573,10 @@ tddCmd.command('run <command>').description('Run tests once in TDD mode with loc
|
|
|
555
573
|
const {
|
|
556
574
|
result,
|
|
557
575
|
cleanup
|
|
558
|
-
} = await tddCommand(command,
|
|
576
|
+
} = await tddCommand(command, {
|
|
577
|
+
...options,
|
|
578
|
+
autoPort: options.port === undefined
|
|
579
|
+
}, globalOptions);
|
|
559
580
|
|
|
560
581
|
// Track cleanup state to prevent double cleanup
|
|
561
582
|
let isCleaningUp = false;
|
|
@@ -575,26 +596,6 @@ tddCmd.command('run <command>').description('Run tests once in TDD mode with loc
|
|
|
575
596
|
process.once('SIGINT', sigintHandler);
|
|
576
597
|
process.once('SIGTERM', sigtermHandler);
|
|
577
598
|
|
|
578
|
-
// If the run captured screenshots, generate a static report for review.
|
|
579
|
-
let hasComparisons = result?.screenshotsCaptured > 0 || result?.comparisons?.length > 0 || result?.summary?.total > 0;
|
|
580
|
-
if (hasComparisons) {
|
|
581
|
-
// Note: Tests have completed at this point, so report-data.json is stable.
|
|
582
|
-
// The report reflects the final state of all comparisons.
|
|
583
|
-
const reportResult = await generateStaticReport(process.cwd());
|
|
584
|
-
if (reportResult.success) {
|
|
585
|
-
const reportUrl = getReportFileUrl(reportResult.reportPath);
|
|
586
|
-
output.print(` ${colors.brand.textTertiary('→')} Report: ${colors.info(reportUrl)}`);
|
|
587
|
-
output.blank();
|
|
588
|
-
|
|
589
|
-
// Open report in browser unless --no-open
|
|
590
|
-
if (options.open !== false) {
|
|
591
|
-
await openBrowser(reportUrl);
|
|
592
|
-
}
|
|
593
|
-
} else {
|
|
594
|
-
output.warn(`Failed to generate static report: ${reportResult.error}`);
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
|
|
598
599
|
// Remove signal handlers before normal cleanup to prevent double cleanup
|
|
599
600
|
process.off('SIGINT', sigintHandler);
|
|
600
601
|
process.off('SIGTERM', sigtermHandler);
|
|
@@ -950,7 +951,7 @@ program.command('whoami').description('Show current authentication status and us
|
|
|
950
951
|
|
|
951
952
|
// Save PATH for the menubar app before commands read or update credentials
|
|
952
953
|
// in the same config file.
|
|
953
|
-
await saveUserPath().catch(() => {});
|
|
954
|
+
if (needsRuntimeBootstrap) await saveUserPath().catch(() => {});
|
|
954
955
|
let commandNames = new Set(program.commands.map(command => command.name()));
|
|
955
956
|
let nestedCommandNames = new Map(program.commands.map(command => [command.name(), new Set(command.registeredArguments.length ? [] : command.commands.map(subcommand => subcommand.name()))]));
|
|
956
957
|
let normalizedArgv = normalizeJsonArgv(process.argv, commandNames);
|
|
@@ -971,7 +972,7 @@ if (requestedCommand && !requestedCommand.known) {
|
|
|
971
972
|
process.exit(1);
|
|
972
973
|
}
|
|
973
974
|
try {
|
|
974
|
-
program.
|
|
975
|
+
await program.parseAsync(normalizedArgv);
|
|
975
976
|
} catch (error) {
|
|
976
977
|
if (error.exitCode == null) {
|
|
977
978
|
output.error('Unexpected CLI error', error);
|