codex-orchestrator 0.1.31 → 0.1.36
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/CHANGELOG.md +84 -0
- package/README.md +150 -174
- package/dist/src/cli.js +88 -0
- package/dist/src/cli.js.map +1 -1
- package/dist/src/codex/command-adapter.d.ts +2 -1
- package/dist/src/codex/command-adapter.d.ts.map +1 -1
- package/dist/src/codex/command-adapter.js +13 -2
- package/dist/src/codex/command-adapter.js.map +1 -1
- package/dist/src/codex/mobile-device-guard.d.ts +7 -0
- package/dist/src/codex/mobile-device-guard.d.ts.map +1 -0
- package/dist/src/codex/mobile-device-guard.js +63 -0
- package/dist/src/codex/mobile-device-guard.js.map +1 -0
- package/dist/src/config/constants.d.ts +1 -1
- package/dist/src/config/constants.d.ts.map +1 -1
- package/dist/src/config/constants.js +1 -0
- package/dist/src/config/constants.js.map +1 -1
- package/dist/src/config/schema.d.ts +21 -2
- package/dist/src/config/schema.d.ts.map +1 -1
- package/dist/src/config/schema.js +41 -0
- package/dist/src/config/schema.js.map +1 -1
- package/dist/src/github/gh-pull-request-adapter.d.ts +1 -0
- package/dist/src/github/gh-pull-request-adapter.d.ts.map +1 -1
- package/dist/src/github/gh-pull-request-adapter.js +20 -0
- package/dist/src/github/gh-pull-request-adapter.js.map +1 -1
- package/dist/src/github/pull-requests.d.ts +2 -0
- package/dist/src/github/pull-requests.d.ts.map +1 -1
- package/dist/src/github/pull-requests.js +9 -0
- package/dist/src/github/pull-requests.js.map +1 -1
- package/dist/src/runner/acceptance-proof-runner.d.ts +54 -0
- package/dist/src/runner/acceptance-proof-runner.d.ts.map +1 -0
- package/dist/src/runner/acceptance-proof-runner.js +229 -0
- package/dist/src/runner/acceptance-proof-runner.js.map +1 -0
- package/dist/src/runner/acceptance-proof.d.ts +121 -0
- package/dist/src/runner/acceptance-proof.d.ts.map +1 -0
- package/dist/src/runner/acceptance-proof.js +405 -0
- package/dist/src/runner/acceptance-proof.js.map +1 -0
- package/dist/src/runner/android-visual-proof-command.d.ts +26 -0
- package/dist/src/runner/android-visual-proof-command.d.ts.map +1 -0
- package/dist/src/runner/android-visual-proof-command.js +598 -0
- package/dist/src/runner/android-visual-proof-command.js.map +1 -0
- package/dist/src/runner/auto-visual-proof-command.d.ts +15 -0
- package/dist/src/runner/auto-visual-proof-command.d.ts.map +1 -0
- package/dist/src/runner/auto-visual-proof-command.js +41 -0
- package/dist/src/runner/auto-visual-proof-command.js.map +1 -0
- package/dist/src/runner/browser-proof-contract.d.ts +134 -0
- package/dist/src/runner/browser-proof-contract.d.ts.map +1 -0
- package/dist/src/runner/browser-proof-contract.js +400 -0
- package/dist/src/runner/browser-proof-contract.js.map +1 -0
- package/dist/src/runner/browser-visual-proof-command.d.ts +101 -0
- package/dist/src/runner/browser-visual-proof-command.d.ts.map +1 -0
- package/dist/src/runner/browser-visual-proof-command.js +532 -0
- package/dist/src/runner/browser-visual-proof-command.js.map +1 -0
- package/dist/src/runner/command-utils.d.ts.map +1 -1
- package/dist/src/runner/command-utils.js +55 -6
- package/dist/src/runner/command-utils.js.map +1 -1
- package/dist/src/runner/completion-report.d.ts +3 -1
- package/dist/src/runner/completion-report.d.ts.map +1 -1
- package/dist/src/runner/completion-report.js +2 -1
- package/dist/src/runner/completion-report.js.map +1 -1
- package/dist/src/runner/daemon-command.d.ts.map +1 -1
- package/dist/src/runner/daemon-command.js +20 -0
- package/dist/src/runner/daemon-command.js.map +1 -1
- package/dist/src/runner/doctor-command.js +3 -3
- package/dist/src/runner/doctor-command.js.map +1 -1
- package/dist/src/runner/durable-run-summary.d.ts +3 -0
- package/dist/src/runner/durable-run-summary.d.ts.map +1 -1
- package/dist/src/runner/durable-run-summary.js +2 -0
- package/dist/src/runner/durable-run-summary.js.map +1 -1
- package/dist/src/runner/handoff-evidence.d.ts +5 -0
- package/dist/src/runner/handoff-evidence.d.ts.map +1 -1
- package/dist/src/runner/handoff-evidence.js +22 -0
- package/dist/src/runner/handoff-evidence.js.map +1 -1
- package/dist/src/runner/ios-visual-proof-command.d.ts +25 -0
- package/dist/src/runner/ios-visual-proof-command.d.ts.map +1 -0
- package/dist/src/runner/ios-visual-proof-command.js +355 -0
- package/dist/src/runner/ios-visual-proof-command.js.map +1 -0
- package/dist/src/runner/lifecycle-events.d.ts +1 -1
- package/dist/src/runner/lifecycle-events.d.ts.map +1 -1
- package/dist/src/runner/local-execution-session.d.ts +19 -6
- package/dist/src/runner/local-execution-session.d.ts.map +1 -1
- package/dist/src/runner/local-execution-session.js +93 -11
- package/dist/src/runner/local-execution-session.js.map +1 -1
- package/dist/src/runner/local-state.d.ts +6 -0
- package/dist/src/runner/local-state.d.ts.map +1 -1
- package/dist/src/runner/local-state.js +14 -0
- package/dist/src/runner/local-state.js.map +1 -1
- package/dist/src/runner/mobile-device-lease.d.ts +11 -0
- package/dist/src/runner/mobile-device-lease.d.ts.map +1 -0
- package/dist/src/runner/mobile-device-lease.js +121 -0
- package/dist/src/runner/mobile-device-lease.js.map +1 -0
- package/dist/src/runner/mobile-visual-proof-command.d.ts +17 -0
- package/dist/src/runner/mobile-visual-proof-command.d.ts.map +1 -0
- package/dist/src/runner/mobile-visual-proof-command.js +168 -0
- package/dist/src/runner/mobile-visual-proof-command.js.map +1 -0
- package/dist/src/runner/plan-auto-command.d.ts.map +1 -1
- package/dist/src/runner/plan-auto-command.js +57 -6
- package/dist/src/runner/plan-auto-command.js.map +1 -1
- package/dist/src/runner/prompt.js +2 -2
- package/dist/src/runner/prompt.js.map +1 -1
- package/dist/src/runner/recovery.d.ts +2 -1
- package/dist/src/runner/recovery.d.ts.map +1 -1
- package/dist/src/runner/recovery.js +24 -0
- package/dist/src/runner/recovery.js.map +1 -1
- package/dist/src/runner/review-gate-policy.d.ts +19 -0
- package/dist/src/runner/review-gate-policy.d.ts.map +1 -1
- package/dist/src/runner/review-gate-policy.js +116 -20
- package/dist/src/runner/review-gate-policy.js.map +1 -1
- package/dist/src/runner/review-gates.d.ts.map +1 -1
- package/dist/src/runner/review-gates.js +44 -11
- package/dist/src/runner/review-gates.js.map +1 -1
- package/dist/src/runner/rework-policy.d.ts +1 -0
- package/dist/src/runner/rework-policy.d.ts.map +1 -1
- package/dist/src/runner/rework-policy.js +7 -0
- package/dist/src/runner/rework-policy.js.map +1 -1
- package/dist/src/runner/scoped-auto-command.d.ts +46 -1
- package/dist/src/runner/scoped-auto-command.d.ts.map +1 -1
- package/dist/src/runner/scoped-auto-command.js +147 -47
- package/dist/src/runner/scoped-auto-command.js.map +1 -1
- package/dist/src/runner/scoped-recovery.d.ts +65 -0
- package/dist/src/runner/scoped-recovery.d.ts.map +1 -0
- package/dist/src/runner/scoped-recovery.js +484 -0
- package/dist/src/runner/scoped-recovery.js.map +1 -0
- package/dist/src/runner/status-command.d.ts.map +1 -1
- package/dist/src/runner/status-command.js +1 -0
- package/dist/src/runner/status-command.js.map +1 -1
- package/dist/src/runner/visual-proof-runner.d.ts +1 -0
- package/dist/src/runner/visual-proof-runner.d.ts.map +1 -1
- package/dist/src/runner/visual-proof-runner.js +158 -19
- package/dist/src/runner/visual-proof-runner.js.map +1 -1
- package/dist/src/setup/project-config.d.ts +5 -0
- package/dist/src/setup/project-config.d.ts.map +1 -1
- package/dist/src/setup/project-config.js +146 -9
- package/dist/src/setup/project-config.js.map +1 -1
- package/dist/src/setup/setup-command.d.ts.map +1 -1
- package/dist/src/setup/setup-command.js +2 -2
- package/dist/src/setup/setup-command.js.map +1 -1
- package/dist/src/setup/workflows.d.ts.map +1 -1
- package/dist/src/setup/workflows.js +5 -0
- package/dist/src/setup/workflows.js.map +1 -1
- package/docs/deep-dive.md +239 -28
- package/package.json +4 -1
- package/prompts/workflows/acceptance-proof.md +29 -0
- package/prompts/workflows/scoped-implementation.md +12 -3
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { type BrowserProofCheckpointEvidence, type BrowserProofDiagnostics, type BrowserProofScenario } from './browser-proof-contract.js';
|
|
2
|
+
import type { AcceptanceProofStatus } from './acceptance-proof.js';
|
|
3
|
+
export interface BrowserVisualProofCommandInput {
|
|
4
|
+
issueNumber: number;
|
|
5
|
+
worktreePath?: string;
|
|
6
|
+
artifactDir?: string;
|
|
7
|
+
scenarioPath?: string;
|
|
8
|
+
baseUrl?: string;
|
|
9
|
+
env?: NodeJS.ProcessEnv;
|
|
10
|
+
envPassthrough?: string[];
|
|
11
|
+
strictConsoleErrors?: boolean;
|
|
12
|
+
strictNetworkFailures?: boolean;
|
|
13
|
+
adapter?: BrowserProofAdapter;
|
|
14
|
+
}
|
|
15
|
+
export interface BrowserProofRunResult {
|
|
16
|
+
checkpoints: BrowserProofCheckpointEvidence[];
|
|
17
|
+
diagnostics: BrowserProofDiagnostics;
|
|
18
|
+
consoleErrors: string[];
|
|
19
|
+
networkFailures: string[];
|
|
20
|
+
browserRuntime?: {
|
|
21
|
+
executablePath?: string;
|
|
22
|
+
chromiumInstallAttempted: boolean;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export interface BrowserProofAdapter {
|
|
26
|
+
run(input: {
|
|
27
|
+
scenario: BrowserProofScenario;
|
|
28
|
+
worktreePath: string;
|
|
29
|
+
proofDir: string;
|
|
30
|
+
artifactDir: string;
|
|
31
|
+
issueNumber: number;
|
|
32
|
+
env: NodeJS.ProcessEnv;
|
|
33
|
+
}): Promise<BrowserProofRunResult>;
|
|
34
|
+
}
|
|
35
|
+
export declare function parseBrowserVisualProofArgs(args: string[], env?: NodeJS.ProcessEnv): {
|
|
36
|
+
ok: true;
|
|
37
|
+
value: BrowserVisualProofCommandInput;
|
|
38
|
+
} | {
|
|
39
|
+
ok: false;
|
|
40
|
+
error: string;
|
|
41
|
+
};
|
|
42
|
+
export declare function runBrowserVisualProofCommand(input: BrowserVisualProofCommandInput): Promise<{
|
|
43
|
+
status: AcceptanceProofStatus;
|
|
44
|
+
reportPath: string;
|
|
45
|
+
}>;
|
|
46
|
+
type PlaywrightLikeModule = {
|
|
47
|
+
chromium?: {
|
|
48
|
+
launchPersistentContext: (profileDir: string, options: {
|
|
49
|
+
headless: boolean;
|
|
50
|
+
executablePath?: string;
|
|
51
|
+
}) => Promise<{
|
|
52
|
+
newPage: () => Promise<PlaywrightLikePage>;
|
|
53
|
+
close: () => Promise<void>;
|
|
54
|
+
}>;
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
interface BrowserProofAdapterRuntime {
|
|
58
|
+
playwright: PlaywrightLikeModule;
|
|
59
|
+
installedBrowserPaths: string[];
|
|
60
|
+
installChromium: (env: NodeJS.ProcessEnv) => Promise<void>;
|
|
61
|
+
}
|
|
62
|
+
export declare function createDefaultBrowserProofAdapterForTest(runtime: PlaywrightLikeModule | Partial<BrowserProofAdapterRuntime>): BrowserProofAdapter;
|
|
63
|
+
type PlaywrightLikePage = {
|
|
64
|
+
on: (event: 'console' | 'requestfailed', handler: (value: PlaywrightLikeConsoleMessage & PlaywrightLikeRequest) => void) => void;
|
|
65
|
+
setViewportSize: (size: {
|
|
66
|
+
width: number;
|
|
67
|
+
height: number;
|
|
68
|
+
}) => Promise<void>;
|
|
69
|
+
goto: (url: string) => Promise<unknown>;
|
|
70
|
+
click: (selector: string) => Promise<void>;
|
|
71
|
+
getByText: (text: string) => {
|
|
72
|
+
click: () => Promise<void>;
|
|
73
|
+
waitFor: () => Promise<void>;
|
|
74
|
+
};
|
|
75
|
+
fill: (selector: string, value: string) => Promise<void>;
|
|
76
|
+
press: (selector: string, key: string) => Promise<void>;
|
|
77
|
+
waitForSelector: (selector: string) => Promise<void>;
|
|
78
|
+
locator: (selector: string) => {
|
|
79
|
+
textContent: () => Promise<string | null>;
|
|
80
|
+
waitFor: () => Promise<void>;
|
|
81
|
+
evaluate: <T>(fn: (element: Element) => T) => Promise<T>;
|
|
82
|
+
};
|
|
83
|
+
url: () => string;
|
|
84
|
+
screenshot: (options: {
|
|
85
|
+
path: string;
|
|
86
|
+
fullPage: boolean;
|
|
87
|
+
}) => Promise<unknown>;
|
|
88
|
+
};
|
|
89
|
+
type PlaywrightLikeConsoleMessage = {
|
|
90
|
+
type: () => string;
|
|
91
|
+
text: () => string;
|
|
92
|
+
};
|
|
93
|
+
type PlaywrightLikeRequest = {
|
|
94
|
+
method: () => string;
|
|
95
|
+
url: () => string;
|
|
96
|
+
failure: () => {
|
|
97
|
+
errorText?: string;
|
|
98
|
+
} | null;
|
|
99
|
+
};
|
|
100
|
+
export {};
|
|
101
|
+
//# sourceMappingURL=browser-visual-proof-command.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-visual-proof-command.d.ts","sourceRoot":"","sources":["../../../src/runner/browser-visual-proof-command.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { createRequire } from 'node:module';
|
|
5
|
+
import { homedir } from 'node:os';
|
|
6
|
+
import { delimiter, dirname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
7
|
+
import { assembleBlockedBrowserProofReport, assembleBrowserAcceptanceProofReport, assertBrowserProofArtifactPath, browserProofArtifactPath, browserProofRuntimeEnv, validateBrowserProofScenario, } from './browser-proof-contract.js';
|
|
8
|
+
const require = createRequire(import.meta.url);
|
|
9
|
+
export function parseBrowserVisualProofArgs(args, env = process.env) {
|
|
10
|
+
const parsed = {
|
|
11
|
+
issueNumber: Number(env.CODEX_ORCHESTRATOR_ISSUE_NUMBER),
|
|
12
|
+
worktreePath: env.CODEX_ORCHESTRATOR_WORKTREE_PATH ?? process.cwd(),
|
|
13
|
+
artifactDir: env.CODEX_ORCHESTRATOR_ARTIFACT_DIR,
|
|
14
|
+
scenarioPath: env.CODEX_ORCHESTRATOR_BROWSER_PROOF_SCENARIO_PATH,
|
|
15
|
+
baseUrl: env.CODEX_ORCHESTRATOR_BROWSER_BASE_URL,
|
|
16
|
+
envPassthrough: envListFromEnv(env.CODEX_ORCHESTRATOR_BROWSER_ENV_PASSTHROUGH),
|
|
17
|
+
strictConsoleErrors: booleanFromEnv(env.CODEX_ORCHESTRATOR_BROWSER_STRICT_CONSOLE),
|
|
18
|
+
strictNetworkFailures: booleanFromEnv(env.CODEX_ORCHESTRATOR_BROWSER_STRICT_NETWORK),
|
|
19
|
+
};
|
|
20
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
21
|
+
const arg = args[index];
|
|
22
|
+
const next = () => {
|
|
23
|
+
const value = args[index + 1];
|
|
24
|
+
if (!value || value.startsWith('--'))
|
|
25
|
+
return undefined;
|
|
26
|
+
index += 1;
|
|
27
|
+
return value;
|
|
28
|
+
};
|
|
29
|
+
switch (arg) {
|
|
30
|
+
case '--issue': {
|
|
31
|
+
const value = next();
|
|
32
|
+
if (!value || !Number.isInteger(Number(value)) || Number(value) < 1) {
|
|
33
|
+
return { ok: false, error: 'visual-proof browser requires --issue <number>' };
|
|
34
|
+
}
|
|
35
|
+
parsed.issueNumber = Number(value);
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
case '--target':
|
|
39
|
+
case '--worktree': {
|
|
40
|
+
const value = next();
|
|
41
|
+
if (!value)
|
|
42
|
+
return { ok: false, error: `${arg} requires a value` };
|
|
43
|
+
parsed.worktreePath = value;
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
case '--artifact-dir': {
|
|
47
|
+
const value = next();
|
|
48
|
+
if (!value)
|
|
49
|
+
return { ok: false, error: `${arg} requires a value` };
|
|
50
|
+
parsed.artifactDir = value;
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
case '--scenario': {
|
|
54
|
+
const value = next();
|
|
55
|
+
if (!value)
|
|
56
|
+
return { ok: false, error: `${arg} requires a value` };
|
|
57
|
+
parsed.scenarioPath = value;
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
case '--base-url': {
|
|
61
|
+
const value = next();
|
|
62
|
+
if (!value)
|
|
63
|
+
return { ok: false, error: `${arg} requires a value` };
|
|
64
|
+
parsed.baseUrl = value;
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
case '--strict-console':
|
|
68
|
+
parsed.strictConsoleErrors = true;
|
|
69
|
+
break;
|
|
70
|
+
case '--strict-network':
|
|
71
|
+
parsed.strictNetworkFailures = true;
|
|
72
|
+
break;
|
|
73
|
+
default:
|
|
74
|
+
return { ok: false, error: `Unknown visual-proof browser option: ${arg ?? ''}` };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (!Number.isInteger(parsed.issueNumber) || parsed.issueNumber < 1) {
|
|
78
|
+
return { ok: false, error: 'visual-proof browser requires --issue <number>' };
|
|
79
|
+
}
|
|
80
|
+
return { ok: true, value: parsed };
|
|
81
|
+
}
|
|
82
|
+
export async function runBrowserVisualProofCommand(input) {
|
|
83
|
+
const env = input.env ?? process.env;
|
|
84
|
+
const issueNumber = input.issueNumber;
|
|
85
|
+
const worktreePath = resolve(input.worktreePath ?? env.CODEX_ORCHESTRATOR_WORKTREE_PATH ?? process.cwd());
|
|
86
|
+
const artifactDir = input.artifactDir ?? env.CODEX_ORCHESTRATOR_ARTIFACT_DIR ?? '.codex-orchestrator/proofs';
|
|
87
|
+
const proofDir = resolve(worktreePath, artifactDir, `issue-${issueNumber}`);
|
|
88
|
+
const reportPath = env[browserProofRuntimeEnv.reportPath] ?? join(proofDir, 'acceptance-proof-report.json');
|
|
89
|
+
const scenarioPath = input.scenarioPath
|
|
90
|
+
? resolve(worktreePath, input.scenarioPath)
|
|
91
|
+
: join(proofDir, 'browser-proof-scenario.json');
|
|
92
|
+
const envPassthrough = input.envPassthrough ?? envListFromEnv(env.CODEX_ORCHESTRATOR_BROWSER_ENV_PASSTHROUGH) ?? [];
|
|
93
|
+
await mkdir(proofDir, { recursive: true });
|
|
94
|
+
const scenarioGuard = validateScenarioPath(scenarioPath, proofDir);
|
|
95
|
+
if (!scenarioGuard.ok) {
|
|
96
|
+
const report = assembleBlockedBrowserProofReport({
|
|
97
|
+
issueNumber,
|
|
98
|
+
artifactDir,
|
|
99
|
+
issue: {
|
|
100
|
+
kind: 'invalidScenario',
|
|
101
|
+
diagnostic: scenarioGuard.error,
|
|
102
|
+
requiredChanges: [scenarioGuard.error],
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
await writeReportWithDiagnostics(worktreePath, reportPath, report, { kind: 'invalidScenario', diagnostic: scenarioGuard.error });
|
|
106
|
+
return { status: 'blocked', reportPath };
|
|
107
|
+
}
|
|
108
|
+
const loaded = await loadScenario(scenarioPath, input.baseUrl);
|
|
109
|
+
if (!loaded.ok) {
|
|
110
|
+
const report = assembleBlockedBrowserProofReport({
|
|
111
|
+
issueNumber,
|
|
112
|
+
artifactDir,
|
|
113
|
+
issue: {
|
|
114
|
+
kind: 'invalidScenario',
|
|
115
|
+
diagnostic: loaded.error,
|
|
116
|
+
requiredChanges: [loaded.error],
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
await writeReportWithDiagnostics(worktreePath, reportPath, report, { kind: 'invalidScenario', diagnostic: loaded.error });
|
|
120
|
+
return { status: 'blocked', reportPath };
|
|
121
|
+
}
|
|
122
|
+
const validated = validateBrowserProofScenario(loaded.scenario, {
|
|
123
|
+
artifactDir,
|
|
124
|
+
issueNumber,
|
|
125
|
+
envPassthrough,
|
|
126
|
+
});
|
|
127
|
+
if (!validated.ok) {
|
|
128
|
+
const diagnostic = validated.errors.join('; ');
|
|
129
|
+
const report = assembleBlockedBrowserProofReport({
|
|
130
|
+
issueNumber,
|
|
131
|
+
artifactDir,
|
|
132
|
+
issue: {
|
|
133
|
+
kind: 'invalidScenario',
|
|
134
|
+
diagnostic,
|
|
135
|
+
requiredChanges: validated.errors,
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
await writeReportWithDiagnostics(worktreePath, reportPath, report, { kind: 'invalidScenario', diagnostic });
|
|
139
|
+
return { status: 'blocked', reportPath };
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
const runtimeDirs = await ensureBrowserRuntimeDirs(env);
|
|
143
|
+
if (!runtimeDirs.ok) {
|
|
144
|
+
const report = assembleBlockedBrowserProofReport({
|
|
145
|
+
issueNumber,
|
|
146
|
+
artifactDir,
|
|
147
|
+
scenario: validated.scenario,
|
|
148
|
+
issue: {
|
|
149
|
+
kind: runtimeDirs.kind,
|
|
150
|
+
diagnostic: runtimeDirs.error,
|
|
151
|
+
requiredChanges: [runtimeDirs.error],
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
await writeReportWithDiagnostics(worktreePath, reportPath, report, { kind: runtimeDirs.kind, diagnostic: runtimeDirs.error });
|
|
155
|
+
return { status: 'blocked', reportPath };
|
|
156
|
+
}
|
|
157
|
+
const adapter = input.adapter ?? defaultBrowserProofAdapter();
|
|
158
|
+
const run = await adapter.run({ scenario: validated.scenario, worktreePath, proofDir, artifactDir, issueNumber, env });
|
|
159
|
+
const strictBlockers = [
|
|
160
|
+
...(input.strictConsoleErrors && run.consoleErrors.length > 0 ? [`Console errors: ${run.consoleErrors.join('; ')}`] : []),
|
|
161
|
+
...(input.strictNetworkFailures && run.networkFailures.length > 0 ? [`Network failures: ${run.networkFailures.join('; ')}`] : []),
|
|
162
|
+
];
|
|
163
|
+
if (strictBlockers.length > 0) {
|
|
164
|
+
const strictKind = run.consoleErrors.length > 0 ? 'consoleErrors' : 'networkFailures';
|
|
165
|
+
const report = assembleBlockedBrowserProofReport({
|
|
166
|
+
issueNumber,
|
|
167
|
+
artifactDir,
|
|
168
|
+
scenario: validated.scenario,
|
|
169
|
+
issue: {
|
|
170
|
+
kind: strictKind,
|
|
171
|
+
diagnostic: strictBlockers.join('; '),
|
|
172
|
+
requiredChanges: strictBlockers,
|
|
173
|
+
},
|
|
174
|
+
diagnostics: run.diagnostics,
|
|
175
|
+
});
|
|
176
|
+
await writeReportWithDiagnostics(worktreePath, reportPath, report, { kind: strictKind, diagnostic: strictBlockers.join('; ') });
|
|
177
|
+
return { status: 'blocked', reportPath };
|
|
178
|
+
}
|
|
179
|
+
const report = assembleBrowserAcceptanceProofReport({
|
|
180
|
+
issueNumber,
|
|
181
|
+
artifactDir,
|
|
182
|
+
scenario: validated.scenario,
|
|
183
|
+
checkpoints: run.checkpoints,
|
|
184
|
+
diagnostics: run.diagnostics,
|
|
185
|
+
workflow: workflowFromScenario(validated.scenario),
|
|
186
|
+
layoutFindings: ['Browser proof reviewed layout-relevant viewport artifacts.'],
|
|
187
|
+
copyFindings: ['Browser proof reviewed visible text through DOM and screenshot artifacts.'],
|
|
188
|
+
});
|
|
189
|
+
await writeJson(reportPath, report);
|
|
190
|
+
return { status: 'passed', reportPath };
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
const message = error instanceof Error ? error.message : 'browser proof failed';
|
|
194
|
+
const assertion = error instanceof Error && 'code' in error && error.code === 'ASSERTION_FAILED';
|
|
195
|
+
if (assertion) {
|
|
196
|
+
const report = needsReworkReport(issueNumber, artifactDir, validated.scenario, message);
|
|
197
|
+
await writeJson(reportPath, report);
|
|
198
|
+
return { status: 'needs-rework', reportPath };
|
|
199
|
+
}
|
|
200
|
+
const report = assembleBlockedBrowserProofReport({
|
|
201
|
+
issueNumber,
|
|
202
|
+
artifactDir,
|
|
203
|
+
scenario: validated.scenario,
|
|
204
|
+
issue: {
|
|
205
|
+
kind: /playwright/iu.test(message) ? 'playwrightPackage' : 'browserBinary',
|
|
206
|
+
diagnostic: message,
|
|
207
|
+
requiredChanges: [message],
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
await writeReportWithDiagnostics(worktreePath, reportPath, report, { kind: 'browserBinary', diagnostic: message });
|
|
211
|
+
return { status: 'blocked', reportPath };
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
export function createDefaultBrowserProofAdapterForTest(runtime) {
|
|
215
|
+
if ('chromium' in runtime) {
|
|
216
|
+
return defaultBrowserProofAdapter({ playwright: runtime });
|
|
217
|
+
}
|
|
218
|
+
return defaultBrowserProofAdapter(runtime);
|
|
219
|
+
}
|
|
220
|
+
function defaultBrowserProofAdapter(runtime = {}) {
|
|
221
|
+
return {
|
|
222
|
+
async run(input) {
|
|
223
|
+
const moduleName = 'playwright-core';
|
|
224
|
+
const playwright = runtime.playwright ?? await import(moduleName);
|
|
225
|
+
if (!playwright.chromium) {
|
|
226
|
+
throw new Error('Playwright chromium runtime is unavailable.');
|
|
227
|
+
}
|
|
228
|
+
const profileDir = input.env[browserProofRuntimeEnv.playwrightProfileDir] ?? join(input.proofDir, 'playwright-profile');
|
|
229
|
+
const executablePath = input.env[browserProofRuntimeEnv.browserExecutablePath]
|
|
230
|
+
?? firstExistingPath(runtime.installedBrowserPaths ?? installedBrowserExecutableCandidates(input.env));
|
|
231
|
+
let chromiumInstallAttempted = false;
|
|
232
|
+
const context = await launchBrowserContext({
|
|
233
|
+
chromium: playwright.chromium,
|
|
234
|
+
profileDir,
|
|
235
|
+
executablePath,
|
|
236
|
+
env: input.env,
|
|
237
|
+
installChromium: async (installEnv) => {
|
|
238
|
+
chromiumInstallAttempted = true;
|
|
239
|
+
await (runtime.installChromium ?? installPlaywrightChromium)(installEnv);
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
const page = await context.newPage();
|
|
243
|
+
const consoleErrors = [];
|
|
244
|
+
const networkFailures = [];
|
|
245
|
+
page.on('console', (message) => {
|
|
246
|
+
if (message.type() === 'error')
|
|
247
|
+
consoleErrors.push(message.text());
|
|
248
|
+
});
|
|
249
|
+
page.on('requestfailed', (request) => {
|
|
250
|
+
networkFailures.push(`${request.method()} ${request.url()} ${request.failure()?.errorText ?? ''}`.trim());
|
|
251
|
+
});
|
|
252
|
+
const checkpoints = [];
|
|
253
|
+
try {
|
|
254
|
+
for (const viewport of input.scenario.viewports) {
|
|
255
|
+
await page.setViewportSize({ width: viewport.width, height: viewport.height });
|
|
256
|
+
for (const step of input.scenario.steps) {
|
|
257
|
+
await runBrowserStep(page, input.scenario.baseUrl, step, input, checkpoints);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
finally {
|
|
262
|
+
await context.close();
|
|
263
|
+
}
|
|
264
|
+
const browserRuntime = { executablePath, chromiumInstallAttempted };
|
|
265
|
+
const diagnostics = await writeBrowserDiagnostics(input, consoleErrors, networkFailures, browserRuntime);
|
|
266
|
+
return { checkpoints, diagnostics, consoleErrors, networkFailures, browserRuntime };
|
|
267
|
+
},
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
async function launchBrowserContext(input) {
|
|
271
|
+
try {
|
|
272
|
+
return await input.chromium.launchPersistentContext(input.profileDir, {
|
|
273
|
+
headless: true,
|
|
274
|
+
...(input.executablePath ? { executablePath: input.executablePath } : {}),
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
catch (error) {
|
|
278
|
+
if (input.executablePath || !isMissingPlaywrightBrowser(error)) {
|
|
279
|
+
throw error;
|
|
280
|
+
}
|
|
281
|
+
await input.installChromium(input.env);
|
|
282
|
+
return input.chromium.launchPersistentContext(input.profileDir, { headless: true });
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
function installedBrowserExecutableCandidates(env) {
|
|
286
|
+
return [
|
|
287
|
+
...macBrowserExecutableCandidates(),
|
|
288
|
+
...linuxBrowserExecutableCandidates(env),
|
|
289
|
+
...windowsBrowserExecutableCandidates(env),
|
|
290
|
+
];
|
|
291
|
+
}
|
|
292
|
+
function macBrowserExecutableCandidates() {
|
|
293
|
+
return [
|
|
294
|
+
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
295
|
+
join(homedir(), 'Applications/Google Chrome.app/Contents/MacOS/Google Chrome'),
|
|
296
|
+
'/Applications/Chromium.app/Contents/MacOS/Chromium',
|
|
297
|
+
join(homedir(), 'Applications/Chromium.app/Contents/MacOS/Chromium'),
|
|
298
|
+
'/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
|
|
299
|
+
join(homedir(), 'Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge'),
|
|
300
|
+
];
|
|
301
|
+
}
|
|
302
|
+
function linuxBrowserExecutableCandidates(env) {
|
|
303
|
+
return [
|
|
304
|
+
findExecutableOnPath('google-chrome-stable', env),
|
|
305
|
+
findExecutableOnPath('google-chrome', env),
|
|
306
|
+
findExecutableOnPath('chromium', env),
|
|
307
|
+
findExecutableOnPath('chromium-browser', env),
|
|
308
|
+
findExecutableOnPath('microsoft-edge', env),
|
|
309
|
+
findExecutableOnPath('microsoft-edge-stable', env),
|
|
310
|
+
].filter((path) => Boolean(path));
|
|
311
|
+
}
|
|
312
|
+
function windowsBrowserExecutableCandidates(env) {
|
|
313
|
+
return [
|
|
314
|
+
env.LOCALAPPDATA ? join(env.LOCALAPPDATA, 'Google', 'Chrome', 'Application', 'chrome.exe') : undefined,
|
|
315
|
+
env.PROGRAMFILES ? join(env.PROGRAMFILES, 'Google', 'Chrome', 'Application', 'chrome.exe') : undefined,
|
|
316
|
+
env['PROGRAMFILES(X86)'] ? join(env['PROGRAMFILES(X86)'], 'Google', 'Chrome', 'Application', 'chrome.exe') : undefined,
|
|
317
|
+
env.LOCALAPPDATA ? join(env.LOCALAPPDATA, 'Microsoft', 'Edge', 'Application', 'msedge.exe') : undefined,
|
|
318
|
+
env.PROGRAMFILES ? join(env.PROGRAMFILES, 'Microsoft', 'Edge', 'Application', 'msedge.exe') : undefined,
|
|
319
|
+
env['PROGRAMFILES(X86)'] ? join(env['PROGRAMFILES(X86)'], 'Microsoft', 'Edge', 'Application', 'msedge.exe') : undefined,
|
|
320
|
+
].filter((path) => Boolean(path));
|
|
321
|
+
}
|
|
322
|
+
function findExecutableOnPath(command, env) {
|
|
323
|
+
const pathEntries = (env.PATH ?? '').split(delimiter).filter(Boolean);
|
|
324
|
+
const extensions = process.platform === 'win32'
|
|
325
|
+
? (env.PATHEXT ?? '.EXE;.CMD;.BAT;.COM').split(';')
|
|
326
|
+
: [''];
|
|
327
|
+
for (const pathEntry of pathEntries) {
|
|
328
|
+
for (const extension of extensions) {
|
|
329
|
+
const candidate = join(pathEntry, `${command}${extension}`);
|
|
330
|
+
if (existsSync(candidate))
|
|
331
|
+
return candidate;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return undefined;
|
|
335
|
+
}
|
|
336
|
+
function firstExistingPath(paths) {
|
|
337
|
+
return paths.find((path) => existsSync(path));
|
|
338
|
+
}
|
|
339
|
+
function isMissingPlaywrightBrowser(error) {
|
|
340
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
341
|
+
return /Executable doesn't exist/iu.test(message)
|
|
342
|
+
|| /run (?:the following command to )?download new browsers/iu.test(message)
|
|
343
|
+
|| /playwright install/iu.test(message);
|
|
344
|
+
}
|
|
345
|
+
async function installPlaywrightChromium(env) {
|
|
346
|
+
const cliPath = require.resolve('playwright-core/cli.js');
|
|
347
|
+
await new Promise((resolvePromise, reject) => {
|
|
348
|
+
execFile(process.execPath, [cliPath, 'install', 'chromium'], { env: { ...process.env, ...env } }, (error, stdout, stderr) => {
|
|
349
|
+
if (error) {
|
|
350
|
+
reject(new Error(`Playwright Chromium install failed: ${error.message}\n${stderr}\n${stdout}`.trim()));
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
resolvePromise();
|
|
354
|
+
});
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
async function runBrowserStep(page, baseUrl, step, input, checkpoints) {
|
|
358
|
+
switch (step.action) {
|
|
359
|
+
case 'navigate':
|
|
360
|
+
await page.goto(step.url ?? new URL(step.path ?? '/', baseUrl).toString());
|
|
361
|
+
break;
|
|
362
|
+
case 'click':
|
|
363
|
+
if (step.selector)
|
|
364
|
+
await page.click(step.selector);
|
|
365
|
+
else
|
|
366
|
+
await page.getByText(step.text ?? '').click();
|
|
367
|
+
break;
|
|
368
|
+
case 'fill':
|
|
369
|
+
await page.fill(step.selector, step.value);
|
|
370
|
+
break;
|
|
371
|
+
case 'press':
|
|
372
|
+
await page.press(step.selector, step.key);
|
|
373
|
+
break;
|
|
374
|
+
case 'waitForSelector':
|
|
375
|
+
await page.waitForSelector(step.selector);
|
|
376
|
+
break;
|
|
377
|
+
case 'waitForText':
|
|
378
|
+
await page.getByText(step.text).waitFor();
|
|
379
|
+
break;
|
|
380
|
+
case 'assertText': {
|
|
381
|
+
const text = step.selector ? await page.locator(step.selector).textContent() : step.text;
|
|
382
|
+
if (!text?.includes(step.text))
|
|
383
|
+
throw assertionError(`Expected text ${step.text} was missing`);
|
|
384
|
+
break;
|
|
385
|
+
}
|
|
386
|
+
case 'assertUrl':
|
|
387
|
+
if (!page.url().includes(step.expected))
|
|
388
|
+
throw assertionError(`Expected URL ${step.expected} but got ${page.url()}`);
|
|
389
|
+
break;
|
|
390
|
+
case 'screenshot':
|
|
391
|
+
await mkdir(dirname(browserProofArtifactFilePath(input.worktreePath, step.path, input.artifactDir, input.issueNumber)), { recursive: true });
|
|
392
|
+
await page.screenshot({ path: browserProofArtifactFilePath(input.worktreePath, step.path, input.artifactDir, input.issueNumber), fullPage: true });
|
|
393
|
+
checkpoints.push({ ...step, kind: 'screenshot', description: `Browser screenshot ${step.checkpointId}` });
|
|
394
|
+
break;
|
|
395
|
+
case 'domSnapshot': {
|
|
396
|
+
const html = await page.locator('body').evaluate((element) => element.outerHTML);
|
|
397
|
+
await mkdir(dirname(browserProofArtifactFilePath(input.worktreePath, step.path, input.artifactDir, input.issueNumber)), { recursive: true });
|
|
398
|
+
await writeFile(browserProofArtifactFilePath(input.worktreePath, step.path, input.artifactDir, input.issueNumber), html, 'utf8');
|
|
399
|
+
checkpoints.push({ ...step, kind: 'domSnapshot', description: `Browser DOM snapshot ${step.checkpointId}` });
|
|
400
|
+
break;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
async function writeBrowserDiagnostics(input, consoleErrors, networkFailures, browserRuntime) {
|
|
405
|
+
const consoleLogRef = browserProofArtifactPath(input.artifactDir, input.issueNumber, 'console.log');
|
|
406
|
+
const networkLogRef = browserProofArtifactPath(input.artifactDir, input.issueNumber, 'network.log');
|
|
407
|
+
const runSummaryRef = browserProofArtifactPath(input.artifactDir, input.issueNumber, 'browser-summary.json');
|
|
408
|
+
const consoleLogPath = browserProofArtifactFilePath(input.worktreePath, consoleLogRef, input.artifactDir, input.issueNumber);
|
|
409
|
+
const networkLogPath = browserProofArtifactFilePath(input.worktreePath, networkLogRef, input.artifactDir, input.issueNumber);
|
|
410
|
+
const runSummaryPath = browserProofArtifactFilePath(input.worktreePath, runSummaryRef, input.artifactDir, input.issueNumber);
|
|
411
|
+
await writeFile(consoleLogPath, consoleErrors.join('\n'), 'utf8');
|
|
412
|
+
await writeFile(networkLogPath, networkFailures.join('\n'), 'utf8');
|
|
413
|
+
await writeFile(runSummaryPath, JSON.stringify({
|
|
414
|
+
status: 'completed',
|
|
415
|
+
steps: input.scenario.steps.length,
|
|
416
|
+
browserRuntime: browserRuntime
|
|
417
|
+
? {
|
|
418
|
+
executablePath: browserRuntime.executablePath ?? null,
|
|
419
|
+
chromiumInstallAttempted: browserRuntime.chromiumInstallAttempted,
|
|
420
|
+
}
|
|
421
|
+
: undefined,
|
|
422
|
+
}, null, 2), 'utf8');
|
|
423
|
+
return {
|
|
424
|
+
consoleLogPath: consoleLogRef,
|
|
425
|
+
networkLogPath: networkLogRef,
|
|
426
|
+
runSummaryPath: runSummaryRef,
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
function needsReworkReport(issueNumber, artifactDir, scenario, message) {
|
|
430
|
+
const diagnosticPath = `${artifactDir}/issue-${issueNumber}/browser-proof-diagnostics.json`;
|
|
431
|
+
return {
|
|
432
|
+
status: 'needs-rework',
|
|
433
|
+
criteria: scenario.criteria.map((criterion) => ({
|
|
434
|
+
id: criterion.id,
|
|
435
|
+
description: criterion.description,
|
|
436
|
+
status: 'failed',
|
|
437
|
+
confidence: 'high',
|
|
438
|
+
reasoningSummary: message,
|
|
439
|
+
artifactRefs: [diagnosticPath],
|
|
440
|
+
})),
|
|
441
|
+
artifacts: [{ type: 'log', path: diagnosticPath, description: 'Browser assertion failure' }],
|
|
442
|
+
proofPhaseDiff: { allowedProofPaths: [diagnosticPath], forbiddenProductPaths: [] },
|
|
443
|
+
reworkRequest: {
|
|
444
|
+
summary: message,
|
|
445
|
+
requiredChanges: [message],
|
|
446
|
+
evidenceRefs: [diagnosticPath],
|
|
447
|
+
},
|
|
448
|
+
residualRisks: [],
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
async function loadScenario(path, baseUrl) {
|
|
452
|
+
try {
|
|
453
|
+
const scenario = JSON.parse(await readFile(path, 'utf8'));
|
|
454
|
+
return { ok: true, scenario: baseUrl ? { ...scenario, baseUrl } : scenario };
|
|
455
|
+
}
|
|
456
|
+
catch (error) {
|
|
457
|
+
const message = error instanceof Error && 'code' in error && error.code === 'ENOENT'
|
|
458
|
+
? `Browser proof scenario file was not found: ${path}`
|
|
459
|
+
: `Browser proof scenario is malformed: ${error instanceof Error ? error.message : String(error)}`;
|
|
460
|
+
return { ok: false, error: message };
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
function validateScenarioPath(path, proofDir) {
|
|
464
|
+
const resolvedPath = resolve(path);
|
|
465
|
+
const resolvedProofDir = resolve(proofDir);
|
|
466
|
+
const relativePath = relative(resolvedProofDir, resolvedPath);
|
|
467
|
+
const name = resolvedPath.split('/').at(-1) ?? '';
|
|
468
|
+
if (name === '.env' || name.startsWith('.env.')) {
|
|
469
|
+
return { ok: false, error: `Browser proof scenario path must not reference a secret file: ${path}` };
|
|
470
|
+
}
|
|
471
|
+
if (relativePath.length === 0 || relativePath.startsWith('..') || isAbsolute(relativePath)) {
|
|
472
|
+
return { ok: false, error: `Browser proof scenario path must stay under the proof directory: ${path}` };
|
|
473
|
+
}
|
|
474
|
+
return { ok: true };
|
|
475
|
+
}
|
|
476
|
+
async function ensureBrowserRuntimeDirs(env) {
|
|
477
|
+
const dirs = [
|
|
478
|
+
{ kind: 'profileDir', path: env[browserProofRuntimeEnv.playwrightProfileDir] },
|
|
479
|
+
{ kind: 'cacheDir', path: env[browserProofRuntimeEnv.browserCacheDir] },
|
|
480
|
+
];
|
|
481
|
+
for (const dir of dirs) {
|
|
482
|
+
if (!dir.path)
|
|
483
|
+
continue;
|
|
484
|
+
try {
|
|
485
|
+
await mkdir(dir.path, { recursive: true });
|
|
486
|
+
}
|
|
487
|
+
catch (error) {
|
|
488
|
+
return {
|
|
489
|
+
ok: false,
|
|
490
|
+
kind: dir.kind,
|
|
491
|
+
error: `Browser proof could not prepare ${dir.kind}: ${error instanceof Error ? error.message : String(error)}`,
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
return { ok: true };
|
|
496
|
+
}
|
|
497
|
+
function browserProofArtifactFilePath(worktreePath, artifactPath, artifactDir, issueNumber) {
|
|
498
|
+
return resolve(worktreePath, assertBrowserProofArtifactPath(artifactPath, artifactDir, issueNumber));
|
|
499
|
+
}
|
|
500
|
+
function workflowFromScenario(scenario) {
|
|
501
|
+
return {
|
|
502
|
+
entrypoint: scenario.baseUrl,
|
|
503
|
+
path: scenario.steps.map((step) => step.action),
|
|
504
|
+
screenState: 'Browser proof scenario completed with mapped checkpoint evidence.',
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
async function writeReportWithDiagnostics(worktreePath, reportPath, report, diagnostic) {
|
|
508
|
+
const diagnosticPath = report.artifacts[0]?.path;
|
|
509
|
+
if (diagnosticPath) {
|
|
510
|
+
await mkdir(dirname(join(worktreePath, diagnosticPath)), { recursive: true });
|
|
511
|
+
await writeFile(join(worktreePath, diagnosticPath), JSON.stringify(diagnostic, null, 2), 'utf8');
|
|
512
|
+
}
|
|
513
|
+
await writeJson(reportPath, report);
|
|
514
|
+
}
|
|
515
|
+
async function writeJson(path, value) {
|
|
516
|
+
await mkdir(dirname(path), { recursive: true });
|
|
517
|
+
await writeFile(path, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
|
|
518
|
+
}
|
|
519
|
+
function assertionError(message) {
|
|
520
|
+
return Object.assign(new Error(message), { code: 'ASSERTION_FAILED' });
|
|
521
|
+
}
|
|
522
|
+
function booleanFromEnv(value) {
|
|
523
|
+
if (value === undefined)
|
|
524
|
+
return undefined;
|
|
525
|
+
return /^(?:1|true|yes)$/iu.test(value);
|
|
526
|
+
}
|
|
527
|
+
function envListFromEnv(value) {
|
|
528
|
+
if (!value)
|
|
529
|
+
return undefined;
|
|
530
|
+
return value.split(/[,\n]/u).map((entry) => entry.trim()).filter(Boolean);
|
|
531
|
+
}
|
|
532
|
+
//# sourceMappingURL=browser-visual-proof-command.js.map
|