@vizzly-testing/cli 0.39.1 → 0.39.2
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 +8 -0
- package/bin/vizzly.js +1 -1
- package/dist/{cli.js → adapters/cli/cli.js} +41 -35
- package/dist/adapters/cli/setup.js +34 -0
- package/dist/{client → adapters/client}/index.js +6 -7
- package/dist/adapters/http/cloud-proxy.js +29 -0
- package/dist/{server → adapters/http}/http-server.js +12 -13
- package/dist/{server-manager/operations.js → adapters/http/server-lifecycle.js} +10 -13
- package/dist/{server-manager/index.js → adapters/http/server-manager.js} +23 -27
- package/dist/{plugin-api.js → adapters/plugins/plugin-api.js} +1 -1
- package/dist/{plugin-loader.js → adapters/plugins/plugin-loader.js} +1 -1
- package/dist/{services/index.js → adapters/plugins/services.js} +4 -9
- package/dist/adapters/sdk/index.d.ts +89 -0
- package/dist/{sdk → adapters/sdk}/index.js +22 -19
- package/dist/{commands/api.js → domains/api/api.commands.js} +3 -3
- package/dist/{auth/client.js → domains/auth/auth-client.js} +4 -3
- package/dist/domains/auth/auth-errors.js +50 -0
- package/dist/domains/auth/auth-headers.js +41 -0
- package/dist/domains/auth/auth.normalizers.js +50 -0
- package/dist/{server/routers/auth.js → domains/auth/auth.routes.js} +9 -9
- package/dist/domains/auth/auth.rules.js +19 -0
- package/dist/domains/auth/auth.serializers.js +32 -0
- package/dist/{auth/operations.js → domains/auth/auth.service.js} +115 -17
- package/dist/domains/auth/auth.validations.js +21 -0
- package/dist/{api/client.js → domains/auth/cloud-client.js} +11 -18
- package/dist/{commands/login.js → domains/auth/login.commands.js} +11 -8
- package/dist/{commands/logout.js → domains/auth/logout.commands.js} +6 -4
- package/dist/domains/auth/token-store.js +81 -0
- package/dist/{commands/whoami.js → domains/auth/whoami.commands.js} +6 -3
- package/dist/domains/baselines/baseline-download.js +192 -0
- package/dist/domains/baselines/baseline-write.js +22 -0
- package/dist/{server/routers/baseline.js → domains/baselines/baseline.routes.js} +3 -3
- package/dist/{tdd/services/baseline-manager.js → domains/baselines/baseline.service.js} +1 -39
- package/dist/{commands/baselines.js → domains/baselines/baselines.commands.js} +3 -3
- package/dist/domains/baselines/cloud-baselines.js +9 -0
- package/dist/domains/baselines/cloud-baselines.routes.js +42 -0
- package/dist/domains/builds/build.normalizers.js +35 -0
- package/dist/domains/builds/build.serializers.js +164 -0
- package/dist/domains/builds/build.service.js +119 -0
- package/dist/domains/builds/build.validations.js +39 -0
- package/dist/{commands/builds.js → domains/builds/builds.commands.js} +6 -74
- package/dist/domains/builds/cloud-builds.routes.js +31 -0
- package/dist/{commands/finalize.js → domains/builds/finalize.commands.js} +5 -17
- package/dist/{commands/status.js → domains/builds/status.commands.js} +9 -120
- package/dist/domains/comparisons/cloud-comparisons.js +43 -0
- package/dist/{tdd/services/comparison-service.js → domains/comparisons/comparison.service.js} +1 -1
- package/dist/{commands/comparisons.js → domains/comparisons/comparisons.commands.js} +7 -5
- package/dist/domains/config/config-defaults.js +27 -0
- package/dist/{utils → domains/config}/config-discovery.js +1 -9
- package/dist/{utils → domains/config}/config-loader.js +10 -9
- package/dist/{commands/config-cmd.js → domains/config/config.commands.js} +4 -4
- package/dist/{types → domains/config}/config.d.ts +3 -3
- package/dist/domains/config/config.normalizers.js +126 -0
- package/dist/{server/routers/config.js → domains/config/config.routes.js} +13 -13
- package/dist/domains/config/config.rules.js +47 -0
- package/dist/domains/config/config.serializers.js +202 -0
- package/dist/domains/config/config.service.js +625 -0
- package/dist/{utils/config-schema.js → domains/config/config.validations.js} +9 -9
- package/dist/{utils/environment-config.js → domains/config/environment.js} +1 -8
- package/dist/domains/context/compact-context.validations.js +60 -0
- package/dist/domains/context/context-presentation.js +368 -0
- package/dist/domains/context/context.commands.js +385 -0
- package/dist/domains/context/context.serializers.js +157 -0
- package/dist/domains/context/context.service.js +76 -0
- package/dist/domains/context/context.validations.js +100 -0
- package/dist/{context → domains/context}/local-workspace-provider.js +1 -1
- package/dist/{commands/doctor.js → domains/diagnostics/doctor.commands.js} +6 -5
- package/dist/{server/routers/health.js → domains/diagnostics/health.routes.js} +11 -20
- package/dist/{commands/preview.js → domains/previews/preview.commands.js} +9 -7
- package/dist/domains/previews/preview.service.js +37 -0
- package/dist/domains/projects/cloud-projects.routes.js +28 -0
- package/dist/{commands/orgs.js → domains/projects/orgs.commands.js} +6 -8
- package/dist/{utils → domains/projects}/project-binding.js +3 -3
- package/dist/domains/projects/project-errors.js +61 -0
- package/dist/{utils → domains/projects}/project-link-store.js +2 -2
- package/dist/domains/projects/project-requests.js +61 -0
- package/dist/{commands/project.js → domains/projects/project.commands.js} +9 -41
- package/dist/domains/projects/project.normalizers.js +69 -0
- package/dist/{project/operations.js → domains/projects/project.service.js} +89 -16
- package/dist/domains/projects/project.validations.js +31 -0
- package/dist/{commands/projects.js → domains/projects/projects.commands.js} +6 -8
- package/dist/{server/routers/projects.js → domains/projects/projects.routes.js} +5 -5
- package/dist/domains/reports/dashboard.routes.js +96 -0
- package/dist/{server/routers/events.js → domains/reports/events.routes.js} +14 -36
- package/dist/domains/reports/report-assets.routes.js +81 -0
- package/dist/domains/reports/report-state.js +51 -0
- package/dist/domains/reports/report-store.js +85 -0
- package/dist/domains/reports/report.serializers.js +99 -0
- package/dist/{services/static-report-generator.js → domains/reports/report.service.js} +11 -33
- package/dist/{commands/review.js → domains/review/review.commands.js} +4 -4
- package/dist/domains/runs/run-environment.js +69 -0
- package/dist/{services/test-runner.js → domains/runs/run-events.js} +11 -9
- package/dist/{commands/run.js → domains/runs/run.commands.js} +16 -13
- package/dist/domains/runs/run.normalizers.js +8 -0
- package/dist/domains/runs/run.rules.js +56 -0
- package/dist/domains/runs/run.serializers.js +101 -0
- package/dist/{test-runner/operations.js → domains/runs/run.service.js} +35 -7
- package/dist/domains/runs/run.validations.js +17 -0
- package/dist/{utils/session.js → domains/runs/session-store.js} +3 -2
- package/dist/{server/handlers/api-handler.js → domains/screenshots/cloud-handler.js} +4 -4
- package/dist/domains/screenshots/cloud-screenshots.js +109 -0
- package/dist/{utils/file-helpers.js → domains/screenshots/image-files.js} +1 -1
- package/dist/domains/screenshots/screenshot-hash.js +53 -0
- package/dist/domains/screenshots/screenshot-server-options.js +28 -0
- package/dist/{screenshot-server/operations.js → domains/screenshots/screenshot-server.js} +14 -1
- package/dist/domains/screenshots/screenshot.normalizers.js +40 -0
- package/dist/{server/routers/screenshot.js → domains/screenshots/screenshot.routes.js} +4 -4
- package/dist/domains/screenshots/screenshot.serializers.js +146 -0
- package/dist/{utils/security.js → domains/screenshots/screenshot.validations.js} +20 -70
- package/dist/{init/agents.js → domains/setup/agent-setup.js} +4 -5
- package/dist/{commands/init.js → domains/setup/init.commands.js} +10 -41
- package/dist/{server/handlers/tdd-handler.js → domains/tdd/screenshot-handler.js} +46 -281
- package/dist/{commands/tdd-daemon.js → domains/tdd/tdd-daemon.commands.js} +6 -6
- package/dist/{commands/tdd-screenshots.js → domains/tdd/tdd-screenshots.commands.js} +5 -5
- package/dist/{commands/tdd.js → domains/tdd/tdd.commands.js} +16 -15
- package/dist/{tdd/tdd-service.js → domains/tdd/tdd.service.js} +81 -405
- package/dist/{utils → domains/updates}/cli-update.js +4 -6
- package/dist/{utils → domains/updates}/update-notifications.js +7 -7
- package/dist/domains/uploads/upload-defaults.js +4 -0
- package/dist/domains/uploads/upload-files.js +32 -0
- package/dist/domains/uploads/upload-settings.js +21 -0
- package/dist/domains/uploads/upload-timing.js +18 -0
- package/dist/{commands/upload.js → domains/uploads/upload.commands.js} +11 -9
- package/dist/domains/uploads/upload.normalizers.js +60 -0
- package/dist/domains/uploads/upload.serializers.js +179 -0
- package/dist/{uploader/operations.js → domains/uploads/upload.service.js} +85 -28
- package/dist/domains/uploads/upload.validations.js +77 -0
- package/dist/index.js +12 -12
- package/dist/reporter/reporter-bundle.iife.js +86 -86
- package/dist/reporter-ssr/ssr-entry.js +2 -2
- package/dist/shared/cloud-protocol.js +171 -0
- package/dist/{utils → shared}/file-changes.js +1 -1
- package/dist/shared/files.js +9 -0
- package/dist/{utils → shared}/global-config.js +7 -78
- package/dist/shared/log-level.js +7 -0
- package/dist/{utils → shared}/output.js +1 -1
- package/dist/{utils → shared}/package-info.js +5 -4
- package/dist/shared/paths.js +57 -0
- package/package.json +23 -22
- package/dist/api/core.js +0 -382
- package/dist/api/endpoints.js +0 -421
- package/dist/api/index.js +0 -19
- package/dist/auth/core.js +0 -237
- package/dist/auth/index.js +0 -31
- package/dist/commands/context.js +0 -1066
- package/dist/config/core.js +0 -439
- package/dist/config/operations.js +0 -327
- package/dist/project/core.js +0 -211
- package/dist/screenshot-server/core.js +0 -161
- package/dist/screenshot-server/index.js +0 -11
- package/dist/server/routers/assets.js +0 -95
- package/dist/server/routers/cloud-proxy.js +0 -146
- package/dist/server/routers/dashboard.js +0 -172
- package/dist/services/auth-service.js +0 -117
- package/dist/services/config-service.js +0 -313
- package/dist/services/project-service.js +0 -88
- package/dist/test-runner/core.js +0 -293
- package/dist/test-runner/index.js +0 -13
- package/dist/types/sdk.d.ts +0 -186
- package/dist/uploader/core.js +0 -432
- package/dist/uploader/index.js +0 -88
- /package/dist/{types/client.d.ts → adapters/client/index.d.ts} +0 -0
- /package/dist/{server → adapters/http}/middleware/cors.js +0 -0
- /package/dist/{server → adapters/http}/middleware/json-parser.js +0 -0
- /package/dist/{server → adapters/http}/middleware/response.js +0 -0
- /package/dist/{server-manager/core.js → adapters/http/server-options.js} +0 -0
- /package/dist/{utils/login-poll-wait.js → domains/auth/login-wait.js} +0 -0
- /package/dist/{tdd/metadata → domains/baselines}/baseline-metadata.js +0 -0
- /package/dist/{services/build-manager.js → domains/builds/build-info.js} +0 -0
- /package/dist/{tdd/services/result-service.js → domains/comparisons/comparison-results.js} +0 -0
- /package/dist/{init/core.js → domains/config/config-plan.js} +0 -0
- /package/dist/{utils/config-helpers.js → domains/config/config.js} +0 -0
- /package/dist/{utils/visual-context-normalizers.js → domains/context/context.normalizers.js} +0 -0
- /package/dist/{context → domains/context}/provider-resolver.js +0 -0
- /package/dist/{utils/context.js → domains/diagnostics/environment-context.js} +0 -0
- /package/dist/{utils/report-data.js → domains/reports/report.normalizers.js} +0 -0
- /package/dist/{utils/image-input-detector.js → domains/screenshots/image-input.js} +0 -0
- /package/dist/{tdd/core/signature.js → domains/screenshots/screenshot-identity.js} +0 -0
- /package/dist/{utils → domains/screenshots}/screenshot-options.js +0 -0
- /package/dist/{init/prompts.js → domains/setup/setup-prompts.js} +0 -0
- /package/dist/{tdd → domains/tdd}/runtime-context.js +0 -0
- /package/dist/{tdd → domains/tdd}/server-registry.js +0 -0
- /package/dist/{commands/tdd-daemon-process.js → domains/tdd/tdd-daemon-process.commands.js} +0 -0
- /package/dist/{utils → domains/updates}/update-check.js +0 -0
- /package/dist/{types/index.d.ts → index.d.ts} +0 -0
- /package/dist/{utils → shared}/api-url.js +0 -0
- /package/dist/{utils → shared}/async-utils.js +0 -0
- /package/dist/{utils → shared}/browser.js +0 -0
- /package/dist/{utils → shared}/ci-env.js +0 -0
- /package/dist/{utils → shared}/colors.js +0 -0
- /package/dist/{utils → shared}/fetch-utils.js +0 -0
- /package/dist/{utils → shared}/git.js +0 -0
- /package/dist/{utils → shared}/patterns.js +0 -0
- /package/dist/{utils → shared}/secret-store.js +0 -0
- /package/dist/{errors → shared}/vizzly-error.js +0 -0
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
/** Context command orchestration and provider selection. */
|
|
2
|
+
|
|
3
|
+
import * as defaultOutput from '../../shared/output.js';
|
|
4
|
+
import { createApiClient as defaultCreateApiClient } from '../auth/cloud-client.js';
|
|
5
|
+
import { loadConfig as defaultLoadConfig } from '../config/config-loader.js';
|
|
6
|
+
import { readSession as defaultReadSession } from '../runs/session-store.js';
|
|
7
|
+
import { validateCompactContext, validateCompactOutputSize } from './compact-context.validations.js';
|
|
8
|
+
import { buildCompactBuildPayload, buildCompactComparisonPayload, buildCompactContextRequest, buildScopeQuery } from './context.serializers.js';
|
|
9
|
+
import { getBuildContext as defaultGetBuildContext, getComparisonContext as defaultGetComparisonContext, getReviewQueueContext as defaultGetReviewQueueContext, getScreenshotContext as defaultGetScreenshotContext, getSimilarFingerprintContext as defaultGetSimilarFingerprintContext } from './context.service.js';
|
|
10
|
+
import { displayBuildContext, displayCompactBuildContext, displayCompactComparisonContext, displayComparisonContext, displayFingerprintContext, displayReviewQueueContext, displayScreenshotContext } from './context-presentation.js';
|
|
11
|
+
import { createLocalWorkspaceContextProvider as defaultCreateLocalWorkspaceContextProvider } from './local-workspace-provider.js';
|
|
12
|
+
import { resolveContextSource as defaultResolveContextSource } from './provider-resolver.js';
|
|
13
|
+
function buildAuthErrorMessage() {
|
|
14
|
+
return 'Authentication required. Use --token, set VIZZLY_TOKEN, run "vizzly login", or link a project.';
|
|
15
|
+
}
|
|
16
|
+
function hasExplicitCloudScope(options = {}, config = {}) {
|
|
17
|
+
return Boolean(options.org || options.project || config.linkedProject?.organizationSlug || config.linkedProject?.projectSlug);
|
|
18
|
+
}
|
|
19
|
+
function createClient(config, createApiClient) {
|
|
20
|
+
return createApiClient({
|
|
21
|
+
baseUrl: config.apiUrl,
|
|
22
|
+
token: config.apiKey || config.userToken,
|
|
23
|
+
command: 'context'
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Attach the provider that produced a context payload.
|
|
29
|
+
*
|
|
30
|
+
* Context endpoints return API-owned evidence, while source selection belongs
|
|
31
|
+
* to the CLI. Keeping both in one payload prevents follow-up agent commands
|
|
32
|
+
* from silently crossing between cloud and local evidence.
|
|
33
|
+
*
|
|
34
|
+
* @param {Object} context - Context payload returned by a provider.
|
|
35
|
+
* @param {string} source - Provider source selected by the CLI.
|
|
36
|
+
* @returns {Object} Context payload with explicit source provenance.
|
|
37
|
+
*/
|
|
38
|
+
function attachContextSource(context, source) {
|
|
39
|
+
return {
|
|
40
|
+
...context,
|
|
41
|
+
source
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
async function loadContextConfig(globalOptions, options, deps) {
|
|
45
|
+
let {
|
|
46
|
+
loadConfig = defaultLoadConfig,
|
|
47
|
+
requireApiKey = true,
|
|
48
|
+
output = defaultOutput,
|
|
49
|
+
exit = code => process.exit(code)
|
|
50
|
+
} = deps;
|
|
51
|
+
let allOptions = {
|
|
52
|
+
...globalOptions,
|
|
53
|
+
...options
|
|
54
|
+
};
|
|
55
|
+
let config = await loadConfig(globalOptions.config, allOptions);
|
|
56
|
+
if (requireApiKey && !config.apiKey && !config.userToken) {
|
|
57
|
+
output.error(buildAuthErrorMessage());
|
|
58
|
+
output.cleanup();
|
|
59
|
+
exit(1);
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
return config;
|
|
63
|
+
}
|
|
64
|
+
function createCloudContextProvider(config, deps = {}) {
|
|
65
|
+
let {
|
|
66
|
+
createApiClient = defaultCreateApiClient,
|
|
67
|
+
getBuildContext = defaultGetBuildContext,
|
|
68
|
+
getComparisonContext = defaultGetComparisonContext,
|
|
69
|
+
getScreenshotContext = defaultGetScreenshotContext,
|
|
70
|
+
getSimilarFingerprintContext = defaultGetSimilarFingerprintContext,
|
|
71
|
+
getReviewQueueContext = defaultGetReviewQueueContext
|
|
72
|
+
} = deps;
|
|
73
|
+
let client = createClient(config, createApiClient);
|
|
74
|
+
return {
|
|
75
|
+
source: 'cloud',
|
|
76
|
+
async getBuildContext(buildId, query) {
|
|
77
|
+
return attachContextSource(await getBuildContext(client, buildId, query), 'cloud');
|
|
78
|
+
},
|
|
79
|
+
async getComparisonContext(comparisonId, query) {
|
|
80
|
+
return attachContextSource(await getComparisonContext(client, comparisonId, query), 'cloud');
|
|
81
|
+
},
|
|
82
|
+
async getScreenshotContext(screenshotName, query) {
|
|
83
|
+
return attachContextSource(await getScreenshotContext(client, screenshotName, query), 'cloud');
|
|
84
|
+
},
|
|
85
|
+
async getSimilarFingerprintContext(fingerprintHash, query) {
|
|
86
|
+
return attachContextSource(await getSimilarFingerprintContext(client, fingerprintHash, query), 'cloud');
|
|
87
|
+
},
|
|
88
|
+
async getReviewQueueContext(query) {
|
|
89
|
+
return attachContextSource(await getReviewQueueContext(client, query), 'cloud');
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function buildLocalFingerprintCapabilityError() {
|
|
94
|
+
let error = new Error('Local workspace context does not support fingerprint similarity yet. Use --source cloud for this query.');
|
|
95
|
+
error.code = 'LOCAL_WORKSPACE_CONTEXT';
|
|
96
|
+
return error;
|
|
97
|
+
}
|
|
98
|
+
function resolveBuildContextId(buildId, runtime, deps = {}) {
|
|
99
|
+
let {
|
|
100
|
+
readSession = defaultReadSession
|
|
101
|
+
} = deps;
|
|
102
|
+
if (buildId !== 'current' || runtime.source !== 'cloud') {
|
|
103
|
+
return buildId;
|
|
104
|
+
}
|
|
105
|
+
let session = readSession({
|
|
106
|
+
cwd: runtime.projectRoot
|
|
107
|
+
});
|
|
108
|
+
if (session?.buildId && !session.expired) {
|
|
109
|
+
return session.buildId;
|
|
110
|
+
}
|
|
111
|
+
let error = new Error('No current cloud build found. Run "vizzly run" first, or pass a build ID.');
|
|
112
|
+
error.code = 'NO_CURRENT_CLOUD_BUILD';
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
function shouldExplainLocalSimilarityGap(requestedSource, command, localProvider) {
|
|
116
|
+
return requestedSource === 'auto' && command === 'similar' && localProvider.isAvailable();
|
|
117
|
+
}
|
|
118
|
+
async function loadContextRuntime(command, target, globalOptions, options, deps = {}) {
|
|
119
|
+
let {
|
|
120
|
+
createLocalWorkspaceContextProvider = defaultCreateLocalWorkspaceContextProvider,
|
|
121
|
+
resolveContextSource = defaultResolveContextSource,
|
|
122
|
+
output = defaultOutput,
|
|
123
|
+
exit = code => process.exit(code)
|
|
124
|
+
} = deps;
|
|
125
|
+
let config = await loadContextConfig(globalOptions, options, {
|
|
126
|
+
...deps,
|
|
127
|
+
output,
|
|
128
|
+
exit,
|
|
129
|
+
requireApiKey: false
|
|
130
|
+
});
|
|
131
|
+
let requestedSource = options.source || 'auto';
|
|
132
|
+
let projectRoot = deps.projectRoot || process.cwd();
|
|
133
|
+
let localProvider = createLocalWorkspaceContextProvider({
|
|
134
|
+
projectRoot
|
|
135
|
+
});
|
|
136
|
+
let source = resolveContextSource({
|
|
137
|
+
requestedSource,
|
|
138
|
+
command,
|
|
139
|
+
target,
|
|
140
|
+
projectRoot,
|
|
141
|
+
hasCloudScope: hasExplicitCloudScope(options, config)
|
|
142
|
+
}, {
|
|
143
|
+
createLocalWorkspaceContextProvider
|
|
144
|
+
});
|
|
145
|
+
if (source === 'cloud' && !config.apiKey && !config.userToken) {
|
|
146
|
+
if (shouldExplainLocalSimilarityGap(requestedSource, command, localProvider)) {
|
|
147
|
+
throw buildLocalFingerprintCapabilityError();
|
|
148
|
+
}
|
|
149
|
+
output.error(buildAuthErrorMessage());
|
|
150
|
+
output.cleanup();
|
|
151
|
+
exit(1);
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
let provider = source === 'local' ? localProvider : createCloudContextProvider(config, deps);
|
|
155
|
+
return {
|
|
156
|
+
config,
|
|
157
|
+
source,
|
|
158
|
+
provider
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
export async function contextBuildCommand(buildId, options = {}, globalOptions = {}, deps = {}) {
|
|
162
|
+
let {
|
|
163
|
+
output = defaultOutput,
|
|
164
|
+
exit = code => process.exit(code)
|
|
165
|
+
} = deps;
|
|
166
|
+
output.configure({
|
|
167
|
+
json: globalOptions.json,
|
|
168
|
+
verbose: globalOptions.verbose,
|
|
169
|
+
color: !globalOptions.noColor
|
|
170
|
+
});
|
|
171
|
+
try {
|
|
172
|
+
let runtime = await loadContextRuntime('build', buildId, globalOptions, options, {
|
|
173
|
+
...deps,
|
|
174
|
+
output,
|
|
175
|
+
exit
|
|
176
|
+
});
|
|
177
|
+
if (!runtime) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
let resolvedBuildId = resolveBuildContextId(buildId, runtime, deps);
|
|
181
|
+
let {
|
|
182
|
+
compact,
|
|
183
|
+
details,
|
|
184
|
+
include,
|
|
185
|
+
query
|
|
186
|
+
} = buildCompactContextRequest(options, globalOptions);
|
|
187
|
+
output.startSpinner('Fetching build context...');
|
|
188
|
+
let context = await runtime.provider.getBuildContext(resolvedBuildId, query);
|
|
189
|
+
output.stopSpinner();
|
|
190
|
+
if (compact) {
|
|
191
|
+
let compactContext = validateCompactContext(context, 'build');
|
|
192
|
+
let payload = validateCompactOutputSize(buildCompactBuildPayload(compactContext, include, options.cursor), 'build', details);
|
|
193
|
+
if (globalOptions.json) {
|
|
194
|
+
output.data(payload);
|
|
195
|
+
} else {
|
|
196
|
+
displayCompactBuildContext(output, payload);
|
|
197
|
+
}
|
|
198
|
+
output.cleanup();
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (globalOptions.json) {
|
|
202
|
+
output.data(context);
|
|
203
|
+
output.cleanup();
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
displayBuildContext(output, context);
|
|
207
|
+
output.cleanup();
|
|
208
|
+
} catch (error) {
|
|
209
|
+
output.stopSpinner();
|
|
210
|
+
output.error('Failed to fetch build context', error);
|
|
211
|
+
output.cleanup();
|
|
212
|
+
exit(1);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
export async function contextComparisonCommand(comparisonId, options = {}, globalOptions = {}, deps = {}) {
|
|
216
|
+
let {
|
|
217
|
+
output = defaultOutput,
|
|
218
|
+
exit = code => process.exit(code)
|
|
219
|
+
} = deps;
|
|
220
|
+
output.configure({
|
|
221
|
+
json: globalOptions.json,
|
|
222
|
+
verbose: globalOptions.verbose,
|
|
223
|
+
color: !globalOptions.noColor
|
|
224
|
+
});
|
|
225
|
+
try {
|
|
226
|
+
let runtime = await loadContextRuntime('comparison', comparisonId, globalOptions, options, {
|
|
227
|
+
...deps,
|
|
228
|
+
output,
|
|
229
|
+
exit
|
|
230
|
+
});
|
|
231
|
+
if (!runtime) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
let {
|
|
235
|
+
compact,
|
|
236
|
+
details,
|
|
237
|
+
include,
|
|
238
|
+
query
|
|
239
|
+
} = buildCompactContextRequest(options, globalOptions);
|
|
240
|
+
output.startSpinner('Fetching comparison context...');
|
|
241
|
+
let context = await runtime.provider.getComparisonContext(comparisonId, query);
|
|
242
|
+
output.stopSpinner();
|
|
243
|
+
if (compact) {
|
|
244
|
+
let compactContext = validateCompactContext(context, 'comparison');
|
|
245
|
+
let payload = validateCompactOutputSize(buildCompactComparisonPayload(compactContext, include), 'comparison', details);
|
|
246
|
+
if (globalOptions.json) {
|
|
247
|
+
output.data(payload);
|
|
248
|
+
} else {
|
|
249
|
+
displayCompactComparisonContext(output, payload);
|
|
250
|
+
}
|
|
251
|
+
output.cleanup();
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (globalOptions.json) {
|
|
255
|
+
output.data(context);
|
|
256
|
+
output.cleanup();
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
displayComparisonContext(output, context);
|
|
260
|
+
output.cleanup();
|
|
261
|
+
} catch (error) {
|
|
262
|
+
output.stopSpinner();
|
|
263
|
+
output.error('Failed to fetch comparison context', error);
|
|
264
|
+
output.cleanup();
|
|
265
|
+
exit(1);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
export async function contextScreenshotCommand(screenshotName, options = {}, globalOptions = {}, deps = {}) {
|
|
269
|
+
let {
|
|
270
|
+
output = defaultOutput,
|
|
271
|
+
exit = code => process.exit(code)
|
|
272
|
+
} = deps;
|
|
273
|
+
output.configure({
|
|
274
|
+
json: globalOptions.json,
|
|
275
|
+
verbose: globalOptions.verbose,
|
|
276
|
+
color: !globalOptions.noColor
|
|
277
|
+
});
|
|
278
|
+
try {
|
|
279
|
+
let runtime = await loadContextRuntime('screenshot', screenshotName, globalOptions, options, {
|
|
280
|
+
...deps,
|
|
281
|
+
output,
|
|
282
|
+
exit
|
|
283
|
+
});
|
|
284
|
+
if (!runtime) {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
let query = buildScopeQuery(options, {
|
|
288
|
+
recentLimit: options.recentLimit
|
|
289
|
+
});
|
|
290
|
+
output.startSpinner('Fetching screenshot context...');
|
|
291
|
+
let context = await runtime.provider.getScreenshotContext(screenshotName, query);
|
|
292
|
+
output.stopSpinner();
|
|
293
|
+
if (globalOptions.json) {
|
|
294
|
+
output.data(context);
|
|
295
|
+
output.cleanup();
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
displayScreenshotContext(output, context);
|
|
299
|
+
output.cleanup();
|
|
300
|
+
} catch (error) {
|
|
301
|
+
output.stopSpinner();
|
|
302
|
+
output.error('Failed to fetch screenshot context', error);
|
|
303
|
+
output.cleanup();
|
|
304
|
+
exit(1);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
export async function contextSimilarCommand(fingerprintHash, options = {}, globalOptions = {}, deps = {}) {
|
|
308
|
+
let {
|
|
309
|
+
output = defaultOutput,
|
|
310
|
+
exit = code => process.exit(code)
|
|
311
|
+
} = deps;
|
|
312
|
+
output.configure({
|
|
313
|
+
json: globalOptions.json,
|
|
314
|
+
verbose: globalOptions.verbose,
|
|
315
|
+
color: !globalOptions.noColor
|
|
316
|
+
});
|
|
317
|
+
try {
|
|
318
|
+
let runtime = await loadContextRuntime('similar', fingerprintHash, globalOptions, options, {
|
|
319
|
+
...deps,
|
|
320
|
+
output,
|
|
321
|
+
exit
|
|
322
|
+
});
|
|
323
|
+
if (!runtime) {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
let query = buildScopeQuery(options, {
|
|
327
|
+
limit: options.limit
|
|
328
|
+
});
|
|
329
|
+
output.startSpinner('Fetching similar visual context...');
|
|
330
|
+
let context = await runtime.provider.getSimilarFingerprintContext(fingerprintHash, query);
|
|
331
|
+
output.stopSpinner();
|
|
332
|
+
if (globalOptions.json) {
|
|
333
|
+
output.data(context);
|
|
334
|
+
output.cleanup();
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
displayFingerprintContext(output, context);
|
|
338
|
+
output.cleanup();
|
|
339
|
+
} catch (error) {
|
|
340
|
+
output.stopSpinner();
|
|
341
|
+
output.error('Failed to fetch similar visual context', error);
|
|
342
|
+
output.cleanup();
|
|
343
|
+
exit(1);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
export async function contextReviewQueueCommand(options = {}, globalOptions = {}, deps = {}) {
|
|
347
|
+
let {
|
|
348
|
+
output = defaultOutput,
|
|
349
|
+
exit = code => process.exit(code)
|
|
350
|
+
} = deps;
|
|
351
|
+
output.configure({
|
|
352
|
+
json: globalOptions.json,
|
|
353
|
+
verbose: globalOptions.verbose,
|
|
354
|
+
color: !globalOptions.noColor
|
|
355
|
+
});
|
|
356
|
+
try {
|
|
357
|
+
let runtime = await loadContextRuntime('review-queue', null, globalOptions, options, {
|
|
358
|
+
...deps,
|
|
359
|
+
output,
|
|
360
|
+
exit
|
|
361
|
+
});
|
|
362
|
+
if (!runtime) {
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
let query = buildScopeQuery(options, {
|
|
366
|
+
limit: options.limit,
|
|
367
|
+
offset: options.offset
|
|
368
|
+
});
|
|
369
|
+
output.startSpinner('Fetching review queue context...');
|
|
370
|
+
let context = await runtime.provider.getReviewQueueContext(query);
|
|
371
|
+
output.stopSpinner();
|
|
372
|
+
if (globalOptions.json) {
|
|
373
|
+
output.data(context);
|
|
374
|
+
output.cleanup();
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
displayReviewQueueContext(output, context);
|
|
378
|
+
output.cleanup();
|
|
379
|
+
} catch (error) {
|
|
380
|
+
output.stopSpinner();
|
|
381
|
+
output.error('Failed to fetch review queue context', error);
|
|
382
|
+
output.cleanup();
|
|
383
|
+
exit(1);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { COMPACT_CONTEXT_LIMIT } from './compact-context.validations.js';
|
|
2
|
+
import { parseIncludeOption } from './context.validations.js';
|
|
3
|
+
export function buildCompactContextRequest(options = {}, globalOptions = {}) {
|
|
4
|
+
let include = parseIncludeOption(options.include);
|
|
5
|
+
let compact = !options.full && (!globalOptions.json || options.agent);
|
|
6
|
+
let details = include.includes('diffs') ? 'diffs' : 'summary';
|
|
7
|
+
return {
|
|
8
|
+
compact,
|
|
9
|
+
details,
|
|
10
|
+
include,
|
|
11
|
+
query: compact ? {
|
|
12
|
+
details,
|
|
13
|
+
limit: COMPACT_CONTEXT_LIMIT,
|
|
14
|
+
cursor: options.cursor
|
|
15
|
+
} : undefined
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export function buildScopeQuery(options = {}, query = {}) {
|
|
19
|
+
let scopedQuery = {
|
|
20
|
+
...query
|
|
21
|
+
};
|
|
22
|
+
if (options.project) {
|
|
23
|
+
scopedQuery.project = options.project;
|
|
24
|
+
}
|
|
25
|
+
if (options.org) {
|
|
26
|
+
scopedQuery.organization = options.org;
|
|
27
|
+
}
|
|
28
|
+
return scopedQuery;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Quote a value only when a suggested command needs shell protection.
|
|
33
|
+
*
|
|
34
|
+
* Suggested commands are meant to be executable, so names with whitespace or
|
|
35
|
+
* apostrophes must survive copy and paste without changing their value.
|
|
36
|
+
*
|
|
37
|
+
* @param {unknown} value - CLI argument value.
|
|
38
|
+
* @returns {string} A shell-safe argument for the displayed command.
|
|
39
|
+
*/
|
|
40
|
+
function quoteCommandArgument(value) {
|
|
41
|
+
let stringValue = String(value);
|
|
42
|
+
if (/^[A-Za-z0-9._:/-]+$/.test(stringValue)) {
|
|
43
|
+
return stringValue;
|
|
44
|
+
}
|
|
45
|
+
return `'${stringValue.replaceAll("'", `'\\''`)}'`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Recognize both local source labels emitted across supported context shapes.
|
|
50
|
+
*
|
|
51
|
+
* @param {Object} context - Normalized context response.
|
|
52
|
+
* @returns {boolean} Whether follow-up commands must stay in local mode.
|
|
53
|
+
*/
|
|
54
|
+
function isLocalContext(context = {}) {
|
|
55
|
+
return ['local', 'local_workspace'].includes(context.source);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Keep executable suggestions on the source that produced their evidence.
|
|
60
|
+
*
|
|
61
|
+
* Mixed workspaces can contain both a cloud run session and persisted local
|
|
62
|
+
* TDD results. Pinning either source prevents a follow-up command from
|
|
63
|
+
* silently crossing that boundary and describing a different build.
|
|
64
|
+
*
|
|
65
|
+
* @param {string} command - Base CLI command.
|
|
66
|
+
* @param {Object} context - Context that produced the command.
|
|
67
|
+
* @returns {string} Command pinned to its originating source.
|
|
68
|
+
*/
|
|
69
|
+
function appendContextSource(command, context = {}) {
|
|
70
|
+
let source = isLocalContext(context) ? 'local' : 'cloud';
|
|
71
|
+
return `${command} --source ${source}`;
|
|
72
|
+
}
|
|
73
|
+
function buildCompactBuildCommands(context = {}, include = [], cursor = null) {
|
|
74
|
+
let evidence = context.evidence?.items || [];
|
|
75
|
+
let page = context.evidence?.page || {};
|
|
76
|
+
let commands = [];
|
|
77
|
+
let buildTarget = isLocalContext(context) ? 'current' : context.build?.id || null;
|
|
78
|
+
let firstComparison = evidence.find(item => item.type === 'comparison' && item.id);
|
|
79
|
+
let firstNamedEvidence = evidence.find(item => item.screenshot_name);
|
|
80
|
+
if (firstComparison) {
|
|
81
|
+
commands.push({
|
|
82
|
+
label: 'Inspect comparison context',
|
|
83
|
+
command: appendContextSource(`vizzly --json context comparison ${quoteCommandArgument(firstComparison.id)} --agent`, context)
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
if (firstNamedEvidence) {
|
|
87
|
+
let screenshotName = firstNamedEvidence.screenshot_name;
|
|
88
|
+
commands.push({
|
|
89
|
+
label: 'Inspect screenshot history',
|
|
90
|
+
command: appendContextSource(`vizzly --json context screenshot ${quoteCommandArgument(screenshotName)}`, context)
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
if (buildTarget && evidence.length > 0 && !include.includes('diffs')) {
|
|
94
|
+
let cursorFlag = cursor ? ` --cursor ${quoteCommandArgument(cursor)}` : '';
|
|
95
|
+
commands.push({
|
|
96
|
+
label: 'Load raw diff diagnostics',
|
|
97
|
+
command: appendContextSource(`vizzly --json context build ${quoteCommandArgument(buildTarget)} --agent --include diffs${cursorFlag}`, context)
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
if (buildTarget && page.has_more && page.next_cursor) {
|
|
101
|
+
let includeFlag = include.length > 0 ? ` --include ${include.join(',')}` : '';
|
|
102
|
+
commands.push({
|
|
103
|
+
label: 'Load next evidence page',
|
|
104
|
+
command: appendContextSource(`vizzly --json context build ${quoteCommandArgument(buildTarget)} --agent --cursor ${quoteCommandArgument(page.next_cursor)}${includeFlag}`, context)
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
if (buildTarget) {
|
|
108
|
+
commands.push({
|
|
109
|
+
label: 'Load full build context',
|
|
110
|
+
command: appendContextSource(`vizzly --json context build ${quoteCommandArgument(buildTarget)} --agent --full`, context)
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
return commands;
|
|
114
|
+
}
|
|
115
|
+
export function buildCompactBuildPayload(context, include = [], cursor = null) {
|
|
116
|
+
return {
|
|
117
|
+
...context,
|
|
118
|
+
resource: 'build_agent_context',
|
|
119
|
+
suggested_commands: buildCompactBuildCommands(context, include, cursor)
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function buildCompactComparisonCommands(context = {}, include = []) {
|
|
123
|
+
let commands = [];
|
|
124
|
+
let comparisonId = context.comparison?.id;
|
|
125
|
+
let streams = [['similar_by_fingerprint', 'similar history'], ['recent_by_name', 'recent history']];
|
|
126
|
+
let includeFlag = include.length > 0 ? ` --include ${include.join(',')}` : '';
|
|
127
|
+
if (comparisonId && !include.includes('diffs')) {
|
|
128
|
+
commands.push({
|
|
129
|
+
label: 'Load raw diff diagnostics',
|
|
130
|
+
command: appendContextSource(`vizzly --json context comparison ${quoteCommandArgument(comparisonId)} --agent --include diffs`, context)
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
for (let [stream, label] of streams) {
|
|
134
|
+
let page = context.history?.[stream]?.page;
|
|
135
|
+
if (!comparisonId || !page?.has_more || !page.next_cursor) {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
commands.push({
|
|
139
|
+
label: `Load next ${label} page`,
|
|
140
|
+
command: appendContextSource(`vizzly --json context comparison ${quoteCommandArgument(comparisonId)} --agent --cursor ${quoteCommandArgument(page.next_cursor)}${includeFlag}`, context)
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
if (comparisonId) {
|
|
144
|
+
commands.push({
|
|
145
|
+
label: 'Load full comparison context',
|
|
146
|
+
command: appendContextSource(`vizzly --json context comparison ${quoteCommandArgument(comparisonId)} --agent --full`, context)
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
return commands;
|
|
150
|
+
}
|
|
151
|
+
export function buildCompactComparisonPayload(context, include = []) {
|
|
152
|
+
return {
|
|
153
|
+
...context,
|
|
154
|
+
resource: 'comparison_agent_context',
|
|
155
|
+
suggested_commands: buildCompactComparisonCommands(context, include)
|
|
156
|
+
};
|
|
157
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { buildEndpointWithParams } from '../../shared/cloud-protocol.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Get a build context bundle at the detail level the caller can consume.
|
|
5
|
+
*
|
|
6
|
+
* Passing detail selection to the API keeps compact callers from downloading
|
|
7
|
+
* raw Honeydiff geometry by default while preserving the unfiltered endpoint
|
|
8
|
+
* for raw and full context consumers.
|
|
9
|
+
*
|
|
10
|
+
* @param {Object} client - API client
|
|
11
|
+
* @param {string} buildId - Build ID
|
|
12
|
+
* @param {Object} options - Optional query params
|
|
13
|
+
* @returns {Promise<Object>} Build context bundle
|
|
14
|
+
*/
|
|
15
|
+
export async function getBuildContext(client, buildId, options = {}) {
|
|
16
|
+
let endpoint = buildEndpointWithParams(`/api/sdk/context/builds/${buildId}`, options);
|
|
17
|
+
return client.request(endpoint);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Get comparison context bundle with similarity and history
|
|
22
|
+
* @param {Object} client - API client
|
|
23
|
+
* @param {string} comparisonId - Comparison ID
|
|
24
|
+
* @param {Object} options - Optional query params
|
|
25
|
+
* @returns {Promise<Object>} Comparison context bundle
|
|
26
|
+
*/
|
|
27
|
+
export async function getComparisonContext(client, comparisonId, options = {}) {
|
|
28
|
+
let endpoint = buildEndpointWithParams(`/api/sdk/context/comparisons/${comparisonId}`, options);
|
|
29
|
+
return client.request(endpoint);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Get screenshot context bundle for a screenshot name
|
|
34
|
+
* @param {Object} client - API client
|
|
35
|
+
* @param {string} screenshotName - Screenshot name
|
|
36
|
+
* @param {Object} options - Optional query params
|
|
37
|
+
* @returns {Promise<Object>} Screenshot context bundle
|
|
38
|
+
*/
|
|
39
|
+
export async function getScreenshotContext(client, screenshotName, options = {}) {
|
|
40
|
+
let encodedName = encodeURIComponent(screenshotName);
|
|
41
|
+
let endpoint = buildEndpointWithParams(`/api/sdk/context/screenshots/${encodedName}`, options);
|
|
42
|
+
return client.request(endpoint);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Get project-scoped similar comparisons for a fingerprint hash
|
|
47
|
+
* @param {Object} client - API client
|
|
48
|
+
* @param {string} fingerprintHash - Honeydiff fingerprint hash
|
|
49
|
+
* @param {Object} options - Optional query params
|
|
50
|
+
* @returns {Promise<Object>} Fingerprint context bundle
|
|
51
|
+
*/
|
|
52
|
+
export async function getSimilarFingerprintContext(client, fingerprintHash, options = {}) {
|
|
53
|
+
let encodedFingerprint = encodeURIComponent(fingerprintHash);
|
|
54
|
+
let endpoint = buildEndpointWithParams(`/api/sdk/context/fingerprints/${encodedFingerprint}/similar`, options);
|
|
55
|
+
return client.request(endpoint);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Get pending review queue context for a project
|
|
60
|
+
* @param {Object} client - API client
|
|
61
|
+
* @param {Object} options - Optional query params
|
|
62
|
+
* @returns {Promise<Object>} Review queue context bundle
|
|
63
|
+
*/
|
|
64
|
+
export async function getReviewQueueContext(client, options = {}) {
|
|
65
|
+
let endpoint = buildEndpointWithParams('/api/sdk/context/review-queue', options);
|
|
66
|
+
return client.request(endpoint);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get token context (organization and project info)
|
|
71
|
+
* @param {Object} client - API client
|
|
72
|
+
* @returns {Promise<Object>} Token context data
|
|
73
|
+
*/
|
|
74
|
+
export async function getTokenContext(client) {
|
|
75
|
+
return client.request('/api/sdk/token/context');
|
|
76
|
+
}
|