@vizzly-testing/cli 0.38.0 → 0.39.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 (40) hide show
  1. package/README.md +222 -183
  2. package/dist/auth/client.js +4 -1
  3. package/dist/auth/core.js +64 -3
  4. package/dist/auth/operations.js +21 -11
  5. package/dist/cli.js +45 -53
  6. package/dist/commands/init.js +156 -483
  7. package/dist/commands/login.js +71 -137
  8. package/dist/commands/project.js +128 -44
  9. package/dist/commands/run.js +2 -2
  10. package/dist/commands/tdd.js +89 -38
  11. package/dist/commands/upload.js +2 -2
  12. package/dist/init/agents.js +99 -0
  13. package/dist/init/core.js +31 -0
  14. package/dist/init/prompts.js +44 -0
  15. package/dist/reporter/reporter-bundle.css +1 -1
  16. package/dist/reporter/reporter-bundle.iife.js +31 -127
  17. package/dist/server/handlers/tdd-handler.js +33 -2
  18. package/dist/server/routers/auth.js +32 -42
  19. package/dist/services/index.js +8 -2
  20. package/dist/tdd/services/comparison-service.js +4 -1
  21. package/dist/tdd/tdd-service.js +14 -2
  22. package/dist/test-runner/core.js +28 -3
  23. package/dist/test-runner/operations.js +41 -20
  24. package/dist/types/index.d.ts +8 -0
  25. package/dist/uploader/core.js +1 -0
  26. package/dist/utils/browser.js +7 -1
  27. package/dist/utils/config-discovery.js +73 -0
  28. package/dist/utils/config-loader.js +29 -8
  29. package/dist/utils/config-schema.js +5 -0
  30. package/dist/utils/file-changes.js +105 -0
  31. package/dist/utils/login-poll-wait.js +50 -0
  32. package/dist/utils/project-binding.js +96 -0
  33. package/dist/utils/project-link-store.js +13 -7
  34. package/package.json +27 -27
  35. package/skills/vizzly/SKILL.md +4 -3
  36. package/skills/vizzly/references/ci.md +33 -0
  37. package/skills/vizzly/references/cli-context.md +81 -71
  38. package/skills/vizzly/references/dynamic-content.md +16 -17
  39. package/skills/vizzly/references/sdks.md +22 -22
  40. package/skills/vizzly/references/setup-ci.md +0 -44
package/dist/cli.js CHANGED
@@ -28,9 +28,7 @@ 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 { openBrowser } from './utils/browser.js';
34
32
  import { colors } from './utils/colors.js';
35
33
  import { loadConfig } from './utils/config-loader.js';
36
34
  import { getContext } from './utils/context.js';
@@ -430,38 +428,49 @@ output.configure({
430
428
  color: colorOverride,
431
429
  json: jsonArg
432
430
  });
433
- let config = await loadConfig(configPath, {
434
- token: tokenArg
435
- });
436
- let services = createServices(config);
437
- let pluginServices = createPluginServices(services);
438
- let plugins = [];
439
- try {
440
- plugins = await loadPlugins(configPath, config);
441
- for (let plugin of plugins) {
442
- try {
443
- // Add timeout protection for plugin registration (5 seconds)
444
- let registerPromise = plugin.register(program, {
445
- config,
446
- services: pluginServices,
447
- output
448
- });
449
- await withTimeout(registerPromise, 5000, 'Plugin registration timeout (5s)');
450
- output.debug(`Registered plugin: ${plugin.name}`);
451
- } catch (error) {
452
- output.warn(`Failed to register plugin ${plugin.name}: ${error.message}`);
431
+ let setupCommand = findRequestedCommand(process.argv, new Set(['init', 'project']))?.name;
432
+ let needsRuntimeBootstrap = !['init', 'project'].includes(setupCommand);
433
+ if (needsRuntimeBootstrap) {
434
+ let config = await loadConfig(configPath, {
435
+ token: tokenArg
436
+ });
437
+ let services = createServices(config);
438
+ let pluginServices = createPluginServices(services);
439
+ try {
440
+ let plugins = await loadPlugins(configPath, config);
441
+ for (let plugin of plugins) {
442
+ try {
443
+ // Add timeout protection for plugin registration (5 seconds)
444
+ let registerPromise = plugin.register(program, {
445
+ config,
446
+ services: pluginServices,
447
+ output
448
+ });
449
+ await withTimeout(registerPromise, 5000, 'Plugin registration timeout (5s)');
450
+ output.debug(`Registered plugin: ${plugin.name}`);
451
+ } catch (error) {
452
+ output.warn(`Failed to register plugin ${plugin.name}: ${error.message}`);
453
+ }
453
454
  }
455
+ } catch (error) {
456
+ output.debug(`Plugin loading failed: ${error.message}`);
454
457
  }
455
- } catch (error) {
456
- output.debug(`Plugin loading failed: ${error.message}`);
457
458
  }
458
- program.command('init').description('Initialize Vizzly in your project').option('--force', 'Overwrite existing configuration').option('--agent-skill', 'Install or refresh the repo-local Vizzly agent skill').option('--agent-guidance', 'Install or refresh the repo-local Vizzly skill and AGENTS.md guidance').option('--skip-agent-skill', 'Skip the Vizzly agent skill prompt').action(async options => {
459
+ 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 the Vizzly agent skill during setup').option('--agent-guidance', 'Install the Vizzly skill and AGENTS.md guidance during setup').option('--skip-agent-skill', 'Skip the Vizzly agent skill prompt').action(async options => {
459
460
  let globalOptions = getGlobalOptions();
460
- await init({
461
- ...globalOptions,
462
- ...options,
463
- plugins
464
- });
461
+ try {
462
+ let result = await init({
463
+ ...globalOptions,
464
+ ...options
465
+ });
466
+ process.exitCode = result.exitCode || 0;
467
+ } catch (error) {
468
+ output.error(error.message, error, {
469
+ changes: error.completed || []
470
+ });
471
+ if (error.completed?.length) output.list(error.completed.map(change => change.path));
472
+ process.exitCode = error.code === 'INIT_INPUT_REQUIRED' ? 2 : 1;
473
+ }
465
474
  });
466
475
  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
476
  let globalOptions = getGlobalOptions();
@@ -555,7 +564,10 @@ tddCmd.command('run <command>').description('Run tests once in TDD mode with loc
555
564
  const {
556
565
  result,
557
566
  cleanup
558
- } = await tddCommand(command, options, globalOptions);
567
+ } = await tddCommand(command, {
568
+ ...options,
569
+ autoPort: options.port === undefined
570
+ }, globalOptions);
559
571
 
560
572
  // Track cleanup state to prevent double cleanup
561
573
  let isCleaningUp = false;
@@ -575,26 +587,6 @@ tddCmd.command('run <command>').description('Run tests once in TDD mode with loc
575
587
  process.once('SIGINT', sigintHandler);
576
588
  process.once('SIGTERM', sigtermHandler);
577
589
 
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
590
  // Remove signal handlers before normal cleanup to prevent double cleanup
599
591
  process.off('SIGINT', sigintHandler);
600
592
  process.off('SIGTERM', sigtermHandler);
@@ -950,7 +942,7 @@ program.command('whoami').description('Show current authentication status and us
950
942
 
951
943
  // Save PATH for the menubar app before commands read or update credentials
952
944
  // in the same config file.
953
- await saveUserPath().catch(() => {});
945
+ if (needsRuntimeBootstrap) await saveUserPath().catch(() => {});
954
946
  let commandNames = new Set(program.commands.map(command => command.name()));
955
947
  let nestedCommandNames = new Map(program.commands.map(command => [command.name(), new Set(command.registeredArguments.length ? [] : command.commands.map(subcommand => subcommand.name()))]));
956
948
  let normalizedArgv = normalizeJsonArgv(process.argv, commandNames);
@@ -971,7 +963,7 @@ if (requestedCommand && !requestedCommand.known) {
971
963
  process.exit(1);
972
964
  }
973
965
  try {
974
- program.parse(normalizedArgv);
966
+ await program.parseAsync(normalizedArgv);
975
967
  } catch (error) {
976
968
  if (error.exitCode == null) {
977
969
  output.error('Unexpected CLI error', error);