@vizzly-testing/cli 0.27.1 → 0.28.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.
@@ -223,6 +223,7 @@ export async function searchComparisons(client, name, filters = {}) {
223
223
  }
224
224
  let {
225
225
  branch,
226
+ project,
226
227
  limit = 50,
227
228
  offset = 0
228
229
  } = filters;
@@ -232,6 +233,7 @@ export async function searchComparisons(client, name, filters = {}) {
232
233
  offset: String(offset)
233
234
  };
234
235
  if (branch) params.branch = branch;
236
+ if (project) params.project = project;
235
237
  let endpoint = buildEndpointWithParams('/api/sdk/comparisons/search', params);
236
238
  return client.request(endpoint);
237
239
  }
package/dist/cli.js CHANGED
@@ -13,7 +13,6 @@ import { loginCommand, validateLoginOptions } from './commands/login.js';
13
13
  import { logoutCommand, validateLogoutOptions } from './commands/logout.js';
14
14
  import { orgsCommand, validateOrgsOptions } from './commands/orgs.js';
15
15
  import { previewCommand, validatePreviewOptions } from './commands/preview.js';
16
- import { projectListCommand, projectRemoveCommand, projectSelectCommand, projectTokenCommand, validateProjectOptions } from './commands/project.js';
17
16
  import { projectsCommand, validateProjectsOptions } from './commands/projects.js';
18
17
  import { approveCommand, commentCommand, rejectCommand, validateApproveOptions, validateCommentOptions, validateRejectOptions } from './commands/review.js';
19
18
  import { runCommand, validateRunOptions } from './commands/run.js';
@@ -93,11 +92,6 @@ const formatHelp = (cmd, helper) => {
93
92
  icon: '▸',
94
93
  title: 'Account',
95
94
  names: ['login', 'logout', 'whoami', 'orgs', 'projects']
96
- }, {
97
- key: 'project',
98
- icon: '▸',
99
- title: 'Projects',
100
- names: ['project:select', 'project:list', 'project:token', 'project:remove']
101
95
  }];
102
96
  let grouped = {
103
97
  core: [],
@@ -105,7 +99,6 @@ const formatHelp = (cmd, helper) => {
105
99
  setup: [],
106
100
  advanced: [],
107
101
  auth: [],
108
- project: [],
109
102
  other: []
110
103
  };
111
104
  for (let command of commands) {
@@ -439,10 +432,11 @@ program.command('status').description('Check the status of a build').argument('<
439
432
  }
440
433
  await statusCommand(buildId, options, globalOptions);
441
434
  });
442
- program.command('builds').description('List and query builds').option('-b, --build <id>', 'Get a specific build by ID').option('--branch <branch>', 'Filter by branch').option('--status <status>', 'Filter by status (created, pending, processing, completed, failed)').option('--environment <env>', 'Filter by environment').option('--limit <n>', 'Maximum results to return (1-250)', val => parseInt(val, 10), 20).option('--offset <n>', 'Skip first N results', val => parseInt(val, 10), 0).option('--comparisons', 'Include comparisons when fetching a specific build').addHelpText('after', `
435
+ program.command('builds').description('List and query builds').option('-b, --build <id>', 'Get a specific build by ID').option('--branch <branch>', 'Filter by branch').option('--status <status>', 'Filter by status (created, pending, processing, completed, failed)').option('--environment <env>', 'Filter by environment').option('-p, --project <slug>', 'Filter by project slug').option('--limit <n>', 'Maximum results to return (1-250)', val => parseInt(val, 10), 20).option('--offset <n>', 'Skip first N results', val => parseInt(val, 10), 0).option('--comparisons', 'Include comparisons when fetching a specific build').addHelpText('after', `
443
436
  Examples:
444
437
  $ vizzly builds # List recent builds
445
438
  $ vizzly builds --branch main # Filter by branch
439
+ $ vizzly builds --project abc123 # Filter by project
446
440
  $ vizzly builds --status completed # Filter by status
447
441
  $ vizzly builds -b abc123-def456 # Get specific build by ID
448
442
  $ vizzly builds -b abc123 --comparisons # Include comparisons
@@ -461,7 +455,7 @@ Examples:
461
455
  }
462
456
  await buildsCommand(options, globalOptions);
463
457
  });
464
- program.command('comparisons').description('Query and search comparisons').option('-b, --build <id>', 'Get comparisons for a specific build').option('--id <id>', 'Get a specific comparison by ID').option('--name <pattern>', 'Search comparisons by name (supports wildcards)').option('--status <status>', 'Filter by status (identical, new, changed)').option('--branch <branch>', 'Filter by branch (for name search)').option('--limit <n>', 'Maximum results to return (1-250)', val => parseInt(val, 10), 50).option('--offset <n>', 'Skip first N results', val => parseInt(val, 10), 0).addHelpText('after', `
458
+ program.command('comparisons').description('Query and search comparisons').option('-b, --build <id>', 'Get comparisons for a specific build').option('--id <id>', 'Get a specific comparison by ID').option('--name <pattern>', 'Search comparisons by name (supports wildcards)').option('--status <status>', 'Filter by status (identical, new, changed)').option('--branch <branch>', 'Filter by branch (for name search)').option('--limit <n>', 'Maximum results to return (1-250)', val => parseInt(val, 10), 50).option('--offset <n>', 'Skip first N results', val => parseInt(val, 10), 0).option('-p, --project <slug>', 'Filter by project slug').addHelpText('after', `
465
459
  Examples:
466
460
  $ vizzly comparisons -b abc123 # List comparisons for a build
467
461
  $ vizzly comparisons --id def456 # Get specific comparison by ID
@@ -747,32 +741,6 @@ program.command('whoami').description('Show current authentication status and us
747
741
  }
748
742
  await whoamiCommand(options, globalOptions);
749
743
  });
750
- program.command('project:select').description('Configure project for current directory').option('--api-url <url>', 'API URL override').action(async options => {
751
- const globalOptions = program.opts();
752
-
753
- // Validate options
754
- const validationErrors = validateProjectOptions(options);
755
- if (validationErrors.length > 0) {
756
- output.error('Validation errors:');
757
- for (let error of validationErrors) {
758
- output.printErr(` - ${error}`);
759
- }
760
- process.exit(1);
761
- }
762
- await projectSelectCommand(options, globalOptions);
763
- });
764
- program.command('project:list').description('Show all configured projects').action(async options => {
765
- const globalOptions = program.opts();
766
- await projectListCommand(options, globalOptions);
767
- });
768
- program.command('project:token').description('Show project token for current directory').action(async options => {
769
- const globalOptions = program.opts();
770
- await projectTokenCommand(options, globalOptions);
771
- });
772
- program.command('project:remove').description('Remove project configuration for current directory').action(async options => {
773
- const globalOptions = program.opts();
774
- await projectRemoveCommand(options, globalOptions);
775
- });
776
744
 
777
745
  // Save user's PATH for menubar app (non-blocking, runs in background)
778
746
  // This auto-configures the menubar app so it can find npx/node
@@ -74,6 +74,7 @@ export async function buildsCommand(options = {}, globalOptions = {}, deps = {})
74
74
  if (options.branch) filters.branch = options.branch;
75
75
  if (options.status) filters.status = options.status;
76
76
  if (options.environment) filters.environment = options.environment;
77
+ if (options.project) filters.project = options.project;
77
78
  let response = await getBuilds(client, filters);
78
79
  output.stopSpinner();
79
80
  let builds = response.builds || [];
@@ -108,6 +108,7 @@ export async function comparisonsCommand(options = {}, globalOptions = {}, deps
108
108
  output.startSpinner('Searching comparisons...');
109
109
  let filters = {
110
110
  branch: options.branch,
111
+ project: options.project,
111
112
  limit: options.limit || 50,
112
113
  offset: options.offset || 0
113
114
  };
@@ -2,9 +2,7 @@
2
2
  * Config command - query and display configuration
3
3
  */
4
4
 
5
- import { resolve } from 'node:path';
6
5
  import { loadConfig as defaultLoadConfig } from '../utils/config-loader.js';
7
- import { getProjectMapping as defaultGetProjectMapping } from '../utils/global-config.js';
8
6
  import * as defaultOutput from '../utils/output.js';
9
7
 
10
8
  /**
@@ -17,7 +15,6 @@ import * as defaultOutput from '../utils/output.js';
17
15
  export async function configCommand(key = null, options = {}, globalOptions = {}, deps = {}) {
18
16
  let {
19
17
  loadConfig = defaultLoadConfig,
20
- getProjectMapping = defaultGetProjectMapping,
21
18
  output = defaultOutput,
22
19
  exit = code => process.exit(code)
23
20
  } = deps;
@@ -34,10 +31,6 @@ export async function configCommand(key = null, options = {}, globalOptions = {}
34
31
  });
35
32
  let configFile = config._configPath || null;
36
33
 
37
- // Get project mapping if available
38
- let currentDir = resolve(process.cwd());
39
- let projectMapping = await getProjectMapping(currentDir);
40
-
41
34
  // Build the config object to display
42
35
  let displayConfig = {
43
36
  server: config.server || {
@@ -90,12 +83,7 @@ export async function configCommand(key = null, options = {}, globalOptions = {}
90
83
  if (globalOptions.json) {
91
84
  output.data({
92
85
  configFile,
93
- config: displayConfig,
94
- project: projectMapping ? {
95
- name: projectMapping.projectName,
96
- slug: projectMapping.projectSlug,
97
- organization: projectMapping.organizationSlug
98
- } : null
86
+ config: displayConfig
99
87
  });
100
88
  output.cleanup();
101
89
  return;
@@ -112,13 +100,6 @@ export async function configCommand(key = null, options = {}, globalOptions = {}
112
100
  }
113
101
  output.blank();
114
102
 
115
- // Project context if available
116
- if (projectMapping) {
117
- output.labelValue('Project', `${projectMapping.projectName} (${projectMapping.projectSlug})`);
118
- output.labelValue('Organization', projectMapping.organizationSlug);
119
- output.blank();
120
- }
121
-
122
103
  // Display configuration sections
123
104
  displaySection(output, 'Server', displayConfig.server);
124
105
  displaySection(output, 'Comparison', displayConfig.comparison);
@@ -6,90 +6,6 @@
6
6
 
7
7
  import { VizzlyError } from '../errors/vizzly-error.js';
8
8
 
9
- // ============================================================================
10
- // Validation
11
- // ============================================================================
12
-
13
- /**
14
- * Validate that a directory path is provided
15
- * @param {string} directory - Directory path to validate
16
- * @returns {{ valid: boolean, error: Error|null }}
17
- */
18
- export function validateDirectory(directory) {
19
- if (!directory) {
20
- return {
21
- valid: false,
22
- error: new VizzlyError('Directory path is required', 'INVALID_DIRECTORY')
23
- };
24
- }
25
- return {
26
- valid: true,
27
- error: null
28
- };
29
- }
30
-
31
- /**
32
- * Validate project data for mapping creation
33
- * @param {Object} projectData - Project data to validate
34
- * @param {string} [projectData.projectSlug] - Project slug
35
- * @param {string} [projectData.organizationSlug] - Organization slug
36
- * @param {string} [projectData.token] - Project token
37
- * @returns {{ valid: boolean, error: Error|null }}
38
- */
39
- export function validateProjectData(projectData) {
40
- if (!projectData.projectSlug) {
41
- return {
42
- valid: false,
43
- error: new VizzlyError('Project slug is required', 'INVALID_PROJECT_DATA')
44
- };
45
- }
46
- if (!projectData.organizationSlug) {
47
- return {
48
- valid: false,
49
- error: new VizzlyError('Organization slug is required', 'INVALID_PROJECT_DATA')
50
- };
51
- }
52
- if (!projectData.token) {
53
- return {
54
- valid: false,
55
- error: new VizzlyError('Project token is required', 'INVALID_PROJECT_DATA')
56
- };
57
- }
58
- return {
59
- valid: true,
60
- error: null
61
- };
62
- }
63
-
64
- // ============================================================================
65
- // Mapping Transformations
66
- // ============================================================================
67
-
68
- /**
69
- * Convert mappings object to array with directory paths included
70
- * @param {Object} mappings - Object with directory paths as keys
71
- * @returns {Array<Object>} Array of mappings with directory property
72
- */
73
- export function mappingsToArray(mappings) {
74
- return Object.entries(mappings).map(([directory, data]) => ({
75
- directory,
76
- ...data
77
- }));
78
- }
79
-
80
- /**
81
- * Build a mapping result object
82
- * @param {string} directory - Directory path
83
- * @param {Object} projectData - Project data
84
- * @returns {Object} Mapping result with directory included
85
- */
86
- export function buildMappingResult(directory, projectData) {
87
- return {
88
- directory,
89
- ...projectData
90
- };
91
- }
92
-
93
9
  // ============================================================================
94
10
  // API Request Helpers
95
11
  // ============================================================================
@@ -2,88 +2,12 @@
2
2
  * Project Operations - Project operations with dependency injection
3
3
  *
4
4
  * Each operation takes its dependencies as parameters:
5
- * - mappingStore: for reading/writing project mappings
6
5
  * - httpClient: for making API requests (OAuth or API token based)
7
6
  *
8
7
  * This makes them trivially testable without mocking modules.
9
8
  */
10
9
 
11
- import { buildBuildsUrl, buildMappingResult, buildNoApiServiceError, buildNoAuthError, buildOrgHeader, buildProjectFetchError, buildProjectUrl, buildTokenCreateError, buildTokenRevokeError, buildTokensFetchError, buildTokensUrl, enrichProjectsWithOrg, extractBuilds, extractOrganizations, extractProject, extractProjects, extractToken, extractTokens, mappingsToArray, validateDirectory, validateProjectData } from './core.js';
12
-
13
- // ============================================================================
14
- // Mapping Operations
15
- // ============================================================================
16
-
17
- /**
18
- * List all project mappings
19
- * @param {Object} mappingStore - Store with getMappings method
20
- * @returns {Promise<Array>} Array of project mappings with directory included
21
- */
22
- export async function listMappings(mappingStore) {
23
- let mappings = await mappingStore.getMappings();
24
- return mappingsToArray(mappings);
25
- }
26
-
27
- /**
28
- * Get project mapping for a specific directory
29
- * @param {Object} mappingStore - Store with getMapping method
30
- * @param {string} directory - Directory path
31
- * @returns {Promise<Object|null>} Project mapping or null
32
- */
33
- export async function getMapping(mappingStore, directory) {
34
- return mappingStore.getMapping(directory);
35
- }
36
-
37
- /**
38
- * Create or update project mapping
39
- * @param {Object} mappingStore - Store with saveMapping method
40
- * @param {string} directory - Directory path
41
- * @param {Object} projectData - Project data
42
- * @returns {Promise<Object>} Created mapping with directory included
43
- */
44
- export async function createMapping(mappingStore, directory, projectData) {
45
- let dirValidation = validateDirectory(directory);
46
- if (!dirValidation.valid) {
47
- throw dirValidation.error;
48
- }
49
- let dataValidation = validateProjectData(projectData);
50
- if (!dataValidation.valid) {
51
- throw dataValidation.error;
52
- }
53
- await mappingStore.saveMapping(directory, projectData);
54
- return buildMappingResult(directory, projectData);
55
- }
56
-
57
- /**
58
- * Remove project mapping
59
- * @param {Object} mappingStore - Store with deleteMapping method
60
- * @param {string} directory - Directory path
61
- * @returns {Promise<void>}
62
- */
63
- export async function removeMapping(mappingStore, directory) {
64
- let validation = validateDirectory(directory);
65
- if (!validation.valid) {
66
- throw validation.error;
67
- }
68
- await mappingStore.deleteMapping(directory);
69
- }
70
-
71
- /**
72
- * Switch project for a directory (convenience wrapper for createMapping)
73
- * @param {Object} mappingStore - Store with saveMapping method
74
- * @param {string} directory - Directory path
75
- * @param {string} projectSlug - Project slug
76
- * @param {string} organizationSlug - Organization slug
77
- * @param {string} token - Project token
78
- * @returns {Promise<Object>} Updated mapping
79
- */
80
- export async function switchProject(mappingStore, directory, projectSlug, organizationSlug, token) {
81
- return createMapping(mappingStore, directory, {
82
- projectSlug,
83
- organizationSlug,
84
- token
85
- });
86
- }
10
+ import { buildBuildsUrl, buildNoApiServiceError, buildNoAuthError, buildOrgHeader, buildProjectFetchError, buildProjectUrl, buildTokenCreateError, buildTokenRevokeError, buildTokensFetchError, buildTokensUrl, enrichProjectsWithOrg, extractBuilds, extractOrganizations, extractProject, extractProjects, extractToken, extractTokens } from './core.js';
87
11
 
88
12
  // ============================================================================
89
13
  // API Operations - List Projects