@vizzly-testing/cli 0.35.3-beta.0 → 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.
@@ -19,7 +19,7 @@ let defaultTimers = {
19
19
  /**
20
20
  * Read and parse JSON from disk, returning null on missing or invalid files.
21
21
  */
22
- export function readJsonFile(path) {
22
+ function readJsonFile(path) {
23
23
  if (!existsSync(path)) {
24
24
  return null;
25
25
  }
@@ -33,7 +33,7 @@ export function readJsonFile(path) {
33
33
  /**
34
34
  * Read report data and attach baseline metadata for the stats view.
35
35
  */
36
- export function readReportDataFile({
36
+ function readReportDataFile({
37
37
  reportDataPath,
38
38
  baselineMetadataPath
39
39
  }) {
@@ -84,7 +84,7 @@ function summaryChanged(oldData, newData) {
84
84
  /**
85
85
  * Build incremental SSE events by diffing old vs new report data.
86
86
  */
87
- export function buildReportDataEvents(oldData, newData) {
87
+ function buildReportDataEvents(oldData, newData) {
88
88
  if (!oldData) {
89
89
  return [{
90
90
  type: 'reportData',
@@ -129,7 +129,7 @@ export function buildReportDataEvents(oldData, newData) {
129
129
  /**
130
130
  * Watch report-data.json with fs.watch plus a lightweight mtime fallback.
131
131
  */
132
- export function watchReportDataFile({
132
+ function watchReportDataFile({
133
133
  workingDir,
134
134
  reportDataPath,
135
135
  onReportDataChanged,
@@ -145,9 +145,11 @@ function getBaselineScreenshot(comparison = {}) {
145
145
  /**
146
146
  * Project compact Honeydiff facts without making raw geometry the default.
147
147
  *
148
- * Agents need stable counts, fingerprints, URLs, and the API projection for
149
- * first-pass diagnosis. Raw regions and scoring details stay behind an
150
- * explicit include because they can dominate an otherwise bounded handoff.
148
+ * Agents need stable counts, fingerprints, URLs, the API projection, and the
149
+ * server's authoritative artifact manifest for first-pass diagnosis. Keeping
150
+ * that manifest intact prevents the CLI from becoming less authoritative than
151
+ * the API. Raw regions and scoring details stay behind an explicit include
152
+ * because they can dominate an otherwise bounded handoff.
151
153
  *
152
154
  * @param {Object} comparison - Comparison record from the API.
153
155
  * @param {boolean} includeDiffs - Whether to include raw Honeydiff diagnostics.
@@ -158,6 +160,7 @@ function getComparisonDiff(comparison = {}, includeDiffs = false) {
158
160
  let analysis = comparison.analysis || {};
159
161
  let regions = diff.regions || diff.diff_regions || analysis.diff_regions || comparison.diff_regions;
160
162
  let projection = diff.projection || diff.analysis_projection || analysis.projection || analysis.analysis_projection || comparison.analysis_projection || comparison.projection || null;
163
+ let artifacts = diff.artifacts ?? analysis.artifacts ?? comparison.artifacts ?? null;
161
164
  let compact = {
162
165
  percentage: diff.percentage ?? analysis.percentage ?? analysis.diff_percentage ?? comparison.diff_percentage ?? null,
163
166
  changed_pixels: diff.changed_pixels ?? analysis.changed_pixels ?? comparison.changed_pixels ?? null,
@@ -168,6 +171,12 @@ function getComparisonDiff(comparison = {}, includeDiffs = false) {
168
171
  projection,
169
172
  image_url: diff.image_url || diff.url || analysis.diff_image_url || comparison.diff_url || comparison.diff_image_url || null
170
173
  };
174
+
175
+ // Preserve server-owned evidence exactly so agents can verify and download
176
+ // artifacts without the CLI recreating identities, digests, or availability.
177
+ if (artifacts != null) {
178
+ compact.artifacts = artifacts;
179
+ }
171
180
  if (includeDiffs) {
172
181
  compact.regions = regions || [];
173
182
  compact.cluster_metadata = diff.cluster_metadata || analysis.cluster_metadata || comparison.cluster_metadata || null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vizzly-testing/cli",
3
- "version": "0.35.3-beta.0",
3
+ "version": "0.35.3-beta.1",
4
4
  "description": "Visual regression testing from your terminal",
5
5
  "keywords": [
6
6
  "llm-context",
@@ -60,7 +60,8 @@
60
60
  "LICENSE"
61
61
  ],
62
62
  "scripts": {
63
- "cli": "source .envrc && node bin/vizzly.js",
63
+ "cli": "node scripts/run-development-cli.js production",
64
+ "cli:local": "node scripts/run-development-cli.js local",
64
65
  "start": "node src/index.js",
65
66
  "build": "pnpm run clean && pnpm run compile && pnpm run build:reporter && pnpm run build:reporter-ssr && pnpm run copy-types",
66
67
  "clean": "rimraf dist",
@@ -93,7 +94,7 @@
93
94
  },
94
95
  "dependencies": {
95
96
  "@vizzly-testing/bear-den": "0.1.4",
96
- "@vizzly-testing/honeydiff": "^0.11.1",
97
+ "@vizzly-testing/honeydiff": "^0.15.0",
97
98
  "ansis": "^4.2.0",
98
99
  "commander": "^15.0.0",
99
100
  "cosmiconfig": "^9.0.0",
@@ -115,18 +116,18 @@
115
116
  }
116
117
  },
117
118
  "devDependencies": {
118
- "@babel/cli": "^7.28.0",
119
- "@babel/core": "^7.23.6",
120
- "@babel/preset-env": "^7.23.6",
121
- "@babel/preset-react": "^7.27.1",
122
- "@babel/preset-typescript": "^7.23.6",
119
+ "@babel/cli": "^8.0.1",
120
+ "@babel/core": "^8.0.1",
121
+ "@babel/preset-env": "^8.0.2",
122
+ "@babel/preset-react": "^8.0.1",
123
+ "@babel/preset-typescript": "^8.0.1",
123
124
  "@biomejs/biome": "^2.3.8",
124
125
  "@heroicons/react": "^2.2.0",
125
126
  "@modelcontextprotocol/sdk": "^1.0.4",
126
127
  "@playwright/test": "^1.55.1",
127
128
  "@tailwindcss/postcss": "^4.1.13",
128
129
  "@tanstack/react-query": "^5.90.11",
129
- "@types/node": "^25.0.2",
130
+ "@types/node": "^26.0.0",
130
131
  "@vitejs/plugin-react": "^6.0.1",
131
132
  "autoprefixer": "^10.4.21",
132
133
  "babel-plugin-react-compiler": "^1.0.0",
@@ -136,7 +137,7 @@
136
137
  "tailwindcss": "^4.1.13",
137
138
  "tsd": "^0.33.0",
138
139
  "tui-driver": "github:vizzly-testing/tui-driver",
139
- "typescript": "^6.0.3",
140
+ "typescript": "^7.0.2",
140
141
  "vite": "^8.0.0",
141
142
  "wouter": "^3.7.1"
142
143
  }
@@ -84,7 +84,7 @@ For each evidence record:
84
84
  Useful manual drill-downs are:
85
85
 
86
86
  ```bash
87
- vizzly context comparison <comparison-id> --source <local-or-cloud> --json
87
+ vizzly context comparison <comparison-id> --source <local-or-cloud> --agent --json
88
88
  vizzly context screenshot "<screenshot-name>" --source <local-or-cloud> --json
89
89
  vizzly context similar <fingerprint-hash> --source cloud --json
90
90
  vizzly context review-queue --source <local-or-cloud> --json
@@ -94,3 +94,7 @@ Use the source from the evidence you are inspecting in place of
94
94
  `<local-or-cloud>`. `context similar` is cloud-only. Keep missing values
95
95
  unknown, and do not turn metadata into a visual conclusion when the underlying
96
96
  images are unavailable.
97
+
98
+ When a build has more than 10 actionable records, run its returned next-page
99
+ command. The command uses `--offset` to preserve API order without pulling the
100
+ full build context into the agent handoff.
@@ -1,13 +0,0 @@
1
- /**
2
- * Config Module - Public exports
3
- *
4
- * Provides functional configuration primitives:
5
- * - core.js: Pure functions for merging, serialization, validation results
6
- * - operations.js: Config operations with dependency injection
7
- */
8
-
9
- // Core pure functions
10
- export { buildGlobalConfigResult, buildMergedConfig, buildMergedConfigResult, buildProjectConfigResult, buildValidationFailure, buildValidationSuccess, CONFIG_DEFAULTS, deepMerge, extractCosmiconfigResult, extractEnvOverrides, getConfigFormat, READ_SCOPES, serializeConfig, serializeToJavaScript, serializeToJson, stringifyWithIndent, validateReadScope, validateWriteScope, WRITE_SCOPES } from './core.js';
11
-
12
- // Config operations (take dependencies as parameters)
13
- export { getConfig, getConfigSource, getGlobalConfig, getMergedConfig, getProjectConfig, updateConfig, updateGlobalConfig, updateProjectConfig, validateConfig, writeProjectConfigFile } from './operations.js';
@@ -1,13 +0,0 @@
1
- /**
2
- * Project Module - Public exports
3
- *
4
- * Provides functional project management primitives:
5
- * - core.js: Pure functions for validation, URL building, response extraction
6
- * - operations.js: Project operations with dependency injection
7
- */
8
-
9
- // Core pure functions
10
- export { buildBuildsQueryParams, buildBuildsUrl, buildMappingResult, buildNoApiServiceError, buildNoAuthError, buildOrgHeader, buildProjectFetchError, buildProjectUrl, buildTokenCreateError, buildTokenRevokeError, buildTokensFetchError, buildTokensUrl, enrichProjectsWithOrg, extractBuilds, extractOrganizations, extractProject, extractProjects, extractToken, extractTokens, mappingsToArray, validateDirectory, validateProjectData } from './core.js';
11
-
12
- // Project operations (take dependencies as parameters)
13
- export { createMapping, createProjectToken, getMapping, getProject, getProjectWithApiToken, getProjectWithOAuth, getRecentBuilds, getRecentBuildsWithApiToken, getRecentBuildsWithOAuth, listMappings, listProjects, listProjectsWithApiToken, listProjectsWithOAuth, listProjectTokens, removeMapping, revokeProjectToken, switchProject } from './operations.js';
package/dist/tdd/index.js DELETED
@@ -1,19 +0,0 @@
1
- /**
2
- * TDD Module Exports
3
- *
4
- * Re-exports all TDD functionality for clean imports.
5
- */
6
-
7
- export { calculateHotspotCoverage, shouldFilterAsHotspot } from './core/hotspot-coverage.js';
8
- // Core pure functions
9
- export { generateBaselineFilename, generateComparisonId, generateScreenshotSignature } from './core/signature.js';
10
-
11
- // Metadata I/O
12
- export { createEmptyBaselineMetadata, findScreenshotBySignature, loadBaselineMetadata, saveBaselineMetadata, upsertScreenshotInMetadata } from './metadata/baseline-metadata.js';
13
- export { createHotspotCache, getHotspotForScreenshot, loadHotspotMetadata, saveHotspotMetadata } from './metadata/hotspot-metadata.js';
14
- export { baselineMatchesSha, buildBaselineMetadataEntry, downloadBaselineImage, downloadBaselinesInBatches } from './services/baseline-downloader.js';
15
- // Services
16
- export { baselineExists, clearBaselineData, getBaselinePath, getCurrentPath, getDiffPath, initializeDirectories, promoteCurrentToBaseline, readBaseline, readCurrent, saveBaseline, saveCurrent } from './services/baseline-manager.js';
17
- export { buildErrorComparison, buildFailedComparison, buildNewComparison, buildPassedComparison, compareImages, isDimensionMismatchError } from './services/comparison-service.js';
18
- export { downloadHotspots } from './services/hotspot-service.js';
19
- export { buildResults, calculateSummary, findComparison, findComparisonById, getErrorComparisons, getFailedComparisons, getNewComparisons, isSuccessful } from './services/result-service.js';
@@ -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
- }