@vizzly-testing/cli 0.20.0 → 0.20.1-beta.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.
Files changed (84) hide show
  1. package/dist/api/client.js +134 -0
  2. package/dist/api/core.js +341 -0
  3. package/dist/api/endpoints.js +314 -0
  4. package/dist/api/index.js +19 -0
  5. package/dist/auth/client.js +91 -0
  6. package/dist/auth/core.js +176 -0
  7. package/dist/auth/index.js +30 -0
  8. package/dist/auth/operations.js +148 -0
  9. package/dist/cli.js +178 -3
  10. package/dist/client/index.js +144 -77
  11. package/dist/commands/doctor.js +121 -36
  12. package/dist/commands/finalize.js +49 -18
  13. package/dist/commands/init.js +13 -18
  14. package/dist/commands/login.js +49 -55
  15. package/dist/commands/logout.js +17 -9
  16. package/dist/commands/project.js +100 -71
  17. package/dist/commands/run.js +189 -95
  18. package/dist/commands/status.js +101 -66
  19. package/dist/commands/tdd-daemon.js +61 -32
  20. package/dist/commands/tdd.js +104 -98
  21. package/dist/commands/upload.js +78 -34
  22. package/dist/commands/whoami.js +44 -42
  23. package/dist/config/core.js +438 -0
  24. package/dist/config/index.js +13 -0
  25. package/dist/config/operations.js +327 -0
  26. package/dist/index.js +1 -1
  27. package/dist/project/core.js +295 -0
  28. package/dist/project/index.js +13 -0
  29. package/dist/project/operations.js +393 -0
  30. package/dist/reporter/reporter-bundle.css +1 -1
  31. package/dist/reporter/reporter-bundle.iife.js +16 -16
  32. package/dist/screenshot-server/core.js +157 -0
  33. package/dist/screenshot-server/index.js +11 -0
  34. package/dist/screenshot-server/operations.js +183 -0
  35. package/dist/sdk/index.js +3 -2
  36. package/dist/server/handlers/api-handler.js +14 -5
  37. package/dist/server/handlers/tdd-handler.js +191 -53
  38. package/dist/server/http-server.js +9 -3
  39. package/dist/server/routers/baseline.js +58 -0
  40. package/dist/server/routers/dashboard.js +10 -6
  41. package/dist/server/routers/screenshot.js +32 -0
  42. package/dist/server-manager/core.js +186 -0
  43. package/dist/server-manager/index.js +81 -0
  44. package/dist/server-manager/operations.js +209 -0
  45. package/dist/services/build-manager.js +2 -69
  46. package/dist/services/index.js +21 -48
  47. package/dist/services/screenshot-server.js +40 -74
  48. package/dist/services/server-manager.js +45 -80
  49. package/dist/services/test-runner.js +90 -250
  50. package/dist/services/uploader.js +56 -358
  51. package/dist/tdd/core/hotspot-coverage.js +112 -0
  52. package/dist/tdd/core/signature.js +101 -0
  53. package/dist/tdd/index.js +19 -0
  54. package/dist/tdd/metadata/baseline-metadata.js +103 -0
  55. package/dist/tdd/metadata/hotspot-metadata.js +93 -0
  56. package/dist/tdd/services/baseline-downloader.js +151 -0
  57. package/dist/tdd/services/baseline-manager.js +166 -0
  58. package/dist/tdd/services/comparison-service.js +230 -0
  59. package/dist/tdd/services/hotspot-service.js +71 -0
  60. package/dist/tdd/services/result-service.js +123 -0
  61. package/dist/tdd/tdd-service.js +1145 -0
  62. package/dist/test-runner/core.js +255 -0
  63. package/dist/test-runner/index.js +13 -0
  64. package/dist/test-runner/operations.js +483 -0
  65. package/dist/types/client.d.ts +25 -2
  66. package/dist/uploader/core.js +396 -0
  67. package/dist/uploader/index.js +11 -0
  68. package/dist/uploader/operations.js +412 -0
  69. package/dist/utils/colors.js +187 -39
  70. package/dist/utils/config-loader.js +3 -6
  71. package/dist/utils/context.js +228 -0
  72. package/dist/utils/output.js +449 -14
  73. package/docs/api-reference.md +173 -8
  74. package/docs/tui-elements.md +560 -0
  75. package/package.json +13 -13
  76. package/dist/services/api-service.js +0 -412
  77. package/dist/services/auth-service.js +0 -226
  78. package/dist/services/config-service.js +0 -369
  79. package/dist/services/html-report-generator.js +0 -455
  80. package/dist/services/project-service.js +0 -326
  81. package/dist/services/report-generator/report.css +0 -411
  82. package/dist/services/report-generator/viewer.js +0 -102
  83. package/dist/services/static-report-generator.js +0 -207
  84. package/dist/services/tdd-service.js +0 -1437
@@ -5,9 +5,9 @@
5
5
 
6
6
  import { resolve } from 'node:path';
7
7
  import readline from 'node:readline';
8
- import { AuthService } from '../services/auth-service.js';
8
+ import { createAuthClient, createTokenStore, getAuthTokens, whoami } from '../auth/index.js';
9
9
  import { getApiUrl } from '../utils/environment-config.js';
10
- import { deleteProjectMapping, getAuthTokens, getProjectMapping, getProjectMappings, saveProjectMapping } from '../utils/global-config.js';
10
+ import { deleteProjectMapping, getProjectMapping, getProjectMappings, saveProjectMapping } from '../utils/global-config.js';
11
11
  import * as output from '../utils/output.js';
12
12
 
13
13
  /**
@@ -22,43 +22,42 @@ export async function projectSelectCommand(options = {}, globalOptions = {}) {
22
22
  color: !globalOptions.noColor
23
23
  });
24
24
  try {
25
+ output.header('project:select');
26
+
25
27
  // Check authentication
26
- const auth = await getAuthTokens();
28
+ let auth = await getAuthTokens();
27
29
  if (!auth || !auth.accessToken) {
28
30
  output.error('Not authenticated');
29
- output.blank();
30
- output.info('Run "vizzly login" to authenticate first');
31
+ output.hint('Run "vizzly login" to authenticate first');
31
32
  process.exit(1);
32
33
  }
33
- const authService = new AuthService({
34
+ let client = createAuthClient({
34
35
  baseUrl: options.apiUrl || getApiUrl()
35
36
  });
37
+ let tokenStore = createTokenStore();
36
38
 
37
39
  // Get user info to show organizations
38
40
  output.startSpinner('Fetching organizations...');
39
- const userInfo = await authService.whoami();
41
+ let userInfo = await whoami(client, tokenStore);
40
42
  output.stopSpinner();
41
43
  if (!userInfo.organizations || userInfo.organizations.length === 0) {
42
44
  output.error('No organizations found');
43
- output.blank();
44
- output.info('Create an organization at https://vizzly.dev');
45
+ output.hint('Create an organization at https://vizzly.dev');
45
46
  process.exit(1);
46
47
  }
47
48
 
48
49
  // Select organization
49
- output.blank();
50
- output.info('Select an organization:');
51
- output.blank();
50
+ output.labelValue('Organizations', '');
52
51
  userInfo.organizations.forEach((org, index) => {
53
52
  output.print(` ${index + 1}. ${org.name} (@${org.slug})`);
54
53
  });
55
54
  output.blank();
56
- const orgChoice = await promptNumber('Enter number', 1, userInfo.organizations.length);
57
- const selectedOrg = userInfo.organizations[orgChoice - 1];
55
+ let orgChoice = await promptNumber('Enter number', 1, userInfo.organizations.length);
56
+ let selectedOrg = userInfo.organizations[orgChoice - 1];
58
57
 
59
58
  // List projects for organization
60
59
  output.startSpinner(`Fetching projects for ${selectedOrg.name}...`);
61
- const response = await makeAuthenticatedRequest(`${options.apiUrl || getApiUrl()}/api/project`, {
60
+ let response = await makeAuthenticatedRequest(`${options.apiUrl || getApiUrl()}/api/project`, {
62
61
  headers: {
63
62
  Authorization: `Bearer ${auth.accessToken}`,
64
63
  'X-Organization': selectedOrg.slug
@@ -67,28 +66,26 @@ export async function projectSelectCommand(options = {}, globalOptions = {}) {
67
66
  output.stopSpinner();
68
67
 
69
68
  // Handle both array response and object with projects property
70
- const projects = Array.isArray(response) ? response : response.projects || [];
69
+ let projects = Array.isArray(response) ? response : response.projects || [];
71
70
  if (projects.length === 0) {
72
71
  output.error('No projects found');
73
- output.blank();
74
- output.info(`Create a project in ${selectedOrg.name} at https://vizzly.dev`);
72
+ output.hint(`Create a project in ${selectedOrg.name} at https://vizzly.dev`);
75
73
  process.exit(1);
76
74
  }
77
75
 
78
76
  // Select project
79
77
  output.blank();
80
- output.info('Select a project:');
81
- output.blank();
78
+ output.labelValue('Projects', '');
82
79
  projects.forEach((project, index) => {
83
80
  output.print(` ${index + 1}. ${project.name} (${project.slug})`);
84
81
  });
85
82
  output.blank();
86
- const projectChoice = await promptNumber('Enter number', 1, projects.length);
87
- const selectedProject = projects[projectChoice - 1];
83
+ let projectChoice = await promptNumber('Enter number', 1, projects.length);
84
+ let selectedProject = projects[projectChoice - 1];
88
85
 
89
86
  // Create API token for project
90
87
  output.startSpinner(`Creating API token for ${selectedProject.name}...`);
91
- const tokenResponse = await makeAuthenticatedRequest(`${options.apiUrl || getApiUrl()}/api/project/${selectedProject.slug}/tokens`, {
88
+ let tokenResponse = await makeAuthenticatedRequest(`${options.apiUrl || getApiUrl()}/api/project/${selectedProject.slug}/tokens`, {
92
89
  method: 'POST',
93
90
  headers: {
94
91
  Authorization: `Bearer ${auth.accessToken}`,
@@ -103,18 +100,20 @@ export async function projectSelectCommand(options = {}, globalOptions = {}) {
103
100
  output.stopSpinner();
104
101
 
105
102
  // Save project mapping
106
- const currentDir = resolve(process.cwd());
103
+ let currentDir = resolve(process.cwd());
107
104
  await saveProjectMapping(currentDir, {
108
105
  token: tokenResponse.token,
109
106
  projectSlug: selectedProject.slug,
110
107
  projectName: selectedProject.name,
111
108
  organizationSlug: selectedOrg.slug
112
109
  });
113
- output.success('Project configured!');
110
+ output.complete('Project configured');
114
111
  output.blank();
115
- output.info(`Project: ${selectedProject.name}`);
116
- output.info(`Organization: ${selectedOrg.name}`);
117
- output.info(`Directory: ${currentDir}`);
112
+ output.keyValue({
113
+ Project: selectedProject.name,
114
+ Organization: selectedOrg.name,
115
+ Directory: currentDir
116
+ });
118
117
  output.cleanup();
119
118
  } catch (error) {
120
119
  output.stopSpinner();
@@ -135,12 +134,17 @@ export async function projectListCommand(_options = {}, globalOptions = {}) {
135
134
  color: !globalOptions.noColor
136
135
  });
137
136
  try {
138
- const mappings = await getProjectMappings();
139
- const paths = Object.keys(mappings);
137
+ let mappings = await getProjectMappings();
138
+ let paths = Object.keys(mappings);
140
139
  if (paths.length === 0) {
141
- output.info('No projects configured');
142
- output.blank();
143
- output.info('Run "vizzly project:select" to configure a project');
140
+ if (globalOptions.json) {
141
+ output.data({});
142
+ } else {
143
+ output.header('project:list');
144
+ output.print(' No projects configured');
145
+ output.blank();
146
+ output.hint('Run "vizzly project:select" to configure a project');
147
+ }
144
148
  output.cleanup();
145
149
  return;
146
150
  }
@@ -149,22 +153,29 @@ export async function projectListCommand(_options = {}, globalOptions = {}) {
149
153
  output.cleanup();
150
154
  return;
151
155
  }
152
- output.info('Configured projects:');
153
- output.blank();
154
- const currentDir = resolve(process.cwd());
155
- for (const path of paths) {
156
- const mapping = mappings[path];
157
- const isCurrent = path === currentDir;
158
- const marker = isCurrent ? '→' : ' ';
159
-
160
- // Extract token string (handle both string and object formats)
161
- const tokenStr = typeof mapping.token === 'string' ? mapping.token : mapping.token?.token || '[invalid token]';
162
- output.print(`${marker} ${path}`);
163
- output.print(` Project: ${mapping.projectName} (${mapping.projectSlug})`);
164
- output.print(` Organization: ${mapping.organizationSlug}`);
156
+ output.header('project:list');
157
+ let colors = output.getColors();
158
+ let currentDir = resolve(process.cwd());
159
+ for (let path of paths) {
160
+ let mapping = mappings[path];
161
+ let isCurrent = path === currentDir;
162
+ let marker = isCurrent ? colors.brand.amber('→') : ' ';
163
+ output.print(`${marker} ${isCurrent ? colors.bold(path) : path}`);
164
+ output.keyValue({
165
+ Project: `${mapping.projectName} (${mapping.projectSlug})`,
166
+ Org: mapping.organizationSlug
167
+ }, {
168
+ indent: 4
169
+ });
165
170
  if (globalOptions.verbose) {
166
- output.print(` Token: ${tokenStr.substring(0, 20)}...`);
167
- output.print(` Created: ${new Date(mapping.createdAt).toLocaleString()}`);
171
+ // Extract token string (handle both string and object formats)
172
+ let tokenStr = typeof mapping.token === 'string' ? mapping.token : mapping.token?.token || '[invalid token]';
173
+ output.hint(`Token: ${tokenStr.substring(0, 20)}...`, {
174
+ indent: 4
175
+ });
176
+ output.hint(`Created: ${new Date(mapping.createdAt).toLocaleString()}`, {
177
+ indent: 4
178
+ });
168
179
  }
169
180
  output.blank();
170
181
  }
@@ -187,17 +198,16 @@ export async function projectTokenCommand(_options = {}, globalOptions = {}) {
187
198
  color: !globalOptions.noColor
188
199
  });
189
200
  try {
190
- const currentDir = resolve(process.cwd());
191
- const mapping = await getProjectMapping(currentDir);
201
+ let currentDir = resolve(process.cwd());
202
+ let mapping = await getProjectMapping(currentDir);
192
203
  if (!mapping) {
193
204
  output.error('No project configured for this directory');
194
- output.blank();
195
- output.info('Run "vizzly project:select" to configure a project');
205
+ output.hint('Run "vizzly project:select" to configure a project');
196
206
  process.exit(1);
197
207
  }
198
208
 
199
209
  // Extract token string (handle both string and object formats)
200
- const tokenStr = typeof mapping.token === 'string' ? mapping.token : mapping.token?.token || '[invalid token]';
210
+ let tokenStr = typeof mapping.token === 'string' ? mapping.token : mapping.token?.token || '[invalid token]';
201
211
  if (globalOptions.json) {
202
212
  output.data({
203
213
  token: tokenStr,
@@ -207,12 +217,15 @@ export async function projectTokenCommand(_options = {}, globalOptions = {}) {
207
217
  output.cleanup();
208
218
  return;
209
219
  }
210
- output.info('Project token:');
211
- output.blank();
212
- output.print(` ${tokenStr}`);
220
+ output.header('project:token');
221
+ output.printBox(tokenStr, {
222
+ title: 'Token'
223
+ });
213
224
  output.blank();
214
- output.info(`Project: ${mapping.projectName} (${mapping.projectSlug})`);
215
- output.info(`Organization: ${mapping.organizationSlug}`);
225
+ output.keyValue({
226
+ Project: `${mapping.projectName} (${mapping.projectSlug})`,
227
+ Org: mapping.organizationSlug
228
+ });
216
229
  output.cleanup();
217
230
  } catch (error) {
218
231
  output.error('Failed to get project token', error);
@@ -275,31 +288,47 @@ export async function projectRemoveCommand(_options = {}, globalOptions = {}) {
275
288
  color: !globalOptions.noColor
276
289
  });
277
290
  try {
278
- const currentDir = resolve(process.cwd());
279
- const mapping = await getProjectMapping(currentDir);
291
+ let currentDir = resolve(process.cwd());
292
+ let mapping = await getProjectMapping(currentDir);
280
293
  if (!mapping) {
281
- output.info('No project configured for this directory');
294
+ if (globalOptions.json) {
295
+ output.data({
296
+ removed: false,
297
+ reason: 'not_configured'
298
+ });
299
+ } else {
300
+ output.header('project:remove');
301
+ output.print(' No project configured for this directory');
302
+ }
282
303
  output.cleanup();
283
304
  return;
284
305
  }
285
306
 
286
307
  // Confirm removal
308
+ output.header('project:remove');
309
+ output.labelValue('Current configuration', '');
310
+ output.keyValue({
311
+ Project: `${mapping.projectName} (${mapping.projectSlug})`,
312
+ Org: mapping.organizationSlug,
313
+ Directory: currentDir
314
+ });
287
315
  output.blank();
288
- output.info('Current project configuration:');
289
- output.print(` Project: ${mapping.projectName} (${mapping.projectSlug})`);
290
- output.print(` Organization: ${mapping.organizationSlug}`);
291
- output.print(` Directory: ${currentDir}`);
292
- output.blank();
293
- const confirmed = await promptConfirm('Remove this project configuration?');
316
+ let confirmed = await promptConfirm('Remove this project configuration?');
294
317
  if (!confirmed) {
295
- output.info('Cancelled');
318
+ output.print(' Cancelled');
296
319
  output.cleanup();
297
320
  return;
298
321
  }
299
322
  await deleteProjectMapping(currentDir);
300
- output.success('Project configuration removed');
301
- output.blank();
302
- output.info('Run "vizzly project:select" to configure a different project');
323
+ if (globalOptions.json) {
324
+ output.data({
325
+ removed: true
326
+ });
327
+ } else {
328
+ output.complete('Project configuration removed');
329
+ output.blank();
330
+ output.hint('Run "vizzly project:select" to configure a different project');
331
+ }
303
332
  output.cleanup();
304
333
  } catch (error) {
305
334
  output.error('Failed to remove project configuration', error);