@vizzly-testing/cli 0.9.1 → 0.10.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.
@@ -0,0 +1,496 @@
1
+ # Vizzly Plugin System
2
+
3
+ The Vizzly CLI supports a powerful plugin system that allows you to extend its functionality with
4
+ custom commands. This enables community contributions and specialized integrations while keeping the
5
+ core CLI lean and focused.
6
+
7
+ ## Overview
8
+
9
+ Plugins are JavaScript modules that export a simple registration function. The CLI automatically
10
+ discovers plugins from `node_modules/@vizzly-testing/*` or loads them explicitly from your config
11
+ file.
12
+
13
+ ## Benefits
14
+
15
+ - **Zero Configuration** - Just `npm install` and the plugin is available
16
+ - **Shared Infrastructure** - Plugins get access to config, logger, and services
17
+ - **Independent Releases** - Plugins can iterate without requiring CLI updates
18
+ - **Smaller Core** - Keep the main CLI lean by moving optional features to plugins
19
+ - **Community Extensible** - Anyone can build and share plugins
20
+
21
+ ## Using Plugins
22
+
23
+ ### Official Plugins
24
+
25
+ Official Vizzly plugins are published under the `@vizzly-testing/*` scope and are automatically
26
+ discovered:
27
+
28
+ ```bash
29
+ # Install plugin
30
+ npm install @vizzly-testing/storybook
31
+
32
+ # Use immediately - no configuration needed!
33
+ vizzly storybook ./storybook-static
34
+
35
+ # Plugin commands appear in help
36
+ vizzly --help
37
+ ```
38
+
39
+ ### Community Plugins
40
+
41
+ You can use community plugins or your own local plugins by configuring them explicitly:
42
+
43
+ ```javascript
44
+ // vizzly.config.js
45
+ export default {
46
+ plugins: [
47
+ './my-custom-plugin.js', // Local file path
48
+ 'npm-package-name', // npm package name
49
+ ],
50
+ };
51
+ ```
52
+
53
+ This is useful for:
54
+ - Local plugin development and testing
55
+ - Private/internal company plugins
56
+ - Community plugins not in the `@vizzly-testing` scope
57
+ - Custom workflow automation
58
+
59
+ ## Creating a Plugin
60
+
61
+ ### Basic Plugin Structure
62
+
63
+ A plugin is a JavaScript module that exports an object with `name` and a `register` function:
64
+
65
+ ```javascript
66
+ // my-plugin.js
67
+ export default {
68
+ name: 'my-plugin',
69
+ version: '1.0.0', // Optional but recommended
70
+
71
+ register(program, { config, logger, services }) {
72
+ // Register your command with Commander.js
73
+ program
74
+ .command('my-command <arg>')
75
+ .description('Description of my command')
76
+ .option('--option <value>', 'An option')
77
+ .action(async (arg, options) => {
78
+ logger.info(`Running my-command with ${arg}`);
79
+
80
+ // Access shared services if needed
81
+ let apiService = await services.get('apiService');
82
+
83
+ // Your command logic here
84
+ });
85
+ }
86
+ };
87
+ ```
88
+
89
+ ### Plugin Interface
90
+
91
+ #### Required Fields
92
+
93
+ - **`name`** (string) - Unique identifier for your plugin
94
+ - **`register`** (function) - Called during CLI initialization to register commands
95
+
96
+ #### Optional Fields
97
+
98
+ - **`version`** (string) - Plugin version (recommended for debugging and compatibility)
99
+
100
+ #### Register Function Parameters
101
+
102
+ The `register` function receives two arguments:
103
+
104
+ 1. **`program`** - [Commander.js](https://github.com/tj/commander.js) program instance for registering commands
105
+ 2. **`context`** - Object containing:
106
+ - `config` - Merged Vizzly configuration object
107
+ - `logger` - Shared logger instance with `.debug()`, `.info()`, `.warn()`, `.error()` methods
108
+ - `services` - Service container with access to internal Vizzly services
109
+
110
+ ### Available Services
111
+
112
+ Plugins can access these services from the container:
113
+
114
+ - **`logger`** - Component logger for consistent output
115
+ - **`apiService`** - Vizzly API client for interacting with the platform
116
+ - **`uploader`** - Screenshot upload service
117
+ - **`buildManager`** - Build lifecycle management
118
+ - **`serverManager`** - Screenshot server management
119
+ - **`tddService`** - TDD mode services
120
+ - **`testRunner`** - Test execution service
121
+
122
+ Example accessing a service:
123
+
124
+ ```javascript
125
+ register(program, { config, logger, services }) {
126
+ program
127
+ .command('upload-screenshots <dir>')
128
+ .action(async (dir) => {
129
+ let uploader = await services.get('uploader');
130
+ await uploader.uploadScreenshots(screenshots);
131
+ });
132
+ }
133
+ ```
134
+
135
+ ## Local Plugin Development
136
+
137
+ ### Setup
138
+
139
+ 1. Create your plugin file:
140
+
141
+ ```bash
142
+ mkdir -p plugins
143
+ touch plugins/my-plugin.js
144
+ ```
145
+
146
+ 2. Write your plugin:
147
+
148
+ ```javascript
149
+ // plugins/my-plugin.js
150
+ export default {
151
+ name: 'my-plugin',
152
+ version: '1.0.0',
153
+ register(program, { config, logger }) {
154
+ program
155
+ .command('greet <name>')
156
+ .description('Greet someone')
157
+ .action((name) => {
158
+ logger.info(`Hello, ${name}!`);
159
+ });
160
+ }
161
+ };
162
+ ```
163
+
164
+ 3. Configure Vizzly to load your plugin:
165
+
166
+ ```javascript
167
+ // vizzly.config.js
168
+ export default {
169
+ plugins: ['./plugins/my-plugin.js'],
170
+ };
171
+ ```
172
+
173
+ 4. Test your plugin:
174
+
175
+ ```bash
176
+ vizzly --help # See your command listed
177
+ vizzly greet World # Test your command
178
+ ```
179
+
180
+ ### Package Structure (For Distribution)
181
+
182
+ If you want to share your plugin via npm:
183
+
184
+ ```
185
+ my-vizzly-plugin/
186
+ ├── package.json
187
+ ├── plugin.js # Main plugin file
188
+ ├── lib/ # Plugin implementation
189
+ ├── README.md
190
+ └── tests/
191
+ ```
192
+
193
+ Add a `vizzly.plugin` field to your `package.json`:
194
+
195
+ ```json
196
+ {
197
+ "name": "vizzly-plugin-custom",
198
+ "version": "1.0.0",
199
+ "description": "My custom Vizzly plugin",
200
+ "main": "./lib/index.js",
201
+ "vizzly": {
202
+ "plugin": "./plugin.js"
203
+ },
204
+ "keywords": ["vizzly", "vizzly-plugin"],
205
+ "peerDependencies": {
206
+ "@vizzly-testing/cli": "^0.9.0"
207
+ }
208
+ }
209
+ ```
210
+
211
+ Users can then install and use your plugin:
212
+
213
+ ```bash
214
+ npm install vizzly-plugin-custom
215
+ ```
216
+
217
+ ```javascript
218
+ // vizzly.config.js
219
+ export default {
220
+ plugins: ['vizzly-plugin-custom'],
221
+ };
222
+ ```
223
+
224
+ ## Best Practices
225
+
226
+ ### Error Handling
227
+
228
+ Always handle errors gracefully and provide helpful error messages:
229
+
230
+ ```javascript
231
+ register(program, { logger }) {
232
+ program
233
+ .command('process <file>')
234
+ .action(async (file) => {
235
+ try {
236
+ if (!existsSync(file)) {
237
+ logger.error(`File not found: ${file}`);
238
+ process.exit(1);
239
+ }
240
+ // Process file...
241
+ } catch (error) {
242
+ logger.error(`Failed to process file: ${error.message}`);
243
+ process.exit(1);
244
+ }
245
+ });
246
+ }
247
+ ```
248
+
249
+ ### Logging
250
+
251
+ Use the provided logger for consistent output across all CLI commands:
252
+
253
+ ```javascript
254
+ logger.debug('Detailed debug info'); // Only shown with --verbose
255
+ logger.info('Normal information'); // Standard output
256
+ logger.warn('Warning message'); // Warning output
257
+ logger.error('Error message'); // Error output
258
+ ```
259
+
260
+ ### Async Operations
261
+
262
+ Use async/await for asynchronous operations:
263
+
264
+ ```javascript
265
+ .action(async (options) => {
266
+ let service = await services.get('apiService');
267
+ let result = await service.doSomething();
268
+ logger.info(`Result: ${result}`);
269
+ });
270
+ ```
271
+
272
+ ### Command Naming
273
+
274
+ Choose clear, descriptive command names that don't conflict with core commands:
275
+
276
+ ✅ **Good command names:**
277
+ - `vizzly storybook`
278
+ - `vizzly import-chromatic`
279
+ - `vizzly generate-report`
280
+
281
+ ❌ **Avoid these (conflicts with core):**
282
+ - `vizzly run`
283
+ - `vizzly upload`
284
+ - `vizzly init`
285
+ - `vizzly tdd`
286
+
287
+ ### Input Validation
288
+
289
+ Validate user input and provide helpful error messages:
290
+
291
+ ```javascript
292
+ .action(async (path, options) => {
293
+ if (!path) {
294
+ logger.error('Path is required');
295
+ process.exit(1);
296
+ }
297
+
298
+ if (!existsSync(path)) {
299
+ logger.error(`Path not found: ${path}`);
300
+ logger.info('Please provide a valid path to your build directory');
301
+ process.exit(1);
302
+ }
303
+
304
+ // Continue with logic
305
+ });
306
+ ```
307
+
308
+ ### Lazy Loading
309
+
310
+ Import heavy dependencies only when needed to keep CLI startup fast:
311
+
312
+ ```javascript
313
+ register(program, { logger }) {
314
+ program
315
+ .command('process-images <dir>')
316
+ .action(async (dir) => {
317
+ // Only import when command runs
318
+ let { processImages } = await import('./heavy-dependency.js');
319
+ await processImages(dir);
320
+ });
321
+ }
322
+ ```
323
+
324
+ ## Examples
325
+
326
+ ### Simple Command Plugin
327
+
328
+ ```javascript
329
+ export default {
330
+ name: 'hello',
331
+ version: '1.0.0',
332
+ register(program, { logger }) {
333
+ program
334
+ .command('hello <name>')
335
+ .description('Say hello')
336
+ .option('-l, --loud', 'Say it loudly')
337
+ .action((name, options) => {
338
+ let greeting = `Hello, ${name}!`;
339
+ if (options.loud) {
340
+ greeting = greeting.toUpperCase();
341
+ }
342
+ logger.info(greeting);
343
+ });
344
+ }
345
+ };
346
+ ```
347
+
348
+ ### Screenshot Capture Plugin
349
+
350
+ ```javascript
351
+ export default {
352
+ name: 'storybook',
353
+ version: '1.0.0',
354
+ register(program, { config, logger, services }) {
355
+ program
356
+ .command('storybook <path>')
357
+ .description('Capture screenshots from Storybook build')
358
+ .option('--viewports <list>', 'Comma-separated viewports', '1280x720')
359
+ .action(async (path, options) => {
360
+ logger.info(`Crawling Storybook at ${path}`);
361
+
362
+ // Import dependencies lazily
363
+ let { crawlStorybook } = await import('./crawler.js');
364
+
365
+ // Capture screenshots
366
+ let screenshots = await crawlStorybook(path, {
367
+ viewports: options.viewports.split(','),
368
+ logger,
369
+ });
370
+
371
+ logger.info(`Captured ${screenshots.length} screenshots`);
372
+
373
+ // Upload using Vizzly's uploader service
374
+ let uploader = await services.get('uploader');
375
+ await uploader.uploadScreenshots(screenshots);
376
+
377
+ logger.info('Upload complete!');
378
+ });
379
+ }
380
+ };
381
+ ```
382
+
383
+ ### Multi-Command Plugin
384
+
385
+ ```javascript
386
+ export default {
387
+ name: 'reports',
388
+ version: '1.0.0',
389
+ register(program, { logger }) {
390
+ let reports = program
391
+ .command('reports')
392
+ .description('Report generation commands');
393
+
394
+ reports
395
+ .command('generate')
396
+ .description('Generate a new report')
397
+ .action(() => {
398
+ logger.info('Generating report...');
399
+ });
400
+
401
+ reports
402
+ .command('list')
403
+ .description('List all reports')
404
+ .action(() => {
405
+ logger.info('Listing reports...');
406
+ });
407
+ }
408
+ };
409
+ ```
410
+
411
+ ## Troubleshooting
412
+
413
+ ### Plugin Not Loading
414
+
415
+ **Check plugin configuration:**
416
+
417
+ ```bash
418
+ vizzly --verbose --help
419
+ ```
420
+
421
+ This will show debug output about plugin loading.
422
+
423
+ **Common issues:**
424
+ - File path is incorrect (should be relative to `vizzly.config.js`)
425
+ - Plugin file has syntax errors
426
+ - Missing `export default` statement
427
+
428
+ ### Plugin Command Not Showing
429
+
430
+ **Verify plugin loaded successfully:**
431
+
432
+ ```bash
433
+ vizzly --verbose --help 2>&1 | grep -i plugin
434
+ ```
435
+
436
+ **Common issues:**
437
+ - Missing `name` or `register` function
438
+ - Command name conflicts with existing commands
439
+ - Plugin threw an error during registration
440
+
441
+ ### Type Errors in Editor
442
+
443
+ If you're using TypeScript or want better IDE support, you can add JSDoc types:
444
+
445
+ ```javascript
446
+ /**
447
+ * @param {import('commander').Command} program
448
+ * @param {Object} context
449
+ * @param {Object} context.config
450
+ * @param {Object} context.logger
451
+ * @param {Object} context.services
452
+ */
453
+ function register(program, { config, logger, services }) {
454
+ // Your plugin code with full autocomplete!
455
+ }
456
+
457
+ export default {
458
+ name: 'my-plugin',
459
+ register,
460
+ };
461
+ ```
462
+
463
+ ## Contributing a Plugin
464
+
465
+ ### Share Your Plugin
466
+
467
+ Built a useful plugin? We'd love to feature it! Here's how to share:
468
+
469
+ 1. **Publish to npm** (optional but recommended for reusability)
470
+ 2. **Add documentation** - Include a clear README with usage examples
471
+ 3. **Open an issue** on the [Vizzly CLI repository](https://github.com/vizzly-testing/cli/issues) with:
472
+ - Plugin name and description
473
+ - Link to repository or npm package
474
+ - Usage example
475
+ - Screenshots/demo if applicable
476
+
477
+ ### Plugin Ideas
478
+
479
+ Here are some plugin ideas the community might find useful:
480
+
481
+ - **Playwright integration** - Automated screenshot capture from Playwright tests
482
+ - **Cypress integration** - Screenshot capture from Cypress tests
483
+ - **Percy migration** - Import screenshots from Percy
484
+ - **Chromatic migration** - Import screenshots from Chromatic
485
+ - **Figma comparison** - Compare designs with Figma files
486
+ - **Image optimization** - Compress screenshots before upload
487
+ - **Custom reporters** - Generate custom HTML/PDF reports
488
+ - **CI/CD integrations** - Specialized workflows for different CI platforms
489
+
490
+ ## Support
491
+
492
+ Need help building a plugin?
493
+
494
+ - 📖 [View example plugin source](https://github.com/vizzly-testing/cli/tree/main/examples/custom-plugin)
495
+ - 🐛 [Report issues](https://github.com/vizzly-testing/cli/issues)
496
+ - 📧 [Email support](https://vizzly.dev/support/)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vizzly-testing/cli",
3
- "version": "0.9.1",
3
+ "version": "0.10.0",
4
4
  "description": "Visual review platform for UI developers and designers",
5
5
  "keywords": [
6
6
  "visual-testing",
@@ -83,7 +83,8 @@
83
83
  "dotenv": "^17.2.1",
84
84
  "form-data": "^4.0.0",
85
85
  "glob": "^11.0.3",
86
- "odiff-bin": "^3.2.1"
86
+ "odiff-bin": "^3.2.1",
87
+ "zod": "^3.24.1"
87
88
  },
88
89
  "devDependencies": {
89
90
  "@babel/cli": "^7.28.0",
@@ -103,7 +104,7 @@
103
104
  "eslint-config-prettier": "^10.1.8",
104
105
  "eslint-plugin-prettier": "^5.5.3",
105
106
  "eslint-plugin-react": "^7.37.5",
106
- "eslint-plugin-react-hooks": "^5.2.0",
107
+ "eslint-plugin-react-hooks": "^6.1.1",
107
108
  "postcss": "^8.5.6",
108
109
  "prettier": "^3.6.2",
109
110
  "react": "^19.1.1",