@vizzly-testing/cli 0.35.2 → 0.35.3-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 (43) hide show
  1. package/README.md +8 -5
  2. package/dist/api/client.js +20 -5
  3. package/dist/api/core.js +17 -3
  4. package/dist/api/endpoints.js +24 -3
  5. package/dist/api/index.js +1 -1
  6. package/dist/cli.js +25 -65
  7. package/dist/commands/baselines.js +0 -7
  8. package/dist/commands/comparisons.js +52 -45
  9. package/dist/commands/config-cmd.js +0 -7
  10. package/dist/commands/context.js +444 -238
  11. package/dist/commands/doctor.js +0 -8
  12. package/dist/commands/init.js +7 -12
  13. package/dist/commands/login.js +0 -13
  14. package/dist/commands/logout.js +0 -12
  15. package/dist/commands/orgs.js +0 -9
  16. package/dist/commands/run.js +84 -29
  17. package/dist/commands/status.js +276 -59
  18. package/dist/commands/tdd.js +19 -3
  19. package/dist/commands/whoami.js +0 -12
  20. package/dist/context/local-workspace-provider.js +293 -37
  21. package/dist/plugin-loader.js +1 -1
  22. package/dist/reporter/reporter-bundle.css +1 -1
  23. package/dist/reporter/reporter-bundle.iife.js +17 -17
  24. package/dist/server/routers/events.js +4 -4
  25. package/dist/test-runner/operations.js +70 -28
  26. package/dist/types/index.d.ts +4 -2
  27. package/dist/uploader/core.js +6 -6
  28. package/dist/uploader/operations.js +4 -1
  29. package/dist/utils/visual-context-normalizers.js +455 -0
  30. package/package.json +13 -13
  31. package/skills/vizzly/SKILL.md +68 -101
  32. package/skills/vizzly/agents/openai.yaml +2 -2
  33. package/skills/vizzly/references/cli-context.md +79 -27
  34. package/skills/vizzly/references/dynamic-content.md +28 -33
  35. package/skills/vizzly/references/sdks.md +26 -39
  36. package/skills/vizzly/references/setup-ci.md +44 -20
  37. package/dist/config/index.js +0 -13
  38. package/dist/project/index.js +0 -13
  39. package/dist/tdd/index.js +0 -19
  40. package/dist/tdd/services/baseline-downloader.js +0 -151
  41. package/dist/tdd/services/hotspot-service.js +0 -58
  42. package/dist/utils/environment.js +0 -109
  43. package/skills/vizzly/references/public-screenshots.md +0 -44
@@ -1,151 +0,0 @@
1
- /**
2
- * Baseline Downloader
3
- *
4
- * Functions for downloading baseline images from the cloud API.
5
- * These are lower-level utilities - orchestration happens in TddService.
6
- */
7
-
8
- import { existsSync, writeFileSync } from 'node:fs';
9
- import { fetchWithTimeout } from '../../utils/fetch-utils.js';
10
-
11
- /**
12
- * Download a single baseline image
13
- *
14
- * @param {string} url - URL to download from
15
- * @param {string} destPath - Destination file path
16
- * @param {Object} options - Options
17
- * @param {number} options.timeout - Request timeout in ms (default: 30000)
18
- * @returns {Promise<{ success: boolean, error?: string }>}
19
- */
20
- export async function downloadBaselineImage(url, destPath, options = {}) {
21
- let {
22
- timeout = 30000
23
- } = options;
24
- try {
25
- let response = await fetchWithTimeout(url, {
26
- timeout
27
- });
28
- if (!response.ok) {
29
- return {
30
- success: false,
31
- error: `HTTP ${response.status}: ${response.statusText}`
32
- };
33
- }
34
- let buffer = Buffer.from(await response.arrayBuffer());
35
- writeFileSync(destPath, buffer);
36
- return {
37
- success: true
38
- };
39
- } catch (error) {
40
- return {
41
- success: false,
42
- error: error.message
43
- };
44
- }
45
- }
46
-
47
- /**
48
- * Check if a baseline already exists with matching SHA
49
- *
50
- * @param {string} filePath - Path to the baseline file
51
- * @param {string} expectedSha - Expected SHA256 hash
52
- * @param {Map<string, string>} shaMap - Map of filename -> sha256
53
- * @returns {boolean}
54
- */
55
- export function baselineMatchesSha(filePath, expectedSha, shaMap) {
56
- if (!existsSync(filePath) || !expectedSha) {
57
- return false;
58
- }
59
- let filename = filePath.split('/').pop();
60
- let storedSha = shaMap.get(filename);
61
- return storedSha === expectedSha;
62
- }
63
-
64
- /**
65
- * Download multiple baselines in batches
66
- *
67
- * @param {Array} screenshots - Screenshots to download
68
- * @param {Object} options - Options
69
- * @param {string} options.baselinePath - Path to baselines directory
70
- * @param {Map<string, string>} options.existingShaMap - Existing SHA map for skip logic
71
- * @param {number} options.batchSize - Concurrent downloads (default: 5)
72
- * @param {Function} options.onProgress - Progress callback (downloaded, skipped, errors, total)
73
- * @param {Function} options.getFilePath - Function to get file path for a screenshot
74
- * @returns {Promise<{ downloaded: number, skipped: number, errors: number }>}
75
- */
76
- export async function downloadBaselinesInBatches(screenshots, options = {}) {
77
- let {
78
- existingShaMap = new Map(),
79
- batchSize = 5,
80
- onProgress,
81
- getFilePath
82
- } = options;
83
- let downloaded = 0;
84
- let skipped = 0;
85
- let errors = 0;
86
- let total = screenshots.length;
87
-
88
- // Process in batches
89
- for (let i = 0; i < screenshots.length; i += batchSize) {
90
- let batch = screenshots.slice(i, i + batchSize);
91
- let batchPromises = batch.map(async screenshot => {
92
- let filePath = getFilePath(screenshot);
93
- let url = screenshot.original_url;
94
- if (!url) {
95
- errors++;
96
- return;
97
- }
98
-
99
- // Skip if SHA matches
100
- if (baselineMatchesSha(filePath, screenshot.sha256, existingShaMap)) {
101
- skipped++;
102
- downloaded++;
103
- return;
104
- }
105
- let result = await downloadBaselineImage(url, filePath);
106
- if (result.success) {
107
- downloaded++;
108
- } else {
109
- errors++;
110
- }
111
- });
112
- await Promise.all(batchPromises);
113
- if (onProgress) {
114
- onProgress(downloaded, skipped, errors, total);
115
- }
116
- }
117
- return {
118
- downloaded,
119
- skipped,
120
- errors
121
- };
122
- }
123
-
124
- /**
125
- * Build baseline metadata entry for a downloaded screenshot
126
- *
127
- * @param {Object} screenshot - Screenshot data from API
128
- * @param {string} filename - Local filename
129
- * @param {string} filePath - Full file path
130
- * @param {Object} buildInfo - Build information
131
- * @returns {Object} Metadata entry
132
- */
133
- export function buildBaselineMetadataEntry(screenshot, filename, filePath, buildInfo = {}) {
134
- return {
135
- name: screenshot.name,
136
- originalName: screenshot.name,
137
- sha256: screenshot.sha256,
138
- id: screenshot.id,
139
- filename,
140
- path: filePath,
141
- browser: screenshot.browser || screenshot.metadata?.browser,
142
- viewport_width: screenshot.viewport_width || screenshot.metadata?.viewport?.width || screenshot.properties?.viewport?.width,
143
- originalUrl: screenshot.original_url,
144
- fileSize: screenshot.file_size,
145
- dimensions: screenshot.dimensions,
146
- // Build info for tracking
147
- buildId: buildInfo.buildId,
148
- commitSha: buildInfo.commitSha,
149
- approvalStatus: screenshot.approval_status
150
- };
151
- }
@@ -1,58 +0,0 @@
1
- /**
2
- * Hotspot Service
3
- *
4
- * Functions for downloading and managing hotspot data from the cloud.
5
- * Hotspots identify regions that frequently change due to dynamic content.
6
- */
7
-
8
- import { saveHotspotMetadata } from '../metadata/hotspot-metadata.js';
9
-
10
- /**
11
- * Download hotspots for screenshots from cloud API
12
- *
13
- * @param {Object} options
14
- * @param {Object} options.api - ApiService instance
15
- * @param {string} options.workingDir - Working directory
16
- * @param {string[]} options.screenshotNames - Names of screenshots to get hotspots for
17
- * @returns {Promise<{ success: boolean, count: number, regionCount: number, error?: string }>}
18
- */
19
- export async function downloadHotspots(options) {
20
- let {
21
- api,
22
- workingDir,
23
- screenshotNames
24
- } = options;
25
- if (!screenshotNames || screenshotNames.length === 0) {
26
- return {
27
- success: true,
28
- count: 0,
29
- regionCount: 0
30
- };
31
- }
32
- try {
33
- let response = await api.getHotspots(screenshotNames);
34
- if (!response?.hotspots) {
35
- return {
36
- success: false,
37
- error: 'API returned no hotspot data'
38
- };
39
- }
40
-
41
- // Save hotspots to disk
42
- saveHotspotMetadata(workingDir, response.hotspots, response.summary);
43
-
44
- // Calculate stats
45
- let count = Object.keys(response.hotspots).length;
46
- let regionCount = Object.values(response.hotspots).reduce((sum, h) => sum + (h.regions?.length || 0), 0);
47
- return {
48
- success: true,
49
- count,
50
- regionCount
51
- };
52
- } catch (error) {
53
- return {
54
- success: false,
55
- error: error.message
56
- };
57
- }
58
- }
@@ -1,109 +0,0 @@
1
- /**
2
- * Environment detection utilities for handling CI/interactive environments
3
- */
4
-
5
- /**
6
- * Check if running in a CI environment
7
- * Based on common CI environment variables
8
- * @returns {boolean} True if running in CI
9
- */
10
- export function isCI() {
11
- return Boolean(process.env.CI ||
12
- // Generic CI flag
13
- process.env.CONTINUOUS_INTEGRATION ||
14
- // TravisCI, CircleCI
15
- process.env.BUILD_NUMBER ||
16
- // Jenkins
17
- process.env.GITHUB_ACTIONS ||
18
- // GitHub Actions
19
- process.env.GITLAB_CI ||
20
- // GitLab CI
21
- process.env.CIRCLECI ||
22
- // CircleCI
23
- process.env.TRAVIS ||
24
- // TravisCI
25
- process.env.APPVEYOR ||
26
- // AppVeyor
27
- process.env.CODEBUILD_BUILD_ID ||
28
- // AWS CodeBuild
29
- process.env.TEAMCITY_VERSION ||
30
- // TeamCity
31
- process.env.TF_BUILD ||
32
- // Azure DevOps
33
- process.env.DRONE ||
34
- // Drone CI
35
- process.env.BITBUCKET_BUILD_NUMBER // Bitbucket Pipelines
36
- );
37
- }
38
-
39
- /**
40
- * Check if stdout supports interactive features
41
- * @returns {boolean} True if interactive features are supported
42
- */
43
- export function isInteractiveTerminal() {
44
- return Boolean(process.stdout.isTTY && process.stdin.isTTY && (!process.env.TERM || process.env.TERM !== 'dumb'));
45
- }
46
-
47
- /**
48
- * Determine if interactive mode should be enabled
49
- * Takes into account CI detection, TTY support, and explicit flags
50
- * @param {Object} options
51
- * @param {boolean} [options.noInteractive] - Explicit flag to disable interactive mode
52
- * @param {boolean} [options.interactive] - Explicit flag to force interactive mode
53
- * @returns {boolean} True if interactive mode should be enabled
54
- */
55
- export function shouldUseInteractiveMode(options = {}) {
56
- // Explicit flags take priority
57
- // Commander.js sets interactive=false when --no-interactive is used
58
- if (options.interactive === false) {
59
- return false;
60
- }
61
- if (options.interactive === true) {
62
- return true;
63
- }
64
-
65
- // Legacy support for explicit noInteractive flag
66
- if (options.noInteractive === true) {
67
- return false;
68
- }
69
-
70
- // Auto-detect based on environment
71
- return !isCI() && isInteractiveTerminal();
72
- }
73
-
74
- /**
75
- * Get environment type for logging/debugging
76
- * @returns {string} Environment type description
77
- */
78
- export function getEnvironmentType() {
79
- if (isCI()) {
80
- return 'CI';
81
- }
82
- if (!isInteractiveTerminal()) {
83
- return 'non-interactive';
84
- }
85
- return 'interactive';
86
- }
87
-
88
- /**
89
- * Get detailed environment info for debugging
90
- * @returns {Object} Environment details
91
- */
92
- export function getEnvironmentDetails() {
93
- return {
94
- isCI: isCI(),
95
- isInteractiveTerminal: isInteractiveTerminal(),
96
- environmentType: getEnvironmentType(),
97
- stdoutIsTTY: Boolean(process.stdout.isTTY),
98
- stdinIsTTY: Boolean(process.stdin.isTTY),
99
- ciVars: {
100
- CI: process.env.CI,
101
- CONTINUOUS_INTEGRATION: process.env.CONTINUOUS_INTEGRATION,
102
- GITHUB_ACTIONS: process.env.GITHUB_ACTIONS,
103
- GITLAB_CI: process.env.GITLAB_CI,
104
- CIRCLECI: process.env.CIRCLECI,
105
- TRAVIS: process.env.TRAVIS
106
- },
107
- termType: process.env.TERM
108
- };
109
- }
@@ -1,44 +0,0 @@
1
- # Public Screenshots
2
-
3
- Public screenshots are Vizzly's path for stable image URLs in docs and manuals.
4
-
5
- Use this when a user asks for hotlinked UI screenshots, generated manual images, docs that stay current with UI, or CDN-like screenshot URLs.
6
-
7
- ## Mental Model
8
-
9
- Do not use arbitrary build screenshot URLs as stable docs assets. Use Public Properties.
10
-
11
- The flow:
12
-
13
- 1. Capture screenshots with stable metadata.
14
- 2. Configure project Public Properties in Vizzly.
15
- 3. Approve/publish the baseline build.
16
- 4. Use the Public URLs tab to copy the stable URL.
17
-
18
- Example capture:
19
-
20
- ```javascript
21
- await vizzlyScreenshot('watering-widget', screenshot, {
22
- properties: {
23
- manual: 'plant-care',
24
- component: 'watering-widget',
25
- viewport: 'desktop'
26
- }
27
- });
28
- ```
29
-
30
- Configure a Public Property like `manual=plant-care`. Vizzly publishes matching approved baseline screenshots and keeps stable URLs for each screenshot/property identity.
31
-
32
- ## Good Uses
33
-
34
- - Product manuals
35
- - User docs
36
- - Component catalogs
37
- - In-app UI examples
38
- - Screenshots that should update when approved UI changes
39
-
40
- ## Cautions
41
-
42
- - Public screenshots are public. Do not publish private user data, secrets, or internal-only UI.
43
- - A new unapproved build does not automatically become the public docs image.
44
- - Use stable names and properties so URLs do not churn.