@vizzly-testing/cli 0.1.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 (90) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +363 -0
  3. package/bin/vizzly.js +3 -0
  4. package/dist/cli.js +104 -0
  5. package/dist/client/index.js +237 -0
  6. package/dist/commands/doctor.js +158 -0
  7. package/dist/commands/init.js +102 -0
  8. package/dist/commands/run.js +224 -0
  9. package/dist/commands/status.js +164 -0
  10. package/dist/commands/tdd.js +212 -0
  11. package/dist/commands/upload.js +181 -0
  12. package/dist/container/index.js +184 -0
  13. package/dist/errors/vizzly-error.js +149 -0
  14. package/dist/index.js +31 -0
  15. package/dist/screenshot-wrapper.js +68 -0
  16. package/dist/sdk/index.js +364 -0
  17. package/dist/server/index.js +522 -0
  18. package/dist/services/api-service.js +215 -0
  19. package/dist/services/base-service.js +154 -0
  20. package/dist/services/build-manager.js +214 -0
  21. package/dist/services/screenshot-server.js +96 -0
  22. package/dist/services/server-manager.js +61 -0
  23. package/dist/services/service-utils.js +171 -0
  24. package/dist/services/tdd-service.js +444 -0
  25. package/dist/services/test-runner.js +210 -0
  26. package/dist/services/uploader.js +413 -0
  27. package/dist/types/cli.d.ts +2 -0
  28. package/dist/types/client/index.d.ts +76 -0
  29. package/dist/types/commands/doctor.d.ts +11 -0
  30. package/dist/types/commands/init.d.ts +14 -0
  31. package/dist/types/commands/run.d.ts +13 -0
  32. package/dist/types/commands/status.d.ts +13 -0
  33. package/dist/types/commands/tdd.d.ts +13 -0
  34. package/dist/types/commands/upload.d.ts +13 -0
  35. package/dist/types/container/index.d.ts +61 -0
  36. package/dist/types/errors/vizzly-error.d.ts +75 -0
  37. package/dist/types/index.d.ts +10 -0
  38. package/dist/types/index.js +153 -0
  39. package/dist/types/screenshot-wrapper.d.ts +27 -0
  40. package/dist/types/sdk/index.d.ts +108 -0
  41. package/dist/types/server/index.d.ts +38 -0
  42. package/dist/types/services/api-service.d.ts +77 -0
  43. package/dist/types/services/base-service.d.ts +72 -0
  44. package/dist/types/services/build-manager.d.ts +68 -0
  45. package/dist/types/services/screenshot-server.d.ts +10 -0
  46. package/dist/types/services/server-manager.d.ts +8 -0
  47. package/dist/types/services/service-utils.d.ts +45 -0
  48. package/dist/types/services/tdd-service.d.ts +55 -0
  49. package/dist/types/services/test-runner.d.ts +25 -0
  50. package/dist/types/services/uploader.d.ts +34 -0
  51. package/dist/types/types/index.d.ts +373 -0
  52. package/dist/types/utils/colors.d.ts +12 -0
  53. package/dist/types/utils/config-helpers.d.ts +6 -0
  54. package/dist/types/utils/config-loader.d.ts +22 -0
  55. package/dist/types/utils/console-ui.d.ts +61 -0
  56. package/dist/types/utils/diagnostics.d.ts +69 -0
  57. package/dist/types/utils/environment-config.d.ts +54 -0
  58. package/dist/types/utils/environment.d.ts +36 -0
  59. package/dist/types/utils/error-messages.d.ts +42 -0
  60. package/dist/types/utils/fetch-utils.d.ts +1 -0
  61. package/dist/types/utils/framework-detector.d.ts +5 -0
  62. package/dist/types/utils/git.d.ts +44 -0
  63. package/dist/types/utils/help.d.ts +11 -0
  64. package/dist/types/utils/image-comparison.d.ts +42 -0
  65. package/dist/types/utils/logger-factory.d.ts +26 -0
  66. package/dist/types/utils/logger.d.ts +79 -0
  67. package/dist/types/utils/package-info.d.ts +15 -0
  68. package/dist/types/utils/package.d.ts +1 -0
  69. package/dist/types/utils/project-detection.d.ts +19 -0
  70. package/dist/types/utils/ui-helpers.d.ts +23 -0
  71. package/dist/utils/colors.js +66 -0
  72. package/dist/utils/config-helpers.js +8 -0
  73. package/dist/utils/config-loader.js +120 -0
  74. package/dist/utils/console-ui.js +226 -0
  75. package/dist/utils/diagnostics.js +184 -0
  76. package/dist/utils/environment-config.js +93 -0
  77. package/dist/utils/environment.js +109 -0
  78. package/dist/utils/error-messages.js +34 -0
  79. package/dist/utils/fetch-utils.js +9 -0
  80. package/dist/utils/framework-detector.js +40 -0
  81. package/dist/utils/git.js +226 -0
  82. package/dist/utils/help.js +66 -0
  83. package/dist/utils/image-comparison.js +172 -0
  84. package/dist/utils/logger-factory.js +76 -0
  85. package/dist/utils/logger.js +231 -0
  86. package/dist/utils/package-info.js +38 -0
  87. package/dist/utils/package.js +9 -0
  88. package/dist/utils/project-detection.js +145 -0
  89. package/dist/utils/ui-helpers.js +86 -0
  90. package/package.json +103 -0
@@ -0,0 +1,364 @@
1
+ /**
2
+ * Vizzly SDK - Full API for custom integrations
3
+ *
4
+ * This is the comprehensive SDK for building custom Vizzly integrations.
5
+ * For simple test runner usage, use @vizzly-testing/cli/client instead.
6
+ */
7
+
8
+ /**
9
+ * @module @vizzly-testing/cli/sdk
10
+ * @description Full SDK for custom integrations and advanced usage
11
+ */
12
+
13
+ import { EventEmitter } from 'events';
14
+ import { createUploader } from '../services/uploader.js';
15
+ import { createTDDService } from '../services/tdd-service.js';
16
+ import { ScreenshotServer } from '../services/screenshot-server.js';
17
+ import { loadConfig } from '../utils/config-loader.js';
18
+ import { createComponentLogger } from '../utils/logger-factory.js';
19
+ import { VizzlyError } from '../errors/vizzly-error.js';
20
+
21
+ /**
22
+ * Create a new Vizzly instance with custom configuration
23
+ *
24
+ * @param {import('../types').VizzlyConfig} [config] - Configuration options
25
+ * @returns {Promise<VizzlySDK>} Configured Vizzly SDK instance
26
+ *
27
+ * @example
28
+ * // Create with custom config
29
+ * import { createVizzly } from '@vizzly-testing/cli/sdk';
30
+ *
31
+ * const vizzly = await createVizzly({
32
+ * apiKey: process.env.VIZZLY_TOKEN,
33
+ * apiUrl: 'https://vizzly.dev',
34
+ * server: {
35
+ * port: 3003,
36
+ * enabled: true
37
+ * }
38
+ * });
39
+ *
40
+ * // Start the server
41
+ * await vizzly.start();
42
+ *
43
+ * // Take screenshots
44
+ * const screenshot = await getScreenshotSomehow();
45
+ * await vizzly.screenshot('my-test', screenshot);
46
+ *
47
+ * // Upload results
48
+ * const result = await vizzly.upload();
49
+ * console.log(`Build URL: ${result.url}`);
50
+ *
51
+ * // Cleanup
52
+ * await vizzly.stop();
53
+ */
54
+ export function createVizzly(config = {}, options = {}) {
55
+ const logger = options.logger || createComponentLogger('SDK', {
56
+ level: options.logLevel || 'info',
57
+ verbose: options.verbose || false
58
+ });
59
+
60
+ // Merge with loaded config
61
+ const resolvedConfig = {
62
+ ...config
63
+ };
64
+
65
+ /**
66
+ * Initialize SDK with config loading
67
+ */
68
+ const init = async () => {
69
+ const fileConfig = await loadConfig();
70
+ Object.assign(resolvedConfig, fileConfig, config); // CLI config takes precedence
71
+ return resolvedConfig;
72
+ };
73
+
74
+ /**
75
+ * Create uploader service
76
+ */
77
+ const createUploaderService = (uploaderOptions = {}) => {
78
+ return createUploader({
79
+ apiKey: resolvedConfig.apiKey,
80
+ apiUrl: resolvedConfig.apiUrl
81
+ }, {
82
+ ...options,
83
+ ...uploaderOptions,
84
+ logger
85
+ });
86
+ };
87
+
88
+ /**
89
+ * Create TDD service
90
+ */
91
+ const createTDDServiceInstance = (tddOptions = {}) => {
92
+ return createTDDService(resolvedConfig, {
93
+ ...options,
94
+ ...tddOptions,
95
+ logger
96
+ });
97
+ };
98
+
99
+ /**
100
+ * Upload screenshots (convenience method)
101
+ */
102
+ const upload = async uploadOptions => {
103
+ const uploader = createUploaderService();
104
+ return uploader.upload(uploadOptions);
105
+ };
106
+
107
+ /**
108
+ * Start TDD mode (convenience method)
109
+ */
110
+ const startTDD = async (tddOptions = {}) => {
111
+ const tddService = createTDDServiceInstance();
112
+ return tddService.start(tddOptions);
113
+ };
114
+ return {
115
+ // Core methods
116
+ init,
117
+ upload,
118
+ startTDD,
119
+ // Service factories
120
+ createUploader: createUploaderService,
121
+ createTDDService: createTDDServiceInstance,
122
+ // Utilities
123
+ loadConfig: () => loadConfig(),
124
+ createLogger: loggerOptions => createComponentLogger('USER', loggerOptions),
125
+ // Config access
126
+ getConfig: () => ({
127
+ ...resolvedConfig
128
+ }),
129
+ updateConfig: newConfig => Object.assign(resolvedConfig, newConfig)
130
+ };
131
+ }
132
+
133
+ /**
134
+ * @typedef {Object} VizzlySDK
135
+ * @property {Function} start - Start the Vizzly server
136
+ * @property {Function} stop - Stop the Vizzly server
137
+ * @property {Function} screenshot - Capture a screenshot
138
+ * @property {Function} upload - Upload screenshots to Vizzly
139
+ * @property {Function} compare - Run local comparison (TDD mode)
140
+ * @property {Function} getConfig - Get current configuration
141
+ * @property {Function} on - Subscribe to events
142
+ * @property {Function} off - Unsubscribe from events
143
+ */
144
+
145
+ /**
146
+ * VizzlySDK class implementation
147
+ * @class
148
+ * @extends {EventEmitter}
149
+ */
150
+ export class VizzlySDK extends EventEmitter {
151
+ /**
152
+ * @param {import('../types').VizzlyConfig} config - Configuration
153
+ * @param {import('../utils/logger').Logger} logger - Logger instance
154
+ * @param {Object} services - Service instances
155
+ */
156
+ constructor(config, logger, services) {
157
+ super();
158
+ this.config = config;
159
+ this.logger = logger;
160
+ this.services = services;
161
+ this.server = null;
162
+ this.currentBuildId = null;
163
+ }
164
+
165
+ /**
166
+ * Stop the Vizzly server
167
+ * @returns {Promise<void>}
168
+ */
169
+ async stop() {
170
+ if (this.server) {
171
+ await this.server.stop();
172
+ this.server = null;
173
+ this.emit('server:stopped');
174
+ this.logger.info('Vizzly server stopped');
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Get current configuration
180
+ * @returns {Object} Current config
181
+ */
182
+ getConfig() {
183
+ return {
184
+ ...this.config
185
+ };
186
+ }
187
+
188
+ /**
189
+ * Start the Vizzly server
190
+ * @returns {Promise<{port: number, url: string}>} Server information
191
+ */
192
+ async start() {
193
+ if (this.server) {
194
+ this.logger.warn('Server already running');
195
+ return {
196
+ port: this.config.server?.port || 3000,
197
+ url: `http://localhost:${this.config.server?.port || 3000}`
198
+ };
199
+ }
200
+
201
+ // Create a simple build manager for screenshot collection
202
+ const buildManager = {
203
+ screenshots: new Map(),
204
+ currentBuildId: null,
205
+ async addScreenshot(buildId, screenshot) {
206
+ if (!this.screenshots.has(buildId)) {
207
+ this.screenshots.set(buildId, []);
208
+ }
209
+ this.screenshots.get(buildId).push(screenshot);
210
+ },
211
+ getScreenshots(buildId) {
212
+ return this.screenshots.get(buildId) || [];
213
+ }
214
+ };
215
+ this.server = new ScreenshotServer(this.config, this.logger, buildManager);
216
+ await this.server.start();
217
+ const port = this.config.server?.port || 3000;
218
+ const serverInfo = {
219
+ port,
220
+ url: `http://localhost:${port}`
221
+ };
222
+ this.emit('server:started', serverInfo);
223
+ return serverInfo;
224
+ }
225
+
226
+ /**
227
+ * Capture a screenshot
228
+ * @param {string} name - Screenshot name
229
+ * @param {Buffer} imageBuffer - Image data
230
+ * @param {import('../types').ScreenshotOptions} [options] - Options
231
+ * @returns {Promise<void>}
232
+ */
233
+ async screenshot(name, imageBuffer, options = {}) {
234
+ if (!this.server || !this.server.isRunning()) {
235
+ throw new VizzlyError('Server not running. Call start() first.', 'SERVER_NOT_RUNNING');
236
+ }
237
+
238
+ // Generate or use provided build ID
239
+ const buildId = options.buildId || this.currentBuildId || 'default';
240
+ this.currentBuildId = buildId;
241
+
242
+ // Convert Buffer to base64 for JSON transport
243
+ const imageBase64 = imageBuffer.toString('base64');
244
+ const screenshotData = {
245
+ buildId,
246
+ name,
247
+ image: imageBase64,
248
+ properties: options.properties || {}
249
+ };
250
+
251
+ // POST to the local screenshot server
252
+ const serverUrl = `http://localhost:${this.config.server?.port || 3000}`;
253
+ try {
254
+ const response = await fetch(`${serverUrl}/screenshot`, {
255
+ method: 'POST',
256
+ headers: {
257
+ 'Content-Type': 'application/json'
258
+ },
259
+ body: JSON.stringify(screenshotData)
260
+ });
261
+ if (!response.ok) {
262
+ const errorData = await response.json().catch(() => ({
263
+ error: 'Unknown error'
264
+ }));
265
+ throw new VizzlyError(`Screenshot capture failed: ${errorData.error}`, 'SCREENSHOT_FAILED', {
266
+ name,
267
+ buildId,
268
+ status: response.status
269
+ });
270
+ }
271
+ this.emit('screenshot:captured', {
272
+ name,
273
+ buildId,
274
+ options
275
+ });
276
+ this.logger.debug(`Screenshot captured: ${name}`);
277
+ } catch (error) {
278
+ if (error instanceof VizzlyError) throw error;
279
+ throw new VizzlyError(`Failed to send screenshot to server: ${error.message}`, 'SCREENSHOT_TRANSPORT_ERROR', {
280
+ name,
281
+ buildId,
282
+ originalError: error.message
283
+ });
284
+ }
285
+ }
286
+
287
+ /**
288
+ * Upload all captured screenshots
289
+ * @param {import('../types').UploadOptions} [options] - Upload options
290
+ * @returns {Promise<import('../types').UploadResult>} Upload result
291
+ */
292
+ async upload(options = {}) {
293
+ if (!this.services?.uploader) {
294
+ this.services = this.services || {};
295
+ this.services.uploader = createUploader({
296
+ apiKey: this.config.apiKey,
297
+ apiUrl: this.config.apiUrl,
298
+ upload: this.config.upload
299
+ }, {
300
+ logger: this.logger
301
+ });
302
+ }
303
+
304
+ // Get the screenshots directory from config or default
305
+ const screenshotsDir = options.screenshotsDir || this.config?.upload?.screenshotsDir || './screenshots';
306
+ const uploadOptions = {
307
+ screenshotsDir,
308
+ buildName: options.buildName || this.config.buildName,
309
+ branch: options.branch || this.config.branch,
310
+ commit: options.commit || this.config.commit,
311
+ message: options.message || this.config.message,
312
+ environment: options.environment || this.config.environment || 'production',
313
+ threshold: options.threshold || this.config.threshold,
314
+ onProgress: progress => {
315
+ this.emit('upload:progress', progress);
316
+ if (options.onProgress) {
317
+ options.onProgress(progress);
318
+ }
319
+ }
320
+ };
321
+ try {
322
+ const result = await this.services.uploader.upload(uploadOptions);
323
+ this.emit('upload:completed', result);
324
+ return result;
325
+ } catch (error) {
326
+ this.emit('upload:failed', error);
327
+ throw error;
328
+ }
329
+ }
330
+
331
+ /**
332
+ * Run local comparison in TDD mode
333
+ * @param {string} name - Screenshot name
334
+ * @param {Buffer} imageBuffer - Current image
335
+ * @returns {Promise<import('../types').ComparisonResult>} Comparison result
336
+ */
337
+ async compare(name, imageBuffer) {
338
+ if (!this.services?.tddService) {
339
+ this.services = this.services || {};
340
+ this.services.tddService = createTDDService(this.config, {
341
+ logger: this.logger
342
+ });
343
+ }
344
+ try {
345
+ const result = await this.services.tddService.compareScreenshot(name, imageBuffer);
346
+ this.emit('comparison:completed', result);
347
+ return result;
348
+ } catch (error) {
349
+ this.emit('comparison:failed', {
350
+ name,
351
+ error
352
+ });
353
+ throw error;
354
+ }
355
+ }
356
+ }
357
+
358
+ // Re-export key utilities and errors
359
+ export { loadConfig } from '../utils/config-loader.js';
360
+ export { createLogger } from '../utils/logger.js';
361
+
362
+ // Export service creators for advanced usage
363
+ export { createUploader } from '../services/uploader.js';
364
+ export { createTDDService } from '../services/tdd-service.js';