@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.
@@ -1,10 +1,9 @@
1
1
  /**
2
2
  * Projects Router
3
- * Handles project management and builds endpoints
3
+ * Handles project listing and builds endpoints
4
4
  */
5
5
 
6
6
  import * as output from '../../utils/output.js';
7
- import { parseJsonBody } from '../middleware/json-parser.js';
8
7
  import { sendError, sendServiceUnavailable, sendSuccess } from '../middleware/response.js';
9
8
 
10
9
  /**
@@ -40,105 +39,6 @@ export function createProjectsRouter({
40
39
  }
41
40
  }
42
41
 
43
- // List project directory mappings
44
- if (req.method === 'GET' && pathname === '/api/projects/mappings') {
45
- try {
46
- const mappings = await projectService.listMappings();
47
- sendSuccess(res, {
48
- mappings
49
- });
50
- return true;
51
- } catch (error) {
52
- output.debug('Error listing project mappings:', {
53
- error: error.message
54
- });
55
- sendError(res, 500, error.message);
56
- return true;
57
- }
58
- }
59
-
60
- // Create or update project mapping
61
- if (req.method === 'POST' && pathname === '/api/projects/mappings') {
62
- try {
63
- const body = await parseJsonBody(req);
64
- const {
65
- directory,
66
- projectSlug,
67
- organizationSlug,
68
- token,
69
- projectName
70
- } = body;
71
- const mapping = await projectService.createMapping(directory, {
72
- projectSlug,
73
- organizationSlug,
74
- token,
75
- projectName
76
- });
77
- sendSuccess(res, {
78
- success: true,
79
- mapping
80
- });
81
- return true;
82
- } catch (error) {
83
- output.debug('Error creating project mapping:', {
84
- error: error.message
85
- });
86
- sendError(res, 500, error.message);
87
- return true;
88
- }
89
- }
90
-
91
- // Delete project mapping
92
- if (req.method === 'DELETE' && pathname.startsWith('/api/projects/mappings/')) {
93
- try {
94
- const directory = decodeURIComponent(pathname.replace('/api/projects/mappings/', ''));
95
- await projectService.removeMapping(directory);
96
- sendSuccess(res, {
97
- success: true,
98
- message: 'Mapping deleted'
99
- });
100
- return true;
101
- } catch (error) {
102
- output.debug('Error deleting project mapping:', {
103
- error: error.message
104
- });
105
- sendError(res, 500, error.message);
106
- return true;
107
- }
108
- }
109
-
110
- // Get recent builds for current project
111
- if (req.method === 'GET' && pathname === '/api/builds/recent') {
112
- if (!projectService) {
113
- sendServiceUnavailable(res, 'Project service');
114
- return true;
115
- }
116
- try {
117
- const currentDir = process.cwd();
118
- const mapping = await projectService.getMapping(currentDir);
119
- if (!mapping || !mapping.projectSlug || !mapping.organizationSlug) {
120
- sendError(res, 400, 'No project configured for this directory');
121
- return true;
122
- }
123
- const limit = parseInt(parsedUrl.searchParams.get('limit') || '10', 10);
124
- const branch = parsedUrl.searchParams.get('branch') || undefined;
125
- const builds = await projectService.getRecentBuilds(mapping.projectSlug, mapping.organizationSlug, {
126
- limit,
127
- branch
128
- });
129
- sendSuccess(res, {
130
- builds
131
- });
132
- return true;
133
- } catch (error) {
134
- output.debug('Error fetching recent builds:', {
135
- error: error.message
136
- });
137
- sendError(res, 500, error.message);
138
- return true;
139
- }
140
- }
141
-
142
42
  // Get builds for a specific project (used by /builds page)
143
43
  const projectBuildsMatch = pathname.match(/^\/api\/projects\/([^/]+)\/([^/]+)\/builds$/);
144
44
  if (req.method === 'GET' && projectBuildsMatch) {
@@ -4,29 +4,22 @@
4
4
  *
5
5
  * Provides the interface expected by src/server/routers/projects.js:
6
6
  * - listProjects() - Returns [] if not authenticated
7
- * - listMappings() - Returns [] if no mappings
8
- * - getMapping(directory) - Returns null if not found
9
- * - createMapping(directory, projectData) - Throws on invalid input
10
- * - removeMapping(directory) - Throws on invalid directory
11
7
  * - getRecentBuilds(projectSlug, organizationSlug, options) - Returns [] if not authenticated
12
8
  *
13
9
  * Error handling:
14
10
  * - API methods (listProjects, getRecentBuilds) return empty arrays when not authenticated
15
- * - Local methods (listMappings, getMapping) never require authentication
16
- * - Validation errors (createMapping, removeMapping) throw with descriptive messages
17
11
  */
18
12
 
19
13
  import { createAuthClient } from '../auth/client.js';
20
14
  import * as projectOps from '../project/operations.js';
21
15
  import { getApiUrl } from '../utils/environment-config.js';
22
- import { deleteProjectMapping, getAuthTokens, getProjectMapping, getProjectMappings, saveProjectMapping } from '../utils/global-config.js';
16
+ import { getAuthTokens } from '../utils/global-config.js';
23
17
 
24
18
  /**
25
19
  * Create a project service instance
26
20
  * @param {Object} [options]
27
21
  * @param {string} [options.apiUrl] - API base URL (defaults to VIZZLY_API_URL or https://app.vizzly.dev)
28
22
  * @param {Object} [options.httpClient] - Injectable HTTP client (for testing)
29
- * @param {Object} [options.mappingStore] - Injectable mapping store (for testing)
30
23
  * @param {Function} [options.getAuthTokens] - Injectable token getter (for testing)
31
24
  * @returns {Object} Project service
32
25
  */
@@ -39,15 +32,6 @@ export function createProjectService(options = {}) {
39
32
  baseUrl: apiUrl
40
33
  });
41
34
 
42
- // Create mapping store adapter for global config
43
- // Allow injection for testing
44
- let mappingStore = options.mappingStore || {
45
- getMappings: getProjectMappings,
46
- getMapping: getProjectMapping,
47
- saveMapping: saveProjectMapping,
48
- deleteMapping: deleteProjectMapping
49
- };
50
-
51
35
  // Allow injection of getAuthTokens for testing
52
36
  let tokenGetter = options.getAuthTokens || getAuthTokens;
53
37
 
@@ -80,38 +64,6 @@ export function createProjectService(options = {}) {
80
64
  apiClient: null
81
65
  });
82
66
  },
83
- /**
84
- * List all project mappings
85
- * @returns {Promise<Array>} Array of project mappings
86
- */
87
- async listMappings() {
88
- return projectOps.listMappings(mappingStore);
89
- },
90
- /**
91
- * Get project mapping for a specific directory
92
- * @param {string} directory - Directory path
93
- * @returns {Promise<Object|null>} Project mapping or null
94
- */
95
- async getMapping(directory) {
96
- return projectOps.getMapping(mappingStore, directory);
97
- },
98
- /**
99
- * Create or update project mapping
100
- * @param {string} directory - Directory path
101
- * @param {Object} projectData - Project data
102
- * @returns {Promise<Object>} Created mapping
103
- */
104
- async createMapping(directory, projectData) {
105
- return projectOps.createMapping(mappingStore, directory, projectData);
106
- },
107
- /**
108
- * Remove project mapping
109
- * @param {string} directory - Directory path
110
- * @returns {Promise<void>}
111
- */
112
- async removeMapping(directory) {
113
- return projectOps.removeMapping(mappingStore, directory);
114
- },
115
67
  /**
116
68
  * Get recent builds for a project
117
69
  * Returns empty array if not authenticated (projectOps handles null oauthClient)
@@ -2,7 +2,7 @@ import { resolve } from 'node:path';
2
2
  import { cosmiconfigSync } from 'cosmiconfig';
3
3
  import { validateVizzlyConfigWithDefaults } from './config-schema.js';
4
4
  import { getApiToken, getApiUrl, getBuildName, getParallelId } from './environment-config.js';
5
- import { getAccessToken, getProjectMapping } from './global-config.js';
5
+ import { getAccessToken } from './global-config.js';
6
6
  import * as output from './output.js';
7
7
  const DEFAULT_CONFIG = {
8
8
  // API Configuration
@@ -73,29 +73,7 @@ export async function loadConfig(configPath = null, cliOverrides = {}) {
73
73
  // Merge validated file config
74
74
  mergeConfig(config, validatedFileConfig);
75
75
 
76
- // 3. Check project mapping for current directory (if no CLI flag)
77
- if (!cliOverrides.token) {
78
- const currentDir = process.cwd();
79
- const projectMapping = await getProjectMapping(currentDir);
80
- if (projectMapping?.token) {
81
- // Handle both string tokens and token objects (backward compatibility)
82
- let token;
83
- if (typeof projectMapping.token === 'string') {
84
- token = projectMapping.token;
85
- } else if (typeof projectMapping.token === 'object' && projectMapping.token.token) {
86
- // Handle nested token object from old API responses
87
- token = projectMapping.token.token;
88
- } else {
89
- token = String(projectMapping.token);
90
- }
91
- config.apiKey = token;
92
- config.projectSlug = projectMapping.projectSlug;
93
- config.organizationSlug = projectMapping.organizationSlug;
94
- output.debug('config', `linked to ${projectMapping.projectSlug} (${projectMapping.organizationSlug})`);
95
- }
96
- }
97
-
98
- // 4. Override with environment variables (higher priority than fallbacks)
76
+ // 3. Override with environment variables (higher priority than fallbacks)
99
77
  const envApiKey = getApiToken();
100
78
  const envApiUrl = getApiUrl();
101
79
  const envBuildName = getBuildName();
@@ -111,20 +89,19 @@ export async function loadConfig(configPath = null, cliOverrides = {}) {
111
89
  }
112
90
  if (envParallelId) config.parallelId = envParallelId;
113
91
 
114
- // 5. Apply CLI overrides (highest priority)
92
+ // 4. Apply CLI overrides (highest priority)
115
93
  if (cliOverrides.token) {
116
94
  output.debug('config', 'using token from --token flag');
117
95
  }
118
96
  applyCLIOverrides(config, cliOverrides);
119
97
 
120
- // 6. Fall back to user auth token if no other token found
98
+ // 5. Fall back to user auth token if no other token found
121
99
  // This enables interactive commands (builds, comparisons, approve, etc.)
122
100
  // to work without a project token when the user is logged in
123
101
  if (!config.apiKey) {
124
102
  let userToken = await getAccessToken();
125
103
  if (userToken) {
126
104
  config.apiKey = userToken;
127
- config.isUserAuth = true; // Flag to indicate this is user auth, not project token
128
105
  output.debug('config', 'using token from user login');
129
106
  }
130
107
  }
@@ -10,7 +10,7 @@
10
10
 
11
11
  import { existsSync, readFileSync } from 'node:fs';
12
12
  import { homedir } from 'node:os';
13
- import { dirname, join } from 'node:path';
13
+ import { join } from 'node:path';
14
14
 
15
15
  /**
16
16
  * Get dynamic context about the current Vizzly state
@@ -63,20 +63,6 @@ export function getContext() {
63
63
  // Ignore
64
64
  }
65
65
 
66
- // Check for project mapping (from vizzly project:select)
67
- // Traverse up to find project config, with bounds check for Windows compatibility
68
- let projectMapping = null;
69
- let checkPath = cwd;
70
- let prevPath = null;
71
- while (checkPath && checkPath !== prevPath) {
72
- if (globalConfig.projects?.[checkPath]) {
73
- projectMapping = globalConfig.projects[checkPath];
74
- break;
75
- }
76
- prevPath = checkPath;
77
- checkPath = dirname(checkPath);
78
- }
79
-
80
66
  // Check for OAuth login (from vizzly login)
81
67
  let isLoggedIn = !!globalConfig.auth?.accessToken;
82
68
  let userName = globalConfig.auth?.user?.name || globalConfig.auth?.user?.email;
@@ -92,13 +78,7 @@ export function getContext() {
92
78
  value: `running on :${serverPort}`
93
79
  });
94
80
  }
95
- if (projectMapping) {
96
- items.push({
97
- type: 'success',
98
- label: 'Project',
99
- value: `${projectMapping.projectName} (${projectMapping.organizationSlug})`
100
- });
101
- } else if (isLoggedIn && userName) {
81
+ if (isLoggedIn && userName) {
102
82
  items.push({
103
83
  type: 'success',
104
84
  label: 'Logged in',
@@ -114,7 +94,7 @@ export function getContext() {
114
94
  items.push({
115
95
  type: 'info',
116
96
  label: 'Not connected',
117
- value: 'run vizzly login or project:select'
97
+ value: 'run vizzly login'
118
98
  });
119
99
  }
120
100
  if (baselineCount > 0) {
@@ -153,8 +133,7 @@ export function getDetailedContext() {
153
133
  port: null
154
134
  },
155
135
  project: {
156
- hasConfig: false,
157
- mapping: null
136
+ hasConfig: false
158
137
  },
159
138
  auth: {
160
139
  loggedIn: false,
@@ -204,19 +183,6 @@ export function getDetailedContext() {
204
183
  // Ignore
205
184
  }
206
185
 
207
- // Check for project mapping
208
- // Traverse up to find project config, with bounds check for Windows compatibility
209
- let checkPath = cwd;
210
- let prevPath = null;
211
- while (checkPath && checkPath !== prevPath) {
212
- if (globalConfig.projects?.[checkPath]) {
213
- context.project.mapping = globalConfig.projects[checkPath];
214
- break;
215
- }
216
- prevPath = checkPath;
217
- checkPath = dirname(checkPath);
218
- }
219
-
220
186
  // Check auth status
221
187
  context.auth.loggedIn = !!globalConfig.auth?.accessToken;
222
188
  context.auth.userName = globalConfig.auth?.user?.name || globalConfig.auth?.user?.email;
@@ -6,7 +6,7 @@
6
6
  import { existsSync } from 'node:fs';
7
7
  import { chmod, mkdir, readFile, writeFile } from 'node:fs/promises';
8
8
  import { homedir } from 'node:os';
9
- import { dirname, join, parse } from 'node:path';
9
+ import { join } from 'node:path';
10
10
  import * as output from './output.js';
11
11
 
12
12
  /**
@@ -197,79 +197,4 @@ export async function getAccessToken() {
197
197
  if (!valid) return null;
198
198
  let auth = await getAuthTokens();
199
199
  return auth?.accessToken || null;
200
- }
201
-
202
- /**
203
- * Get project mapping for a directory
204
- * Walks up the directory tree to find the closest mapping
205
- * @param {string} directoryPath - Absolute path to project directory
206
- * @returns {Promise<Object|null>} Project data or null
207
- */
208
- export async function getProjectMapping(directoryPath) {
209
- const config = await loadGlobalConfig();
210
- if (!config.projects) {
211
- return null;
212
- }
213
-
214
- // Walk up the directory tree looking for a mapping
215
- let currentPath = directoryPath;
216
- const {
217
- root
218
- } = parse(currentPath);
219
- while (currentPath !== root) {
220
- if (config.projects[currentPath]) {
221
- return config.projects[currentPath];
222
- }
223
-
224
- // Move to parent directory
225
- const parentPath = dirname(currentPath);
226
- if (parentPath === currentPath) {
227
- // We've reached the root
228
- break;
229
- }
230
- currentPath = parentPath;
231
- }
232
- return null;
233
- }
234
-
235
- /**
236
- * Save project mapping for a directory
237
- * @param {string} directoryPath - Absolute path to project directory
238
- * @param {Object} projectData - Project configuration
239
- * @param {string} projectData.token - Project API token (vzt_...)
240
- * @param {string} projectData.projectSlug - Project slug
241
- * @param {string} projectData.organizationSlug - Organization slug
242
- * @param {string} projectData.projectName - Project name
243
- */
244
- export async function saveProjectMapping(directoryPath, projectData) {
245
- const config = await loadGlobalConfig();
246
- if (!config.projects) {
247
- config.projects = {};
248
- }
249
- config.projects[directoryPath] = {
250
- ...projectData,
251
- createdAt: new Date().toISOString()
252
- };
253
- await saveGlobalConfig(config);
254
- }
255
-
256
- /**
257
- * Get all project mappings
258
- * @returns {Promise<Object>} Map of directory paths to project data
259
- */
260
- export async function getProjectMappings() {
261
- const config = await loadGlobalConfig();
262
- return config.projects || {};
263
- }
264
-
265
- /**
266
- * Delete project mapping for a directory
267
- * @param {string} directoryPath - Absolute path to project directory
268
- */
269
- export async function deleteProjectMapping(directoryPath) {
270
- const config = await loadGlobalConfig();
271
- if (config.projects?.[directoryPath]) {
272
- delete config.projects[directoryPath];
273
- await saveGlobalConfig(config);
274
- }
275
200
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vizzly-testing/cli",
3
- "version": "0.27.1",
3
+ "version": "0.28.0",
4
4
  "description": "Visual review platform for UI developers and designers",
5
5
  "keywords": [
6
6
  "visual-testing",