@vizzly-testing/cli 0.39.0 → 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 +12 -3
- package/bin/vizzly.js +1 -1
- package/dist/{cli.js → adapters/cli/cli.js} +50 -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} +54 -14
- package/dist/{commands/init.js → domains/setup/init.commands.js} +11 -44
- 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/domains/updates/cli-update.js +103 -0
- package/dist/domains/updates/update-check.js +2 -0
- package/dist/domains/updates/update-notifications.js +175 -0
- 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 +3 -6
- 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 +37 -36
- 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/{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,100 @@
|
|
|
1
|
+
function buildSourceErrorMessage() {
|
|
2
|
+
return '--source must be one of: auto, cloud, local';
|
|
3
|
+
}
|
|
4
|
+
function buildIncludeErrorMessage() {
|
|
5
|
+
return '--include must contain only: diffs';
|
|
6
|
+
}
|
|
7
|
+
function validateLimitRange(value, flagName, {
|
|
8
|
+
min = 1,
|
|
9
|
+
max
|
|
10
|
+
}) {
|
|
11
|
+
if (value == null) {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
if (!Number.isInteger(value) || value < min || value > max) {
|
|
15
|
+
return [`${flagName} must be an integer between ${min} and ${max}`];
|
|
16
|
+
}
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
function validateCursor(value) {
|
|
20
|
+
if (value == null) {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
if (typeof value !== 'string' || value.trim().length === 0) {
|
|
24
|
+
return ['--cursor must be a non-empty opaque cursor'];
|
|
25
|
+
}
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
function validateOffset(value) {
|
|
29
|
+
if (value == null) return [];
|
|
30
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
31
|
+
return ['--offset must be a non-negative integer'];
|
|
32
|
+
}
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
function validateSourceOption(value) {
|
|
36
|
+
if (value == null) {
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
if (!['auto', 'cloud', 'local'].includes(value)) {
|
|
40
|
+
return [buildSourceErrorMessage()];
|
|
41
|
+
}
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
export function parseIncludeOption(value) {
|
|
45
|
+
if (!value) {
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
let rawItems = Array.isArray(value) ? value : String(value).split(',');
|
|
49
|
+
return rawItems.map(item => item.trim()).filter(Boolean);
|
|
50
|
+
}
|
|
51
|
+
function validateIncludeOption(value) {
|
|
52
|
+
let allowed = new Set(['diffs']);
|
|
53
|
+
let invalid = parseIncludeOption(value).filter(item => !allowed.has(item));
|
|
54
|
+
return invalid.length > 0 ? [buildIncludeErrorMessage()] : [];
|
|
55
|
+
}
|
|
56
|
+
function validateScopedProjectOptions(options = {}) {
|
|
57
|
+
let errors = [];
|
|
58
|
+
if (options.org && !options.project) {
|
|
59
|
+
errors.push('--org requires --project');
|
|
60
|
+
}
|
|
61
|
+
return errors;
|
|
62
|
+
}
|
|
63
|
+
export function validateContextBuildOptions(_options = {}) {
|
|
64
|
+
let errors = validateSourceOption(_options.source);
|
|
65
|
+
errors.push(...validateIncludeOption(_options.include));
|
|
66
|
+
errors.push(...validateCursor(_options.cursor));
|
|
67
|
+
return errors;
|
|
68
|
+
}
|
|
69
|
+
export function validateContextComparisonOptions(options = {}) {
|
|
70
|
+
let errors = [];
|
|
71
|
+
errors.push(...validateSourceOption(options.source));
|
|
72
|
+
errors.push(...validateIncludeOption(options.include));
|
|
73
|
+
errors.push(...validateCursor(options.cursor));
|
|
74
|
+
return errors;
|
|
75
|
+
}
|
|
76
|
+
export function validateContextScreenshotOptions(options = {}) {
|
|
77
|
+
let errors = validateScopedProjectOptions(options);
|
|
78
|
+
errors.push(...validateSourceOption(options.source));
|
|
79
|
+
errors.push(...validateLimitRange(options.recentLimit, '--recent-limit', {
|
|
80
|
+
max: 50
|
|
81
|
+
}));
|
|
82
|
+
return errors;
|
|
83
|
+
}
|
|
84
|
+
export function validateContextSimilarOptions(options = {}) {
|
|
85
|
+
let errors = validateScopedProjectOptions(options);
|
|
86
|
+
errors.push(...validateSourceOption(options.source));
|
|
87
|
+
errors.push(...validateLimitRange(options.limit, '--limit', {
|
|
88
|
+
max: 50
|
|
89
|
+
}));
|
|
90
|
+
return errors;
|
|
91
|
+
}
|
|
92
|
+
export function validateContextReviewQueueOptions(options = {}) {
|
|
93
|
+
let errors = validateScopedProjectOptions(options);
|
|
94
|
+
errors.push(...validateSourceOption(options.source));
|
|
95
|
+
errors.push(...validateLimitRange(options.limit, '--limit', {
|
|
96
|
+
max: 100
|
|
97
|
+
}));
|
|
98
|
+
errors.push(...validateOffset(options.offset));
|
|
99
|
+
return errors;
|
|
100
|
+
}
|
|
@@ -2,7 +2,7 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import { existsSync as defaultExistsSync, readFileSync } from 'node:fs';
|
|
3
3
|
import { basename, isAbsolute, join } from 'node:path';
|
|
4
4
|
import { pathToFileURL } from 'node:url';
|
|
5
|
-
import { normalizeReportData } from '../
|
|
5
|
+
import { normalizeReportData } from '../reports/report.normalizers.js';
|
|
6
6
|
let LOCAL_CONTEXT_SOURCE = 'local_workspace';
|
|
7
7
|
let DEFAULT_LOCAL_REVIEW_QUEUE_LIMIT = 50;
|
|
8
8
|
function readJsonIfExists(path) {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { URL } from 'node:url';
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
2
|
+
import * as defaultOutput from '../../shared/output.js';
|
|
3
|
+
import { createApiClient as defaultCreateApiClient } from '../auth/cloud-client.js';
|
|
4
|
+
import { getBuilds as defaultGetBuilds } from '../builds/build.service.js';
|
|
5
|
+
import { loadConfig as defaultLoadConfig } from '../config/config-loader.js';
|
|
6
|
+
import { getApiToken as defaultGetApiToken } from '../config/environment.js';
|
|
7
|
+
import { getContext as defaultGetContext } from './environment-context.js';
|
|
7
8
|
export let MIN_NODE_MAJOR = 22;
|
|
8
9
|
export function createDoctorDiagnostics() {
|
|
9
10
|
return {
|
|
@@ -3,43 +3,34 @@
|
|
|
3
3
|
* Health check endpoint with diagnostics
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { sendSuccess } from '../middleware/response.js';
|
|
6
|
+
import { sendSuccess } from '../../adapters/http/middleware/response.js';
|
|
7
|
+
import { createReportStore } from '../reports/report-store.js';
|
|
9
8
|
|
|
10
9
|
/**
|
|
11
10
|
* Create health router
|
|
12
11
|
* @param {Object} context - Router context
|
|
13
12
|
* @param {number} context.port - Server port
|
|
14
13
|
* @param {Object} context.screenshotHandler - Screenshot handler
|
|
14
|
+
* @param {string} [context.workingDir] - Workspace containing report artifacts
|
|
15
15
|
* @returns {Function} Route handler
|
|
16
16
|
*/
|
|
17
17
|
export function createHealthRouter({
|
|
18
18
|
port,
|
|
19
|
-
screenshotHandler
|
|
19
|
+
screenshotHandler,
|
|
20
|
+
workingDir = process.cwd()
|
|
20
21
|
}) {
|
|
22
|
+
let reports = createReportStore(workingDir);
|
|
21
23
|
return async function handleHealthRoute(req, res, pathname) {
|
|
22
24
|
if (req.method !== 'GET' || pathname !== '/health') {
|
|
23
25
|
return false;
|
|
24
26
|
}
|
|
25
|
-
const reportDataPath = join(process.cwd(), '.vizzly', 'report-data.json');
|
|
26
|
-
const baselineMetadataPath = join(process.cwd(), '.vizzly', 'baselines', 'metadata.json');
|
|
27
27
|
let reportData = null;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
} catch {
|
|
33
|
-
// Ignore read errors
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
if (existsSync(baselineMetadataPath)) {
|
|
37
|
-
try {
|
|
38
|
-
baselineInfo = JSON.parse(readFileSync(baselineMetadataPath, 'utf8'));
|
|
39
|
-
} catch {
|
|
40
|
-
// Ignore read errors
|
|
41
|
-
}
|
|
28
|
+
try {
|
|
29
|
+
reportData = reports.readReport();
|
|
30
|
+
} catch {
|
|
31
|
+
// Health remains available when report artifacts cannot be read.
|
|
42
32
|
}
|
|
33
|
+
let baselineInfo = reports.readBaselineMetadata();
|
|
43
34
|
sendSuccess(res, {
|
|
44
35
|
status: 'ok',
|
|
45
36
|
port,
|
|
@@ -12,13 +12,15 @@ import { readdir, readFile, realpath, stat, unlink } from 'node:fs/promises';
|
|
|
12
12
|
import { tmpdir } from 'node:os';
|
|
13
13
|
import { join, resolve } from 'node:path';
|
|
14
14
|
import { promisify } from 'node:util';
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import
|
|
18
|
-
import {
|
|
19
|
-
import
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
15
|
+
import { openBrowser as defaultOpenBrowser } from '../../shared/browser.js';
|
|
16
|
+
import { detectBranch as defaultDetectBranch } from '../../shared/git.js';
|
|
17
|
+
import * as defaultOutput from '../../shared/output.js';
|
|
18
|
+
import { createWildcardMatcher } from '../../shared/patterns.js';
|
|
19
|
+
import { createApiClient as defaultCreateApiClient } from '../auth/cloud-client.js';
|
|
20
|
+
import { getBuild as defaultGetBuild } from '../builds/build.service.js';
|
|
21
|
+
import { loadConfig as defaultLoadConfig } from '../config/config-loader.js';
|
|
22
|
+
import { formatSessionAge as defaultFormatSessionAge, readSession as defaultReadSession } from '../runs/session-store.js';
|
|
23
|
+
import { uploadPreviewZip as defaultUploadPreviewZip } from './preview.service.js';
|
|
22
24
|
let execFileAsync = promisify(execFile);
|
|
23
25
|
|
|
24
26
|
// Maximum files to show in dry-run output (use --verbose for all)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Upload preview ZIP file for a build
|
|
3
|
+
* @param {Object} client - API client
|
|
4
|
+
* @param {string} buildId - Build ID
|
|
5
|
+
* @param {Buffer} zipBuffer - ZIP file contents
|
|
6
|
+
* @returns {Promise<Object>} Upload result with preview URL
|
|
7
|
+
*/
|
|
8
|
+
export async function uploadPreviewZip(client, buildId, zipBuffer) {
|
|
9
|
+
// Use native FormData (Node 18+) with Blob for proper fetch compatibility
|
|
10
|
+
let formData = new FormData();
|
|
11
|
+
let blob = new Blob([zipBuffer], {
|
|
12
|
+
type: 'application/zip'
|
|
13
|
+
});
|
|
14
|
+
formData.append('file', blob, 'preview.zip');
|
|
15
|
+
return client.request(`/api/sdk/builds/${buildId}/preview/upload-zip`, {
|
|
16
|
+
method: 'POST',
|
|
17
|
+
body: formData
|
|
18
|
+
// Let fetch set the Content-Type with boundary automatically
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Get preview info for a build
|
|
24
|
+
* @param {Object} client - API client
|
|
25
|
+
* @param {string} buildId - Build ID
|
|
26
|
+
* @returns {Promise<Object>} Preview info or null if not found
|
|
27
|
+
*/
|
|
28
|
+
export async function getPreviewInfo(client, buildId) {
|
|
29
|
+
try {
|
|
30
|
+
return await client.request(`/api/sdk/builds/${buildId}/preview`);
|
|
31
|
+
} catch (error) {
|
|
32
|
+
if (error.context?.status === 404) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { sendError, sendSuccess } from '../../adapters/http/middleware/response.js';
|
|
2
|
+
import * as output from '../../shared/output.js';
|
|
3
|
+
export function createCloudProjectsRouter({
|
|
4
|
+
authService
|
|
5
|
+
}) {
|
|
6
|
+
return async function handleCloudProjectsRoute(req, res, pathname) {
|
|
7
|
+
if (req.method !== 'GET' || pathname !== '/api/cloud/projects') return false;
|
|
8
|
+
try {
|
|
9
|
+
let response = await authService.authenticatedRequest('/api/cli/projects', {
|
|
10
|
+
method: 'GET'
|
|
11
|
+
});
|
|
12
|
+
sendSuccess(res, {
|
|
13
|
+
projects: response.projects || []
|
|
14
|
+
});
|
|
15
|
+
} catch (error) {
|
|
16
|
+
output.error('Error fetching projects from cloud:', error);
|
|
17
|
+
if (error.message?.toLowerCase().includes('not authenticated') || error.code === 'AUTH_ERROR') {
|
|
18
|
+
sendSuccess(res, {
|
|
19
|
+
projects: [],
|
|
20
|
+
authenticated: false
|
|
21
|
+
});
|
|
22
|
+
} else {
|
|
23
|
+
sendError(res, 500, error.message);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return true;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -1,13 +1,12 @@
|
|
|
1
|
+
import * as defaultOutput from '../../shared/output.js';
|
|
2
|
+
import { createApiClient as defaultCreateApiClient } from '../auth/cloud-client.js';
|
|
3
|
+
import { loadConfig as defaultLoadConfig } from '../config/config-loader.js';
|
|
4
|
+
import { getApiUrl as defaultGetApiUrl } from '../config/environment.js';
|
|
5
|
+
|
|
1
6
|
/**
|
|
2
7
|
* Organizations command - List organizations the user has access to
|
|
3
8
|
*/
|
|
4
9
|
|
|
5
|
-
import { createApiClient as defaultCreateApiClient } from '../api/client.js';
|
|
6
|
-
import { loadConfig as defaultLoadConfig } from '../utils/config-loader.js';
|
|
7
|
-
import { getApiUrl as defaultGetApiUrl } from '../utils/environment-config.js';
|
|
8
|
-
import { getAccessToken as defaultGetAccessToken } from '../utils/global-config.js';
|
|
9
|
-
import * as defaultOutput from '../utils/output.js';
|
|
10
|
-
|
|
11
10
|
/**
|
|
12
11
|
* Organizations command implementation
|
|
13
12
|
* @param {Object} options - Command options
|
|
@@ -19,7 +18,6 @@ export async function orgsCommand(_options = {}, globalOptions = {}, deps = {})
|
|
|
19
18
|
loadConfig = defaultLoadConfig,
|
|
20
19
|
createApiClient = defaultCreateApiClient,
|
|
21
20
|
getApiUrl = defaultGetApiUrl,
|
|
22
|
-
getAccessToken = defaultGetAccessToken,
|
|
23
21
|
output = defaultOutput,
|
|
24
22
|
exit = code => process.exit(code)
|
|
25
23
|
} = deps;
|
|
@@ -33,7 +31,7 @@ export async function orgsCommand(_options = {}, globalOptions = {}, deps = {})
|
|
|
33
31
|
|
|
34
32
|
// Prefer user auth token for listing orgs (project tokens are org-scoped).
|
|
35
33
|
// Falls back to config.apiKey which may be: VIZZLY_TOKEN, --token flag, or project token.
|
|
36
|
-
let token =
|
|
34
|
+
let token = config.userToken || config.apiKey;
|
|
37
35
|
if (!token) {
|
|
38
36
|
output.error('API token required. Use --token, set VIZZLY_TOKEN, or run "vizzly login"');
|
|
39
37
|
output.cleanup();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { applyChanges } from '../../shared/file-changes.js';
|
|
3
|
+
import { VizzlyError } from '../../shared/vizzly-error.js';
|
|
4
|
+
import { discoverConfig } from '../config/config-discovery.js';
|
|
5
5
|
function sameProject(left, right) {
|
|
6
6
|
return left?.organizationSlug === right.organizationSlug && left?.projectSlug === right.projectSlug;
|
|
7
7
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { VizzlyError } from '../../shared/vizzly-error.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Build error for project fetch failure
|
|
5
|
+
* @param {Error} originalError - Original error
|
|
6
|
+
* @returns {VizzlyError} Wrapped error
|
|
7
|
+
*/
|
|
8
|
+
export function buildProjectFetchError(originalError) {
|
|
9
|
+
return new VizzlyError(`Failed to fetch project: ${originalError.message}`, 'PROJECT_FETCH_FAILED', {
|
|
10
|
+
originalError
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Build error for no authentication available
|
|
16
|
+
* @returns {VizzlyError} No auth error
|
|
17
|
+
*/
|
|
18
|
+
export function buildNoAuthError() {
|
|
19
|
+
return new VizzlyError('No authentication available', 'NO_AUTH_SERVICE');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Build error for no API service available
|
|
24
|
+
* @returns {VizzlyError} No API service error
|
|
25
|
+
*/
|
|
26
|
+
export function buildNoApiServiceError() {
|
|
27
|
+
return new VizzlyError('API service not available', 'NO_API_SERVICE');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Build error for token creation failure
|
|
32
|
+
* @param {Error} originalError - Original error
|
|
33
|
+
* @returns {VizzlyError} Wrapped error
|
|
34
|
+
*/
|
|
35
|
+
export function buildTokenCreateError(originalError) {
|
|
36
|
+
return new VizzlyError(`Failed to create project token: ${originalError.message}`, 'TOKEN_CREATE_FAILED', {
|
|
37
|
+
originalError
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Build error for token fetch failure
|
|
43
|
+
* @param {Error} originalError - Original error
|
|
44
|
+
* @returns {VizzlyError} Wrapped error
|
|
45
|
+
*/
|
|
46
|
+
export function buildTokensFetchError(originalError) {
|
|
47
|
+
return new VizzlyError(`Failed to fetch project tokens: ${originalError.message}`, 'TOKENS_FETCH_FAILED', {
|
|
48
|
+
originalError
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Build error for token revoke failure
|
|
54
|
+
* @param {Error} originalError - Original error
|
|
55
|
+
* @returns {VizzlyError} Wrapped error
|
|
56
|
+
*/
|
|
57
|
+
export function buildTokenRevokeError(originalError) {
|
|
58
|
+
return new VizzlyError(`Failed to revoke project token: ${originalError.message}`, 'TOKEN_REVOKE_FAILED', {
|
|
59
|
+
originalError
|
|
60
|
+
});
|
|
61
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { loadGlobalConfig, saveGlobalConfig } from '
|
|
2
|
-
import { deleteSecret as defaultDeleteSecret, getSecret as defaultGetSecret, saveSecret as defaultSaveSecret } from '
|
|
1
|
+
import { loadGlobalConfig, saveGlobalConfig } from '../../shared/global-config.js';
|
|
2
|
+
import { deleteSecret as defaultDeleteSecret, getSecret as defaultGetSecret, saveSecret as defaultSaveSecret } from '../../shared/secret-store.js';
|
|
3
3
|
export function buildProjectLinkAccount({
|
|
4
4
|
apiUrl,
|
|
5
5
|
organizationSlug,
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build query params for builds API request
|
|
3
|
+
* @param {Object} options - Query options
|
|
4
|
+
* @param {number} [options.limit] - Number of builds to fetch
|
|
5
|
+
* @param {string} [options.branch] - Filter by branch
|
|
6
|
+
* @returns {string} Query string (empty string if no params)
|
|
7
|
+
*/
|
|
8
|
+
export function buildBuildsQueryParams(options = {}) {
|
|
9
|
+
let params = new globalThis.URLSearchParams();
|
|
10
|
+
if (options.limit) {
|
|
11
|
+
params.append('limit', String(options.limit));
|
|
12
|
+
}
|
|
13
|
+
if (options.branch) {
|
|
14
|
+
params.append('branch', options.branch);
|
|
15
|
+
}
|
|
16
|
+
let query = params.toString();
|
|
17
|
+
return query ? `?${query}` : '';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Build organization header object
|
|
22
|
+
* @param {string} organizationSlug - Organization slug
|
|
23
|
+
* @returns {Object} Headers object with X-Organization header
|
|
24
|
+
*/
|
|
25
|
+
export function buildOrgHeader(organizationSlug) {
|
|
26
|
+
return {
|
|
27
|
+
'X-Organization': organizationSlug
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Build API URL for project endpoint
|
|
33
|
+
* @param {string} projectSlug - Project slug
|
|
34
|
+
* @returns {string} API URL path
|
|
35
|
+
*/
|
|
36
|
+
export function buildProjectUrl(projectSlug) {
|
|
37
|
+
return `/api/project/${projectSlug}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Build API URL for builds endpoint
|
|
42
|
+
* @param {string} projectSlug - Project slug
|
|
43
|
+
* @param {Object} options - Query options
|
|
44
|
+
* @returns {string} Full API URL path with query params
|
|
45
|
+
*/
|
|
46
|
+
export function buildBuildsUrl(projectSlug, options = {}) {
|
|
47
|
+
let queryString = buildBuildsQueryParams(options);
|
|
48
|
+
return `/api/build/${projectSlug}${queryString}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Build API URL for project tokens endpoint
|
|
53
|
+
* @param {string} organizationSlug - Organization slug
|
|
54
|
+
* @param {string} projectSlug - Project slug
|
|
55
|
+
* @param {string} [tokenId] - Optional token ID for specific token operations
|
|
56
|
+
* @returns {string} API URL path
|
|
57
|
+
*/
|
|
58
|
+
export function buildTokensUrl(_organizationSlug, projectSlug, tokenId) {
|
|
59
|
+
let base = `/api/project/${projectSlug}/tokens`;
|
|
60
|
+
return tokenId ? `${base}/${tokenId}` : base;
|
|
61
|
+
}
|
|
@@ -1,46 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
7
|
-
import { planProjectBinding as defaultPlanProjectBinding, writeProjectBinding as defaultWriteProjectBinding } from '
|
|
8
|
-
import { getProjectLink as defaultGetProjectLink, saveProjectLink as defaultSaveProjectLink } from '
|
|
9
|
-
export function parseProjectSelector(selector, options = {}) {
|
|
10
|
-
let organizationSlug = options.org || null;
|
|
11
|
-
let projectSlug = options.project || null;
|
|
12
|
-
if (selector) {
|
|
13
|
-
let parts = selector.split('/');
|
|
14
|
-
if (parts.length === 2) {
|
|
15
|
-
organizationSlug = organizationSlug || parts[0];
|
|
16
|
-
projectSlug = projectSlug || parts[1];
|
|
17
|
-
} else if (parts.length === 1) {
|
|
18
|
-
projectSlug = projectSlug || parts[0];
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
return {
|
|
22
|
-
organizationSlug,
|
|
23
|
-
projectSlug
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
export function validateProjectLinkOptions(selector, options = {}) {
|
|
27
|
-
let errors = [];
|
|
28
|
-
let {
|
|
29
|
-
organizationSlug,
|
|
30
|
-
projectSlug
|
|
31
|
-
} = parseProjectSelector(selector, options);
|
|
32
|
-
if (!organizationSlug) {
|
|
33
|
-
errors.push('Organization is required. Use <org>/<project> or --org <slug>.');
|
|
34
|
-
}
|
|
35
|
-
if (!projectSlug) {
|
|
36
|
-
errors.push('Project is required. Use <org>/<project> or --project <slug>.');
|
|
37
|
-
}
|
|
38
|
-
return errors;
|
|
39
|
-
}
|
|
1
|
+
import { preflightChanges } from '../../shared/file-changes.js';
|
|
2
|
+
import * as defaultOutput from '../../shared/output.js';
|
|
3
|
+
import { createApiClient as defaultCreateApiClient } from '../auth/cloud-client.js';
|
|
4
|
+
import { loadConfig as defaultLoadConfig } from '../config/config-loader.js';
|
|
5
|
+
import { getApiUrl as defaultGetApiUrl } from '../config/environment.js';
|
|
6
|
+
import { parseProjectSelector } from './project.validations.js';
|
|
7
|
+
import { planProjectBinding as defaultPlanProjectBinding, writeProjectBinding as defaultWriteProjectBinding } from './project-binding.js';
|
|
8
|
+
import { getProjectLink as defaultGetProjectLink, saveProjectLink as defaultSaveProjectLink } from './project-link-store.js';
|
|
40
9
|
export async function projectLinkCommand(selector, options = {}, globalOptions = {}, deps = {}) {
|
|
41
10
|
let {
|
|
42
11
|
createApiClient = defaultCreateApiClient,
|
|
43
|
-
getAccessToken = defaultGetAccessToken,
|
|
44
12
|
getApiUrl = defaultGetApiUrl,
|
|
45
13
|
loadConfig = defaultLoadConfig,
|
|
46
14
|
output = defaultOutput,
|
|
@@ -111,7 +79,7 @@ export async function projectLinkCommand(selector, options = {}, globalOptions =
|
|
|
111
79
|
...project
|
|
112
80
|
}) : null;
|
|
113
81
|
let reused = Boolean(linkedProject);
|
|
114
|
-
let userToken = linkedProject ? null : config.userToken || globalOptions.token
|
|
82
|
+
let userToken = linkedProject ? null : config.userToken || globalOptions.token;
|
|
115
83
|
if (!linkedProject && !userToken) {
|
|
116
84
|
output.error('Login required before linking a project');
|
|
117
85
|
output.hint('Run "vizzly login" first, then try project link again');
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract projects from API response
|
|
3
|
+
* @param {Object} response - API response
|
|
4
|
+
* @returns {Array} Array of projects
|
|
5
|
+
*/
|
|
6
|
+
export function extractProjects(response) {
|
|
7
|
+
return response?.projects || [];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Extract project from API response
|
|
12
|
+
* @param {Object} response - API response
|
|
13
|
+
* @returns {Object} Project object
|
|
14
|
+
*/
|
|
15
|
+
export function extractProject(response) {
|
|
16
|
+
return response?.project || response;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Extract builds from API response
|
|
21
|
+
* @param {Object} response - API response
|
|
22
|
+
* @returns {Array} Array of builds
|
|
23
|
+
*/
|
|
24
|
+
export function extractBuilds(response) {
|
|
25
|
+
return response?.builds || [];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Extract token from API response
|
|
30
|
+
* @param {Object} response - API response
|
|
31
|
+
* @returns {Object} Token object
|
|
32
|
+
*/
|
|
33
|
+
export function extractToken(response) {
|
|
34
|
+
return response?.token;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Extract tokens from API response
|
|
39
|
+
* @param {Object} response - API response
|
|
40
|
+
* @returns {Array} Array of tokens
|
|
41
|
+
*/
|
|
42
|
+
export function extractTokens(response) {
|
|
43
|
+
return response?.tokens || [];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Enrich projects with organization info
|
|
48
|
+
* @param {Array} projects - Array of projects
|
|
49
|
+
* @param {Object} org - Organization object
|
|
50
|
+
* @param {string} org.slug - Organization slug
|
|
51
|
+
* @param {string} org.name - Organization name
|
|
52
|
+
* @returns {Array} Projects with organization info added
|
|
53
|
+
*/
|
|
54
|
+
export function enrichProjectsWithOrg(projects, org) {
|
|
55
|
+
return projects.map(project => ({
|
|
56
|
+
...project,
|
|
57
|
+
organizationSlug: org.slug,
|
|
58
|
+
organizationName: org.name
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Extract organizations from whoami response
|
|
64
|
+
* @param {Object} whoamiResponse - Whoami API response
|
|
65
|
+
* @returns {Array} Array of organizations
|
|
66
|
+
*/
|
|
67
|
+
export function extractOrganizations(whoamiResponse) {
|
|
68
|
+
return whoamiResponse?.organizations || [];
|
|
69
|
+
}
|