@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
@@ -0,0 +1,393 @@
1
+ /**
2
+ * Project Operations - Project operations with dependency injection
3
+ *
4
+ * Each operation takes its dependencies as parameters:
5
+ * - mappingStore: for reading/writing project mappings
6
+ * - httpClient: for making API requests (OAuth or API token based)
7
+ *
8
+ * This makes them trivially testable without mocking modules.
9
+ */
10
+
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
+ }
87
+
88
+ // ============================================================================
89
+ // API Operations - List Projects
90
+ // ============================================================================
91
+
92
+ /**
93
+ * List all projects from API using OAuth authentication
94
+ * @param {Object} oauthClient - OAuth HTTP client with authenticatedRequest method
95
+ * @returns {Promise<Array>} Array of projects with organization info
96
+ */
97
+ export async function listProjectsWithOAuth(oauthClient) {
98
+ // First get the user's organizations via whoami
99
+ let whoami = await oauthClient.authenticatedRequest('/api/auth/cli/whoami', {
100
+ method: 'GET'
101
+ });
102
+ let organizations = extractOrganizations(whoami);
103
+ if (organizations.length === 0) {
104
+ return [];
105
+ }
106
+
107
+ // Fetch projects for each organization
108
+ let allProjects = [];
109
+ for (let org of organizations) {
110
+ try {
111
+ let response = await oauthClient.authenticatedRequest('/api/project', {
112
+ method: 'GET',
113
+ headers: buildOrgHeader(org.slug)
114
+ });
115
+ let projects = extractProjects(response);
116
+ let enriched = enrichProjectsWithOrg(projects, org);
117
+ allProjects.push(...enriched);
118
+ } catch {
119
+ // Silently skip failed orgs
120
+ }
121
+ }
122
+ return allProjects;
123
+ }
124
+
125
+ /**
126
+ * List all projects from API using API token authentication
127
+ * @param {Object} apiClient - API HTTP client with request method
128
+ * @returns {Promise<Array>} Array of projects
129
+ */
130
+ export async function listProjectsWithApiToken(apiClient) {
131
+ let response = await apiClient.request('/api/project', {
132
+ method: 'GET'
133
+ });
134
+ return extractProjects(response);
135
+ }
136
+
137
+ /**
138
+ * List all projects, trying OAuth first then falling back to API token
139
+ * @param {Object} options - Options
140
+ * @param {Object} [options.oauthClient] - OAuth HTTP client
141
+ * @param {Object} [options.apiClient] - API token HTTP client
142
+ * @returns {Promise<Array>} Array of projects
143
+ */
144
+ export async function listProjects({
145
+ oauthClient,
146
+ apiClient
147
+ }) {
148
+ // Try OAuth-based request first
149
+ if (oauthClient) {
150
+ try {
151
+ return await listProjectsWithOAuth(oauthClient);
152
+ } catch {
153
+ // Fall back to API token
154
+ }
155
+ }
156
+
157
+ // Fall back to API token-based request
158
+ if (apiClient) {
159
+ try {
160
+ return await listProjectsWithApiToken(apiClient);
161
+ } catch {
162
+ return [];
163
+ }
164
+ }
165
+
166
+ // No authentication available
167
+ return [];
168
+ }
169
+
170
+ // ============================================================================
171
+ // API Operations - Get Project
172
+ // ============================================================================
173
+
174
+ /**
175
+ * Get project details using OAuth authentication
176
+ * @param {Object} oauthClient - OAuth HTTP client
177
+ * @param {string} projectSlug - Project slug
178
+ * @param {string} organizationSlug - Organization slug
179
+ * @returns {Promise<Object>} Project details
180
+ */
181
+ export async function getProjectWithOAuth(oauthClient, projectSlug, organizationSlug) {
182
+ let response = await oauthClient.authenticatedRequest(buildProjectUrl(projectSlug), {
183
+ method: 'GET',
184
+ headers: buildOrgHeader(organizationSlug)
185
+ });
186
+ return extractProject(response);
187
+ }
188
+
189
+ /**
190
+ * Get project details using API token authentication
191
+ * @param {Object} apiClient - API HTTP client
192
+ * @param {string} projectSlug - Project slug
193
+ * @param {string} organizationSlug - Organization slug
194
+ * @returns {Promise<Object>} Project details
195
+ */
196
+ export async function getProjectWithApiToken(apiClient, projectSlug, organizationSlug) {
197
+ let response = await apiClient.request(buildProjectUrl(projectSlug), {
198
+ method: 'GET',
199
+ headers: buildOrgHeader(organizationSlug)
200
+ });
201
+ return extractProject(response);
202
+ }
203
+
204
+ /**
205
+ * Get project details, trying OAuth first then falling back to API token
206
+ * @param {Object} options - Options
207
+ * @param {Object} [options.oauthClient] - OAuth HTTP client
208
+ * @param {Object} [options.apiClient] - API token HTTP client
209
+ * @param {string} options.projectSlug - Project slug
210
+ * @param {string} options.organizationSlug - Organization slug
211
+ * @returns {Promise<Object>} Project details
212
+ */
213
+ export async function getProject({
214
+ oauthClient,
215
+ apiClient,
216
+ projectSlug,
217
+ organizationSlug
218
+ }) {
219
+ // Try OAuth-based request first
220
+ if (oauthClient) {
221
+ try {
222
+ return await getProjectWithOAuth(oauthClient, projectSlug, organizationSlug);
223
+ } catch {
224
+ // Fall back to API token
225
+ }
226
+ }
227
+
228
+ // Fall back to API token
229
+ if (apiClient) {
230
+ try {
231
+ return await getProjectWithApiToken(apiClient, projectSlug, organizationSlug);
232
+ } catch (error) {
233
+ throw buildProjectFetchError(error);
234
+ }
235
+ }
236
+ throw buildNoAuthError();
237
+ }
238
+
239
+ // ============================================================================
240
+ // API Operations - Recent Builds
241
+ // ============================================================================
242
+
243
+ /**
244
+ * Get recent builds for a project using OAuth authentication
245
+ * @param {Object} oauthClient - OAuth HTTP client
246
+ * @param {string} projectSlug - Project slug
247
+ * @param {string} organizationSlug - Organization slug
248
+ * @param {Object} options - Query options
249
+ * @returns {Promise<Array>} Array of builds
250
+ */
251
+ export async function getRecentBuildsWithOAuth(oauthClient, projectSlug, organizationSlug, options = {}) {
252
+ let response = await oauthClient.authenticatedRequest(buildBuildsUrl(projectSlug, options), {
253
+ method: 'GET',
254
+ headers: buildOrgHeader(organizationSlug)
255
+ });
256
+ return extractBuilds(response);
257
+ }
258
+
259
+ /**
260
+ * Get recent builds for a project using API token authentication
261
+ * @param {Object} apiClient - API HTTP client
262
+ * @param {string} projectSlug - Project slug
263
+ * @param {string} organizationSlug - Organization slug
264
+ * @param {Object} options - Query options
265
+ * @returns {Promise<Array>} Array of builds
266
+ */
267
+ export async function getRecentBuildsWithApiToken(apiClient, projectSlug, organizationSlug, options = {}) {
268
+ let response = await apiClient.request(buildBuildsUrl(projectSlug, options), {
269
+ method: 'GET',
270
+ headers: buildOrgHeader(organizationSlug)
271
+ });
272
+ return extractBuilds(response);
273
+ }
274
+
275
+ /**
276
+ * Get recent builds for a project, trying OAuth first then falling back to API token
277
+ * @param {Object} options - Options
278
+ * @param {Object} [options.oauthClient] - OAuth HTTP client
279
+ * @param {Object} [options.apiClient] - API token HTTP client
280
+ * @param {string} options.projectSlug - Project slug
281
+ * @param {string} options.organizationSlug - Organization slug
282
+ * @param {number} [options.limit] - Number of builds to fetch
283
+ * @param {string} [options.branch] - Filter by branch
284
+ * @returns {Promise<Array>} Array of builds
285
+ */
286
+ export async function getRecentBuilds({
287
+ oauthClient,
288
+ apiClient,
289
+ projectSlug,
290
+ organizationSlug,
291
+ limit,
292
+ branch
293
+ }) {
294
+ let queryOptions = {
295
+ limit,
296
+ branch
297
+ };
298
+
299
+ // Try OAuth-based request first
300
+ if (oauthClient) {
301
+ try {
302
+ return await getRecentBuildsWithOAuth(oauthClient, projectSlug, organizationSlug, queryOptions);
303
+ } catch {
304
+ // Fall back to API token
305
+ }
306
+ }
307
+
308
+ // Fall back to API token-based request
309
+ if (apiClient) {
310
+ try {
311
+ return await getRecentBuildsWithApiToken(apiClient, projectSlug, organizationSlug, queryOptions);
312
+ } catch {
313
+ return [];
314
+ }
315
+ }
316
+
317
+ // No authentication available
318
+ return [];
319
+ }
320
+
321
+ // ============================================================================
322
+ // API Operations - Project Tokens
323
+ // ============================================================================
324
+
325
+ /**
326
+ * Create a project token
327
+ * @param {Object} apiClient - API HTTP client with request method
328
+ * @param {string} projectSlug - Project slug
329
+ * @param {string} organizationSlug - Organization slug
330
+ * @param {Object} tokenData - Token data
331
+ * @param {string} tokenData.name - Token name
332
+ * @param {string} [tokenData.description] - Token description
333
+ * @returns {Promise<Object>} Created token
334
+ */
335
+ export async function createProjectToken(apiClient, projectSlug, organizationSlug, tokenData) {
336
+ if (!apiClient) {
337
+ throw buildNoApiServiceError();
338
+ }
339
+ try {
340
+ let response = await apiClient.request(buildTokensUrl(organizationSlug, projectSlug), {
341
+ method: 'POST',
342
+ headers: {
343
+ 'Content-Type': 'application/json'
344
+ },
345
+ body: JSON.stringify(tokenData)
346
+ });
347
+ return extractToken(response);
348
+ } catch (error) {
349
+ throw buildTokenCreateError(error);
350
+ }
351
+ }
352
+
353
+ /**
354
+ * List project tokens
355
+ * @param {Object} apiClient - API HTTP client with request method
356
+ * @param {string} projectSlug - Project slug
357
+ * @param {string} organizationSlug - Organization slug
358
+ * @returns {Promise<Array>} Array of tokens
359
+ */
360
+ export async function listProjectTokens(apiClient, projectSlug, organizationSlug) {
361
+ if (!apiClient) {
362
+ throw buildNoApiServiceError();
363
+ }
364
+ try {
365
+ let response = await apiClient.request(buildTokensUrl(organizationSlug, projectSlug), {
366
+ method: 'GET'
367
+ });
368
+ return extractTokens(response);
369
+ } catch (error) {
370
+ throw buildTokensFetchError(error);
371
+ }
372
+ }
373
+
374
+ /**
375
+ * Revoke a project token
376
+ * @param {Object} apiClient - API HTTP client with request method
377
+ * @param {string} projectSlug - Project slug
378
+ * @param {string} organizationSlug - Organization slug
379
+ * @param {string} tokenId - Token ID to revoke
380
+ * @returns {Promise<void>}
381
+ */
382
+ export async function revokeProjectToken(apiClient, projectSlug, organizationSlug, tokenId) {
383
+ if (!apiClient) {
384
+ throw buildNoApiServiceError();
385
+ }
386
+ try {
387
+ await apiClient.request(buildTokensUrl(organizationSlug, projectSlug, tokenId), {
388
+ method: 'DELETE'
389
+ });
390
+ } catch (error) {
391
+ throw buildTokenRevokeError(error);
392
+ }
393
+ }