@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.
- package/README.md +7 -4
- package/dist/api/client.js +20 -5
- package/dist/api/core.js +17 -3
- package/dist/cli.js +11 -51
- package/dist/commands/baselines.js +0 -7
- package/dist/commands/config-cmd.js +0 -7
- package/dist/commands/context.js +248 -306
- package/dist/commands/doctor.js +0 -8
- package/dist/commands/init.js +7 -12
- package/dist/commands/login.js +0 -13
- package/dist/commands/logout.js +0 -12
- package/dist/commands/orgs.js +0 -9
- package/dist/commands/whoami.js +0 -12
- package/dist/context/local-workspace-provider.js +276 -30
- package/dist/plugin-loader.js +1 -1
- package/dist/reporter/reporter-bundle.css +1 -1
- package/dist/reporter/reporter-bundle.iife.js +17 -17
- package/dist/server/routers/events.js +4 -4
- package/dist/utils/visual-context-normalizers.js +12 -3
- package/package.json +11 -10
- package/skills/vizzly/references/cli-context.md +5 -1
- package/dist/config/index.js +0 -13
- package/dist/project/index.js +0 -13
- package/dist/tdd/index.js +0 -19
- package/dist/tdd/services/baseline-downloader.js +0 -151
- package/dist/tdd/services/hotspot-service.js +0 -58
- package/dist/utils/environment.js +0 -109
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
149
|
-
* first-pass diagnosis.
|
|
150
|
-
*
|
|
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.
|
|
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": "
|
|
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.
|
|
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": "^
|
|
119
|
-
"@babel/core": "^
|
|
120
|
-
"@babel/preset-env": "^
|
|
121
|
-
"@babel/preset-react": "^
|
|
122
|
-
"@babel/preset-typescript": "^
|
|
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": "^
|
|
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": "^
|
|
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.
|
package/dist/config/index.js
DELETED
|
@@ -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';
|
package/dist/project/index.js
DELETED
|
@@ -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
|
-
}
|