@e2edev/e2e 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -15
- package/dist/agent/act.d.ts +1 -1
- package/dist/agent/act.js +12 -36
- package/dist/agent/act.js.map +1 -1
- package/dist/agent/actions.d.ts +2 -2
- package/dist/agent/actions.d.ts.map +1 -1
- package/dist/agent/actions.js +1 -1
- package/dist/agent/default-agent.d.ts +3 -29
- package/dist/agent/default-agent.d.ts.map +1 -1
- package/dist/agent/default-agent.js +4 -11
- package/dist/agent/default-agent.js.map +1 -1
- package/dist/agent/error.d.ts +0 -2
- package/dist/agent/error.d.ts.map +1 -1
- package/dist/agent/error.js +1 -3
- package/dist/agent/error.js.map +1 -1
- package/dist/agent/executor.d.ts +15 -49
- package/dist/agent/executor.d.ts.map +1 -1
- package/dist/agent/executor.js +0 -1
- package/dist/agent/executor.js.map +1 -1
- package/dist/agent/index.js +2 -2
- package/dist/agent/invocation.d.ts +6 -4
- package/dist/agent/invocation.d.ts.map +1 -1
- package/dist/agent/invocation.js +2 -2
- package/dist/agent/invocation.js.map +1 -1
- package/dist/agent/ledger.d.ts +16 -11
- package/dist/agent/ledger.d.ts.map +1 -1
- package/dist/agent/ledger.js +5 -8
- package/dist/agent/ledger.js.map +1 -1
- package/dist/agent/observation.d.ts +6 -6
- package/dist/agent/observation.d.ts.map +1 -1
- package/dist/agent/observation.js +5 -5
- package/dist/agent/observation.js.map +1 -1
- package/dist/agent/phases.d.ts +3 -3
- package/dist/agent/phases.d.ts.map +1 -1
- package/dist/agent/phases.js +4 -4
- package/dist/agent/phases.js.map +1 -1
- package/dist/agent/primitives.d.ts +4 -25
- package/dist/agent/primitives.d.ts.map +1 -1
- package/dist/agent/primitives.js +3 -36
- package/dist/agent/primitives.js.map +1 -1
- package/dist/agent/protocol.d.ts +1 -1
- package/dist/agent/protocol.js +1 -1
- package/dist/agent/public.d.ts +7 -9
- package/dist/agent/public.d.ts.map +1 -1
- package/dist/agent/public.js +6 -7
- package/dist/agent/public.js.map +1 -1
- package/dist/agent/replay.d.ts +1 -1
- package/dist/agent/replay.d.ts.map +1 -1
- package/dist/agent/secrets.d.ts +2 -2
- package/dist/agent/secrets.d.ts.map +1 -1
- package/dist/agent/secrets.js +3 -3
- package/dist/agent/secrets.js.map +1 -1
- package/dist/agent/tool-loop.d.ts +2 -30
- package/dist/agent/tool-loop.d.ts.map +1 -1
- package/dist/agent/tool-loop.js +9 -33
- package/dist/agent/tool-loop.js.map +1 -1
- package/dist/agent/tool.d.ts +4 -5
- package/dist/agent/tool.d.ts.map +1 -1
- package/dist/agent/tool.js +2 -7
- package/dist/agent/tool.js.map +1 -1
- package/dist/cache/anchors.d.ts +1 -1
- package/dist/cache/anchors.d.ts.map +1 -1
- package/dist/cache/identity.d.ts +8 -8
- package/dist/cache/identity.d.ts.map +1 -1
- package/dist/cache/identity.js +5 -5
- package/dist/cache/identity.js.map +1 -1
- package/dist/cache/relocate.d.ts +1 -1
- package/dist/cache/relocate.d.ts.map +1 -1
- package/dist/cli/index.js +3 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init/{backends.d.ts → engines.d.ts} +12 -13
- package/dist/cli/init/engines.d.ts.map +1 -0
- package/dist/cli/init/{backends.js → engines.js} +13 -14
- package/dist/cli/init/engines.js.map +1 -0
- package/dist/cli/init/scaffold.d.ts +3 -3
- package/dist/cli/init/scaffold.d.ts.map +1 -1
- package/dist/cli/init/scaffold.js +9 -9
- package/dist/cli/init/scaffold.js.map +1 -1
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +11 -10
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/signals.d.ts +1 -1
- package/dist/cli/signals.d.ts.map +1 -1
- package/dist/cli/signals.js +1 -1
- package/dist/cli/signals.js.map +1 -1
- package/dist/collect/collect.d.ts +32 -3
- package/dist/collect/collect.d.ts.map +1 -1
- package/dist/collect/collect.js +95 -17
- package/dist/collect/collect.js.map +1 -1
- package/dist/collect/registry.js +1 -1
- package/dist/collect/registry.js.map +1 -1
- package/dist/collect/select.d.ts.map +1 -1
- package/dist/collect/select.js +9 -5
- package/dist/collect/select.js.map +1 -1
- package/dist/config/app.d.ts +76 -19
- package/dist/config/app.d.ts.map +1 -1
- package/dist/config/app.js +157 -102
- package/dist/config/app.js.map +1 -1
- package/dist/config/resolve.d.ts +6 -5
- package/dist/config/resolve.d.ts.map +1 -1
- package/dist/config/resolve.js +35 -32
- package/dist/config/resolve.js.map +1 -1
- package/dist/{backend → engine}/contract.d.ts +26 -18
- package/dist/engine/contract.d.ts.map +1 -0
- package/dist/{backend → engine}/contract.js +18 -18
- package/dist/engine/contract.js.map +1 -0
- package/dist/{backend → engine}/index.d.ts +141 -79
- package/dist/engine/index.d.ts.map +1 -0
- package/dist/{backend → engine}/index.js +60 -35
- package/dist/engine/index.js.map +1 -0
- package/dist/engine/session.d.ts +23 -0
- package/dist/engine/session.d.ts.map +1 -0
- package/dist/{backend → engine}/session.js +55 -51
- package/dist/engine/session.js.map +1 -0
- package/dist/{backend → engine}/surface.d.ts +11 -11
- package/dist/engine/surface.d.ts.map +1 -0
- package/dist/{backend → engine}/surface.js +2 -2
- package/dist/engine/surface.js.map +1 -0
- package/dist/engine/timing.d.ts.map +1 -0
- package/dist/engine/timing.js +8 -0
- package/dist/engine/timing.js.map +1 -0
- package/dist/expect/index.js +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/brands.d.ts +1 -1
- package/dist/internal/brands.d.ts.map +1 -1
- package/dist/internal/brands.js +1 -1
- package/dist/internal/brands.js.map +1 -1
- package/dist/internal/errors.d.ts +16 -16
- package/dist/internal/errors.d.ts.map +1 -1
- package/dist/internal/errors.js +39 -39
- package/dist/internal/errors.js.map +1 -1
- package/dist/internal/objects.d.ts +21 -0
- package/dist/internal/objects.d.ts.map +1 -0
- package/dist/internal/objects.js +14 -0
- package/dist/internal/objects.js.map +1 -0
- package/dist/internal/urls.d.ts +1 -1
- package/dist/internal/urls.d.ts.map +1 -1
- package/dist/internal/urls.js +6 -1
- package/dist/internal/urls.js.map +1 -1
- package/dist/locator/engine.d.ts +6 -6
- package/dist/locator/engine.d.ts.map +1 -1
- package/dist/locator/engine.js +13 -13
- package/dist/locator/engine.js.map +1 -1
- package/dist/locator/expression.d.ts +4 -2
- package/dist/locator/expression.d.ts.map +1 -1
- package/dist/locator/expression.js +24 -4
- package/dist/locator/expression.js.map +1 -1
- package/dist/locator/screen.d.ts +1 -1
- package/dist/locator/screen.d.ts.map +1 -1
- package/dist/locator/screen.js +2 -2
- package/dist/locator/screen.js.map +1 -1
- package/dist/report/build.d.ts +9 -8
- package/dist/report/build.d.ts.map +1 -1
- package/dist/report/build.js +21 -18
- package/dist/report/build.js.map +1 -1
- package/dist/report/code-frame.d.ts +8 -4
- package/dist/report/code-frame.d.ts.map +1 -1
- package/dist/report/code-frame.js +23 -18
- package/dist/report/code-frame.js.map +1 -1
- package/dist/report/debug-steps.js +1 -1
- package/dist/report/debug-steps.js.map +1 -1
- package/dist/report/format.d.ts +73 -0
- package/dist/report/format.d.ts.map +1 -0
- package/dist/report/format.js +147 -0
- package/dist/report/format.js.map +1 -0
- package/dist/report/list.d.ts +96 -47
- package/dist/report/list.d.ts.map +1 -1
- package/dist/report/list.js +485 -244
- package/dist/report/list.js.map +1 -1
- package/dist/report/live-window.d.ts +28 -0
- package/dist/report/live-window.d.ts.map +1 -0
- package/dist/report/live-window.js +102 -0
- package/dist/report/live-window.js.map +1 -0
- package/dist/run/declared-processes.d.ts +30 -0
- package/dist/run/declared-processes.d.ts.map +1 -0
- package/dist/run/declared-processes.js +61 -0
- package/dist/run/declared-processes.js.map +1 -0
- package/dist/run/events.d.ts +30 -2
- package/dist/run/events.d.ts.map +1 -1
- package/dist/run/events.js.map +1 -1
- package/dist/run/execute.d.ts +17 -13
- package/dist/run/execute.d.ts.map +1 -1
- package/dist/run/execute.js +61 -62
- package/dist/run/execute.js.map +1 -1
- package/dist/run/fixture-recording.d.ts +10 -4
- package/dist/run/fixture-recording.d.ts.map +1 -1
- package/dist/run/fixture-recording.js +19 -6
- package/dist/run/fixture-recording.js.map +1 -1
- package/dist/run/fixtures.d.ts +1 -1
- package/dist/run/fixtures.d.ts.map +1 -1
- package/dist/run/fixtures.js +33 -41
- package/dist/run/fixtures.js.map +1 -1
- package/dist/run/in-process.d.ts +1 -1
- package/dist/run/in-process.js +1 -1
- package/dist/run/managed-process.d.ts +39 -9
- package/dist/run/managed-process.d.ts.map +1 -1
- package/dist/run/managed-process.js +102 -34
- package/dist/run/managed-process.js.map +1 -1
- package/dist/run/runner.d.ts +1 -1
- package/dist/run/runner.d.ts.map +1 -1
- package/dist/run/runner.js +67 -31
- package/dist/run/runner.js.map +1 -1
- package/dist/run/scheduler.d.ts +4 -3
- package/dist/run/scheduler.d.ts.map +1 -1
- package/dist/run/scheduler.js +5 -4
- package/dist/run/scheduler.js.map +1 -1
- package/dist/run/serial.d.ts +9 -1
- package/dist/run/serial.d.ts.map +1 -1
- package/dist/run/serial.js +2 -0
- package/dist/run/serial.js.map +1 -1
- package/dist/run/sessions.d.ts +9 -9
- package/dist/run/sessions.d.ts.map +1 -1
- package/dist/run/sessions.js +10 -10
- package/dist/run/sessions.js.map +1 -1
- package/dist/run/steps.d.ts +1 -1
- package/dist/run/steps.d.ts.map +1 -1
- package/dist/run/worker/entry.d.ts +1 -1
- package/dist/run/worker/entry.js +2 -2
- package/dist/run/worker/entry.js.map +1 -1
- package/dist/run/worker/protocol.d.ts +18 -6
- package/dist/run/worker/protocol.d.ts.map +1 -1
- package/dist/run/worker/protocol.js +1 -1
- package/dist/run/worker/protocol.js.map +1 -1
- package/dist/run/worker/session.d.ts +2 -2
- package/dist/run/worker/session.d.ts.map +1 -1
- package/dist/run/worker/session.js +5 -3
- package/dist/run/worker/session.js.map +1 -1
- package/dist/types.d.ts +36 -37
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -5
- package/dist/backend/contract.d.ts.map +0 -1
- package/dist/backend/contract.js.map +0 -1
- package/dist/backend/index.d.ts.map +0 -1
- package/dist/backend/index.js.map +0 -1
- package/dist/backend/session.d.ts +0 -23
- package/dist/backend/session.d.ts.map +0 -1
- package/dist/backend/session.js.map +0 -1
- package/dist/backend/surface.d.ts.map +0 -1
- package/dist/backend/surface.js.map +0 -1
- package/dist/backend/timing.d.ts.map +0 -1
- package/dist/backend/timing.js +0 -8
- package/dist/backend/timing.js.map +0 -1
- package/dist/cli/init/backends.d.ts.map +0 -1
- package/dist/cli/init/backends.js.map +0 -1
- package/dist/report/live-status.d.ts +0 -51
- package/dist/report/live-status.d.ts.map +0 -1
- package/dist/report/live-status.js +0 -163
- package/dist/report/live-status.js.map +0 -1
- package/dist/run/fixture-legacy.d.ts +0 -36
- package/dist/run/fixture-legacy.d.ts.map +0 -1
- package/dist/run/fixture-legacy.js +0 -143
- package/dist/run/fixture-legacy.js.map +0 -1
- /package/dist/{backend → engine}/timing.d.ts +0 -0
package/dist/config/app.js
CHANGED
|
@@ -1,98 +1,74 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* Per-target app resolution (spec 05-config.md): what an engine declares
|
|
3
|
+
* about the app it drives, validated where an error can name the target.
|
|
4
|
+
*/
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
2
7
|
import { ConfigurationError } from '../internal/errors.js';
|
|
8
|
+
import { obj } from '../internal/objects.js';
|
|
3
9
|
import { isImplicitTestHost, normalizeBaseUrl } from '../internal/urls.js';
|
|
4
10
|
import { httpUrl, positiveInt } from './validate.js';
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
const services = resolveServices(raw.app?.services);
|
|
25
|
-
const rawUrl = raw.app?.url ?? env['APP_URL'];
|
|
26
|
-
if (rawUrl === undefined || rawUrl === '') {
|
|
27
|
-
// Not every surface has an app URL to point at (a device, a desktop
|
|
28
|
-
// shell): a synthetic loopback base keeps navigation resolution and
|
|
29
|
-
// identity digests defined, and `app.open()` fails loud with
|
|
30
|
-
// APP_URL_REQUIRED the moment a test actually needs one. A configured
|
|
31
|
-
// app command without a URL is still a mistake worth failing early on.
|
|
32
|
-
const orphaned = Object.keys(raw.app ?? {}).filter((key) => !URL_FREE_APP_KEYS.has(key));
|
|
33
|
-
if (orphaned.length > 0) {
|
|
34
|
-
throw new ConfigurationError('INVALID_CONFIG', `app.${orphaned[0]} requires app.url: set app.url in e2e.config.ts or the APP_URL environment variable`);
|
|
35
|
-
}
|
|
36
|
-
if (raw.app?.command === undefined) {
|
|
37
|
-
return {
|
|
38
|
-
configured: false,
|
|
39
|
-
base: normalizeBaseUrl('http://127.0.0.1:1'),
|
|
40
|
-
readyUrl: 'http://127.0.0.1:1/',
|
|
41
|
-
allowedOrigins: [],
|
|
42
|
-
environment: 'test',
|
|
43
|
-
command: undefined,
|
|
44
|
-
services,
|
|
45
|
-
identity: undefined,
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
throw new ConfigurationError('APP_URL_REQUIRED', 'an app URL is required alongside app.command: set app.url in e2e.config.ts or the APP_URL environment variable');
|
|
11
|
+
const ENVIRONMENTS = new Set(['test', 'staging', 'production']);
|
|
12
|
+
/**
|
|
13
|
+
* Resolves one target's app from its engine's `app` declaration. Every fact
|
|
14
|
+
* is optional: a URL normalizes like any base URL, origins must be serialized
|
|
15
|
+
* origins, a command needs something to poll, services need one readiness
|
|
16
|
+
* contract each, and the identity defaults to where the app is served when
|
|
17
|
+
* the engine gives none. `projectRoot` anchors every command's `log` path.
|
|
18
|
+
*/
|
|
19
|
+
export function resolveTargetApp(targetName, engine, projectRoot) {
|
|
20
|
+
const declared = engine?.app ?? {};
|
|
21
|
+
const where = `target "${targetName}" engine ${engine?.name ?? 'none'}`;
|
|
22
|
+
const base = declared.url === undefined ? undefined : normalizeBaseUrl(declared.url);
|
|
23
|
+
const environment = declared.environment ??
|
|
24
|
+
(base !== undefined && !isImplicitTestHost(new URL(base.href).hostname) ? 'production' : 'test');
|
|
25
|
+
if (!ENVIRONMENTS.has(environment)) {
|
|
26
|
+
throw new ConfigurationError('INVALID_CONFIG', `${where} declares invalid app.environment "${String(environment)}"`);
|
|
49
27
|
}
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
// digest, not a gate: a loopback, `.localhost`, or `.test` host is `test`,
|
|
54
|
-
// any other host is `production` unless the config says otherwise.
|
|
55
|
-
const environment = raw.app?.environment ?? (isImplicitTestHost(baseHost) ? 'test' : 'production');
|
|
56
|
-
if (!['test', 'staging', 'production'].includes(environment)) {
|
|
57
|
-
throw new ConfigurationError('INVALID_CONFIG', `invalid app.environment "${environment}"`);
|
|
28
|
+
const allowedOrigins = declared.allowedOrigins ?? (base === undefined ? [] : [base.origin]);
|
|
29
|
+
if (!Array.isArray(allowedOrigins)) {
|
|
30
|
+
throw new ConfigurationError('INVALID_CONFIG', `${where} app.allowedOrigins must be an array`);
|
|
58
31
|
}
|
|
59
|
-
const allowedOrigins = raw.app?.allowedOrigins ?? [base.origin];
|
|
60
32
|
for (const origin of allowedOrigins) {
|
|
61
33
|
let parsed;
|
|
62
34
|
try {
|
|
63
35
|
parsed = new URL(origin);
|
|
64
36
|
}
|
|
65
37
|
catch {
|
|
66
|
-
throw new ConfigurationError('INVALID_CONFIG',
|
|
38
|
+
throw new ConfigurationError('INVALID_CONFIG', `${where} declares an invalid allowed origin: ${origin}`);
|
|
67
39
|
}
|
|
68
40
|
if (parsed.origin !== origin) {
|
|
69
|
-
throw new ConfigurationError('INVALID_CONFIG',
|
|
41
|
+
throw new ConfigurationError('INVALID_CONFIG', `${where} allowed origin must be a serialized origin, got ${origin} (expected ${parsed.origin})`);
|
|
70
42
|
}
|
|
71
43
|
}
|
|
72
|
-
const
|
|
73
|
-
if (command !== undefined)
|
|
74
|
-
validateCommand(command, 'app.command');
|
|
75
|
-
const identity = raw.app?.identity;
|
|
44
|
+
const identity = declared.identity;
|
|
76
45
|
if (identity !== undefined && (typeof identity !== 'string' || identity.trim() === '')) {
|
|
77
|
-
throw new ConfigurationError('INVALID_CONFIG',
|
|
46
|
+
throw new ConfigurationError('INVALID_CONFIG', `${where} app.identity must be a non-empty string`);
|
|
47
|
+
}
|
|
48
|
+
const command = declared.command;
|
|
49
|
+
if (command !== undefined)
|
|
50
|
+
validateCommand(command, `${where} app.command`, projectRoot);
|
|
51
|
+
const readyUrl = httpUrl(declared.readyUrl, `${where} app.readyUrl`) ?? base?.href;
|
|
52
|
+
if (command !== undefined && readyUrl === undefined) {
|
|
53
|
+
throw new ConfigurationError('APP_URL_REQUIRED', `${where} declares app.command without a URL to poll: declare url or readyUrl beside it`);
|
|
78
54
|
}
|
|
79
55
|
return {
|
|
80
|
-
configured: true,
|
|
81
56
|
base,
|
|
82
|
-
readyUrl: httpUrl(raw.app?.readyUrl, 'app.readyUrl') ?? base.href,
|
|
83
57
|
allowedOrigins,
|
|
84
58
|
environment,
|
|
59
|
+
identity: identity ?? (base === undefined ? undefined : `${base.origin}${base.basePath}`),
|
|
85
60
|
command,
|
|
86
|
-
|
|
87
|
-
|
|
61
|
+
readyUrl: command === undefined ? undefined : readyUrl,
|
|
62
|
+
services: resolveServices(declared.services, projectRoot, `${where} app.services`),
|
|
88
63
|
};
|
|
89
64
|
}
|
|
90
65
|
/**
|
|
91
|
-
* The shape every spawned command shares: a non-empty executable
|
|
92
|
-
*
|
|
93
|
-
* make the readiness loop spin
|
|
66
|
+
* The shape every spawned command shares: a non-empty executable, when set
|
|
67
|
+
* positive integer timeouts, and when set a `log` path inside the project
|
|
68
|
+
* root. A NaN or infinite budget would otherwise make the readiness loop spin
|
|
69
|
+
* without a deadline; a log outside the root would let config write anywhere.
|
|
94
70
|
*/
|
|
95
|
-
function validateCommand(command, label) {
|
|
71
|
+
function validateCommand(command, label, projectRoot) {
|
|
96
72
|
if (typeof command !== 'object' || command === null) {
|
|
97
73
|
throw new ConfigurationError('INVALID_CONFIG', `${label} must be an object`);
|
|
98
74
|
}
|
|
@@ -101,42 +77,119 @@ function validateCommand(command, label) {
|
|
|
101
77
|
}
|
|
102
78
|
positiveInt(command.startupTimeout, `${label}.startupTimeout`);
|
|
103
79
|
positiveInt(command.shutdownTimeout, `${label}.shutdownTimeout`);
|
|
80
|
+
if (command.log !== undefined) {
|
|
81
|
+
if (typeof command.log !== 'string' || command.log.trim() === '') {
|
|
82
|
+
throw new ConfigurationError('INVALID_CONFIG', `${label}.log must be a non-empty path`);
|
|
83
|
+
}
|
|
84
|
+
if (!insideProjectRoot(projectRoot, path.resolve(projectRoot, command.log))) {
|
|
85
|
+
throw new ConfigurationError('INVALID_CONFIG', `${label}.log must be a file inside the project root, got ${JSON.stringify(command.log)}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (command.reuseExisting !== undefined && typeof command.reuseExisting !== 'boolean') {
|
|
89
|
+
throw new ConfigurationError('INVALID_CONFIG', `${label}.reuseExisting must be a boolean`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/** Only a command with a URL to probe can find something already answering there. */
|
|
93
|
+
function rejectReuse(command, label, reason) {
|
|
94
|
+
if (command.reuseExisting !== undefined) {
|
|
95
|
+
throw new ConfigurationError('INVALID_CONFIG', `${label}.reuseExisting needs readyUrl: ${reason}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* `target` with the symlinks of its deepest existing ancestor resolved and the
|
|
100
|
+
* missing tail appended as written. A log file usually does not exist yet,
|
|
101
|
+
* but the directory a symlink points at does, so this is what the runner
|
|
102
|
+
* would actually open.
|
|
103
|
+
*/
|
|
104
|
+
function realpathOfExisting(target) {
|
|
105
|
+
const tail = [];
|
|
106
|
+
let current = target;
|
|
107
|
+
for (;;) {
|
|
108
|
+
try {
|
|
109
|
+
return path.join(fs.realpathSync(current), ...tail);
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
const parent = path.dirname(current);
|
|
113
|
+
if (parent === current)
|
|
114
|
+
return target;
|
|
115
|
+
tail.unshift(path.basename(current));
|
|
116
|
+
current = parent;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
104
119
|
}
|
|
105
|
-
/**
|
|
106
|
-
|
|
107
|
-
|
|
120
|
+
/**
|
|
121
|
+
* Whether `target` is a path strictly below the project root once both are
|
|
122
|
+
* resolved through the filesystem: an in-project symlink pointing outside is
|
|
123
|
+
* rejected, a symlinked project root still counts as the root, and a name
|
|
124
|
+
* that merely starts with `..` (`..logs/out.log`) is an ordinary entry.
|
|
125
|
+
*/
|
|
126
|
+
function insideProjectRoot(projectRoot, target) {
|
|
127
|
+
const relative = path.relative(realpathOfExisting(projectRoot), realpathOfExisting(target));
|
|
128
|
+
return (relative !== '' &&
|
|
129
|
+
relative !== '..' &&
|
|
130
|
+
!relative.startsWith(`..${path.sep}`) &&
|
|
131
|
+
!path.isAbsolute(relative));
|
|
132
|
+
}
|
|
133
|
+
/** The longest `name` a service may carry; a label, not a description. */
|
|
134
|
+
const SERVICE_NAME_MAX_LENGTH = 64;
|
|
135
|
+
/**
|
|
136
|
+
* The name a service goes by in errors, reporter output, and the report: the
|
|
137
|
+
* explicit `name`, trimmed, or the executable's base name (`docker`, `pnpm`,
|
|
138
|
+
* and for a shell wrapper just `sh`, which is when an explicit name earns its
|
|
139
|
+
* keep). Explicit names must be unique so two failures never read alike.
|
|
140
|
+
*/
|
|
141
|
+
function serviceName(service, position, taken) {
|
|
142
|
+
if (service.name === undefined)
|
|
143
|
+
return path.basename(service.executable);
|
|
144
|
+
const name = typeof service.name === 'string' ? service.name.trim() : '';
|
|
145
|
+
if (name.length === 0 || name.length > SERVICE_NAME_MAX_LENGTH) {
|
|
146
|
+
throw new ConfigurationError('INVALID_CONFIG', `${position}.name must be a non-empty string of at most ${SERVICE_NAME_MAX_LENGTH} characters`);
|
|
147
|
+
}
|
|
148
|
+
if (taken.has(name)) {
|
|
149
|
+
throw new ConfigurationError('INVALID_CONFIG', `${position}.name "${name}" is already used by another service; service names must be unique`);
|
|
150
|
+
}
|
|
151
|
+
taken.add(name);
|
|
152
|
+
return name;
|
|
108
153
|
}
|
|
109
154
|
/**
|
|
110
|
-
* Resolves `
|
|
111
|
-
* readiness contract (`readyUrl` or `waitForExit`), and a `teardown` is a
|
|
155
|
+
* Resolves a declaration's `services`: every service is a command with exactly
|
|
156
|
+
* one readiness contract (`readyUrl` or `waitForExit`), and a `teardown` is a
|
|
112
157
|
* command of its own. The service fields that only steer the runner
|
|
113
|
-
* (`readyUrl`, `waitForExit`, `teardown`) are lifted out of the
|
|
158
|
+
* (`name`, `readyUrl`, `waitForExit`, `teardown`) are lifted out of the
|
|
159
|
+
* command; `reuseExisting` stays on it, and only a `readyUrl` service may set it.
|
|
160
|
+
* `projectRoot` anchors each `log` path; `prefix` names the declaring target
|
|
161
|
+
* in errors, so a failing service is traceable to the engine that declared it.
|
|
114
162
|
*/
|
|
115
|
-
export function resolveServices(raw) {
|
|
163
|
+
export function resolveServices(raw, projectRoot, prefix = 'app.services') {
|
|
116
164
|
if (raw === undefined)
|
|
117
165
|
return [];
|
|
118
166
|
if (!Array.isArray(raw)) {
|
|
119
|
-
throw new ConfigurationError('INVALID_CONFIG',
|
|
167
|
+
throw new ConfigurationError('INVALID_CONFIG', `${prefix} must be an array`);
|
|
120
168
|
}
|
|
169
|
+
const names = new Set();
|
|
121
170
|
return raw.map((service, index) => {
|
|
122
|
-
const position =
|
|
123
|
-
validateCommand(service, position);
|
|
124
|
-
const { readyUrl: rawReadyUrl, waitForExit, teardown, ...command } = service;
|
|
171
|
+
const position = `${prefix}[${index}]`;
|
|
172
|
+
validateCommand(service, position, projectRoot);
|
|
173
|
+
const { name: _name, readyUrl: rawReadyUrl, waitForExit, teardown, ...command } = service;
|
|
125
174
|
const readyUrl = httpUrl(rawReadyUrl, `${position}.readyUrl`);
|
|
126
175
|
if ((readyUrl !== undefined) === (waitForExit === true)) {
|
|
127
176
|
throw new ConfigurationError('INVALID_CONFIG', `${position} needs exactly one readiness contract: set readyUrl or waitForExit: true`);
|
|
128
177
|
}
|
|
129
178
|
const readiness = readyUrl === undefined ? { waitForExit: true } : { readyUrl };
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
179
|
+
if (readyUrl === undefined)
|
|
180
|
+
rejectReuse(command, position, 'a waitForExit service has nothing to reuse');
|
|
181
|
+
const name = serviceName(service, position, names);
|
|
182
|
+
const label = `service "${name}"`;
|
|
183
|
+
if (teardown !== undefined) {
|
|
184
|
+
validateCommand(teardown, `${position}.teardown`, projectRoot);
|
|
185
|
+
rejectReuse(teardown, `${position}.teardown`, 'a teardown command has nothing to reuse');
|
|
186
|
+
}
|
|
133
187
|
return {
|
|
134
188
|
label,
|
|
189
|
+
name: service.name === undefined ? undefined : name,
|
|
135
190
|
command,
|
|
136
191
|
readiness,
|
|
137
|
-
teardown: teardown === undefined
|
|
138
|
-
? undefined
|
|
139
|
-
: { label: `${label} teardown (${commandLine(teardown)})`, command: teardown },
|
|
192
|
+
teardown: teardown === undefined ? undefined : { label: `${label} teardown`, command: teardown },
|
|
140
193
|
};
|
|
141
194
|
});
|
|
142
195
|
}
|
|
@@ -147,22 +200,24 @@ function digestCommand(command) {
|
|
|
147
200
|
return { ...rest, env: Object.fromEntries(Object.keys(env).map((key) => [key, { envName: key }])) };
|
|
148
201
|
}
|
|
149
202
|
/**
|
|
150
|
-
* The
|
|
151
|
-
*
|
|
152
|
-
*
|
|
203
|
+
* The declarative part of an engine's `app` manifest as it enters the config
|
|
204
|
+
* digest: hooks stripped, and every `command.env`, service env, and service
|
|
205
|
+
* teardown env value replaced by `{ envName: key }`, so no environment value
|
|
206
|
+
* contributes to the digest.
|
|
153
207
|
*/
|
|
154
|
-
export function
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
208
|
+
export function digestAppDeclaration(app) {
|
|
209
|
+
const { url, allowedOrigins, environment, identity, command, readyUrl, services } = app;
|
|
210
|
+
return obj({
|
|
211
|
+
url,
|
|
212
|
+
allowedOrigins,
|
|
213
|
+
environment,
|
|
214
|
+
identity,
|
|
215
|
+
readyUrl,
|
|
216
|
+
command: command === undefined ? undefined : digestCommand(command),
|
|
217
|
+
services: services?.map(({ teardown, ...service }) => obj({
|
|
218
|
+
...digestCommand(service),
|
|
219
|
+
teardown: teardown === undefined ? undefined : digestCommand(teardown),
|
|
220
|
+
})),
|
|
221
|
+
});
|
|
167
222
|
}
|
|
168
223
|
//# sourceMappingURL=app.js.map
|
package/dist/config/app.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.js","sourceRoot":"","sources":["../../src/config/app.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"app.js","sourceRoot":"","sources":["../../src/config/app.ts"],"names":[],"mappings":"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"}
|
package/dist/config/resolve.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Config validation, defaults, and resolution (spec 05-config.md). */
|
|
2
2
|
import type { ArtifactStore, CacheMode, E2EConfig, Platform, SecretProvider, TraceCacheStore } from '../types.ts';
|
|
3
|
-
import { type
|
|
3
|
+
import { type EngineHandle } from '../engine/index.ts';
|
|
4
4
|
import { type ResolvedAgentConfig, type ResolvedLimits } from './agent.ts';
|
|
5
5
|
import { type ResolvedApp } from './app.ts';
|
|
6
6
|
export type { ResolvedAgentConfig, ResolvedLimits } from './agent.ts';
|
|
@@ -9,8 +9,10 @@ export interface ResolvedTarget {
|
|
|
9
9
|
readonly name: string;
|
|
10
10
|
readonly index: number;
|
|
11
11
|
readonly platform: Platform;
|
|
12
|
-
/** Validated
|
|
13
|
-
readonly
|
|
12
|
+
/** Validated engine; undefined for an agent-tools-only target. */
|
|
13
|
+
readonly engine: EngineHandle | undefined;
|
|
14
|
+
/** The app under test, resolved from the engine's declaration. */
|
|
15
|
+
readonly app: ResolvedApp;
|
|
14
16
|
}
|
|
15
17
|
export interface ResolvedCredential {
|
|
16
18
|
readonly name: string;
|
|
@@ -25,7 +27,6 @@ export interface ResolvedConfig {
|
|
|
25
27
|
readonly projectRoot: string;
|
|
26
28
|
readonly configPath: string | undefined;
|
|
27
29
|
readonly ci: boolean;
|
|
28
|
-
readonly app: ResolvedApp;
|
|
29
30
|
readonly targets: readonly ResolvedTarget[];
|
|
30
31
|
readonly tests: readonly string[];
|
|
31
32
|
readonly timeout: number;
|
|
@@ -36,7 +37,7 @@ export interface ResolvedConfig {
|
|
|
36
37
|
readonly retries: number;
|
|
37
38
|
readonly workers: number;
|
|
38
39
|
readonly artifacts: readonly ('trace' | 'screenshot')[];
|
|
39
|
-
/** True when artifact kinds were set in config, so
|
|
40
|
+
/** True when artifact kinds were set in config, so an engine that cannot produce one is an error. */
|
|
40
41
|
readonly artifactsExplicit: boolean;
|
|
41
42
|
/** Host store every produced artifact is handed to; undefined keeps files local only. */
|
|
42
43
|
readonly artifactStore: ArtifactStore | undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/config/resolve.ts"],"names":[],"mappings":"AAAA,uEAAuE;AASvE,OAAO,KAAK,EAEV,aAAa,EACb,SAAS,EACT,SAAS,EAET,QAAQ,EACR,cAAc,EACd,eAAe,EAChB,MAAM,aAAa,CAAC;AACrB,OAAO,
|
|
1
|
+
{"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/config/resolve.ts"],"names":[],"mappings":"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"}
|
package/dist/config/resolve.js
CHANGED
|
@@ -6,15 +6,14 @@ import { ConfigurationError } from '../internal/errors.js';
|
|
|
6
6
|
import { canonicalDigest, sha256Hex } from '../internal/ids.js';
|
|
7
7
|
import { isStepExecutor } from '../agent/executor.js';
|
|
8
8
|
import { boundedInt, positiveInt } from './validate.js';
|
|
9
|
-
import {
|
|
9
|
+
import { isEngineHandle } from '../engine/index.js';
|
|
10
10
|
import { isModelInstance, resolveAgentConfig, resolveLimits, } from './agent.js';
|
|
11
|
-
import {
|
|
11
|
+
import { digestAppDeclaration, resolveTargetApp } from './app.js';
|
|
12
12
|
const TARGET_NAME_PATTERN = /^[A-Za-z0-9_.-]+$/;
|
|
13
|
-
const TARGET_KEYS = new Set(['name', 'platform', '
|
|
13
|
+
const TARGET_KEYS = new Set(['name', 'platform', 'engine']);
|
|
14
14
|
const TOP_LEVEL_KEYS = new Set([
|
|
15
15
|
'specVersion',
|
|
16
16
|
'projectId',
|
|
17
|
-
'app',
|
|
18
17
|
'targets',
|
|
19
18
|
'tests',
|
|
20
19
|
'timeout',
|
|
@@ -58,8 +57,7 @@ export function resolveConfig(raw, options) {
|
|
|
58
57
|
if (raw.specVersion !== undefined && raw.specVersion !== '0.1') {
|
|
59
58
|
throw new ConfigurationError('INVALID_CONFIG', `unsupported specVersion ${JSON.stringify(raw.specVersion)}; this runner implements 0.1`);
|
|
60
59
|
}
|
|
61
|
-
const targets = resolveTargets(raw);
|
|
62
|
-
const app = resolveApp(raw, env);
|
|
60
|
+
const targets = resolveTargets(raw, options.projectRoot);
|
|
63
61
|
const tests = normalizeTests(raw.tests);
|
|
64
62
|
const timeout = positiveInt(raw.timeout, 'timeout') ?? 120_000;
|
|
65
63
|
const launchTimeout = positiveInt(raw.launchTimeout, 'launchTimeout') ?? 60_000;
|
|
@@ -102,7 +100,6 @@ export function resolveConfig(raw, options) {
|
|
|
102
100
|
projectRoot: options.projectRoot,
|
|
103
101
|
configPath: options.configPath,
|
|
104
102
|
ci,
|
|
105
|
-
app,
|
|
106
103
|
targets,
|
|
107
104
|
tests,
|
|
108
105
|
timeout,
|
|
@@ -129,19 +126,23 @@ export function resolveConfig(raw, options) {
|
|
|
129
126
|
* Resolves the `cache` key. The cache is opt-out: an unset key means
|
|
130
127
|
* `read-write`, so a project earns replay speed without asking for it, and
|
|
131
128
|
* `cache: 'off'` or `--no-cache` (which wins over the config) turns it off.
|
|
132
|
-
* CI
|
|
133
|
-
* committed
|
|
134
|
-
* learned (spec 10-determinism.md).
|
|
135
|
-
*
|
|
136
|
-
*
|
|
129
|
+
* CI demotes the *defaulted* mode from `read-write` to `read-only`: a
|
|
130
|
+
* committed cache is untrusted input, and a CI run never publishes what it
|
|
131
|
+
* learned unless the project says so (spec 10-determinism.md). An explicit
|
|
132
|
+
* `read-write` in the config is that statement of trust and is honored as
|
|
133
|
+
* written, as is a host-supplied `cache.store`, which states its own trust
|
|
134
|
+
* through the store's `writable` flag.
|
|
137
135
|
*/
|
|
138
136
|
function resolveCacheConfig(raw, ci, projectRoot, cliMode) {
|
|
139
137
|
const value = raw.cache;
|
|
140
138
|
let mode = 'read-write';
|
|
139
|
+
/** Whether the config named a mode; only a defaulted mode is demoted in CI. */
|
|
140
|
+
let explicit = false;
|
|
141
141
|
let store;
|
|
142
142
|
let dir;
|
|
143
143
|
if (typeof value === 'string') {
|
|
144
144
|
mode = value;
|
|
145
|
+
explicit = true;
|
|
145
146
|
}
|
|
146
147
|
else if (value !== undefined) {
|
|
147
148
|
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
@@ -153,6 +154,7 @@ function resolveCacheConfig(raw, ci, projectRoot, cliMode) {
|
|
|
153
154
|
}
|
|
154
155
|
}
|
|
155
156
|
mode = value.mode ?? 'read-write';
|
|
157
|
+
explicit = value.mode !== undefined;
|
|
156
158
|
store = value.store;
|
|
157
159
|
if (store !== undefined && !isTraceCacheStore(store)) {
|
|
158
160
|
throw new ConfigurationError('INVALID_CONFIG', 'cache.store must implement TraceCacheStore: { writable, read(keyHash), write(keyHash, payload) }');
|
|
@@ -169,7 +171,7 @@ function resolveCacheConfig(raw, ci, projectRoot, cliMode) {
|
|
|
169
171
|
}
|
|
170
172
|
if (cliMode !== undefined)
|
|
171
173
|
mode = cliMode;
|
|
172
|
-
if (ci && mode === 'read-write' && store === undefined)
|
|
174
|
+
if (ci && mode === 'read-write' && !explicit && store === undefined)
|
|
173
175
|
mode = 'read-only';
|
|
174
176
|
return {
|
|
175
177
|
mode,
|
|
@@ -238,10 +240,10 @@ function isTraceCacheStore(value) {
|
|
|
238
240
|
typeof candidate['read'] === 'function' &&
|
|
239
241
|
typeof candidate['write'] === 'function');
|
|
240
242
|
}
|
|
241
|
-
function resolveTargets(raw) {
|
|
243
|
+
function resolveTargets(raw, projectRoot) {
|
|
242
244
|
if (raw.targets === undefined) {
|
|
243
|
-
throw new ConfigurationError('INVALID_CONFIG', 'targets is required: declare at least one target and the
|
|
244
|
-
'e.g. targets: [{ name, platform,
|
|
245
|
+
throw new ConfigurationError('INVALID_CONFIG', 'targets is required: declare at least one target and the engine that drives it, ' +
|
|
246
|
+
'e.g. targets: [{ name, platform, engine }]');
|
|
245
247
|
}
|
|
246
248
|
if (!Array.isArray(raw.targets) || raw.targets.length === 0) {
|
|
247
249
|
throw new ConfigurationError('INVALID_CONFIG', 'targets must be a non-empty array');
|
|
@@ -257,20 +259,21 @@ function resolveTargets(raw) {
|
|
|
257
259
|
seen.add(target.name);
|
|
258
260
|
for (const key of Object.keys(target)) {
|
|
259
261
|
if (!TARGET_KEYS.has(key)) {
|
|
260
|
-
throw new ConfigurationError('INVALID_CONFIG', `target "${target.name}" has unknown key "${key}"; a target is { name, platform,
|
|
262
|
+
throw new ConfigurationError('INVALID_CONFIG', `target "${target.name}" has unknown key "${key}"; a target is { name, platform, engine? }`);
|
|
261
263
|
}
|
|
262
264
|
}
|
|
263
265
|
if (typeof target.platform !== 'string' || target.platform.trim() === '') {
|
|
264
266
|
throw new ConfigurationError('INVALID_CONFIG', `target "${target.name}" requires a non-empty platform`);
|
|
265
267
|
}
|
|
266
|
-
if (target.
|
|
267
|
-
throw new ConfigurationError('INVALID_CONFIG', `target "${target.name}"
|
|
268
|
+
if (target.engine !== undefined && !isEngineHandle(target.engine)) {
|
|
269
|
+
throw new ConfigurationError('INVALID_CONFIG', `target "${target.name}" engine must be a defineEngine(...) handle`);
|
|
268
270
|
}
|
|
269
271
|
return {
|
|
270
272
|
name: target.name,
|
|
271
273
|
index,
|
|
272
274
|
platform: target.platform,
|
|
273
|
-
|
|
275
|
+
engine: target.engine,
|
|
276
|
+
app: resolveTargetApp(target.name, target.engine, projectRoot),
|
|
274
277
|
};
|
|
275
278
|
});
|
|
276
279
|
}
|
|
@@ -324,7 +327,7 @@ function resolveCredentials(raw, env) {
|
|
|
324
327
|
/**
|
|
325
328
|
* SHA-256/JCS digest of resolved config after replacing credential material
|
|
326
329
|
* with `{ secretName }` and env values with `{ envName }` (13-reporting.md).
|
|
327
|
-
* Live objects (
|
|
330
|
+
* Live objects (engine handles, model instances) are replaced by their stable
|
|
328
331
|
* identity before the JSON clone, so they never enter the digest and cannot
|
|
329
332
|
* make it nondeterministic across processes.
|
|
330
333
|
*/
|
|
@@ -341,11 +344,9 @@ function computeConfigDigest(raw, projectId) {
|
|
|
341
344
|
// which is exactly what survives the function-stripping JSON clone below.
|
|
342
345
|
// Every model slot is reduced to its identity: a live instance carries
|
|
343
346
|
// provider settings (and possibly credentials) that must never be digested.
|
|
344
|
-
// `app` is digested with every command env reduced to names.
|
|
345
347
|
const rawAgent = raw.agent;
|
|
346
348
|
const forClone = {
|
|
347
349
|
...raw,
|
|
348
|
-
...(raw.app === undefined ? {} : { app: digestApp(raw.app) }),
|
|
349
350
|
...(rawAgent === undefined || isStepExecutor(rawAgent)
|
|
350
351
|
? {}
|
|
351
352
|
: {
|
|
@@ -382,17 +383,19 @@ function computeConfigDigest(raw, projectId) {
|
|
|
382
383
|
}
|
|
383
384
|
if (raw.targets !== undefined) {
|
|
384
385
|
sanitized['targets'] = raw.targets.map((target) => {
|
|
385
|
-
//
|
|
386
|
-
// declaration - name, version, contract version,
|
|
387
|
-
|
|
388
|
-
|
|
386
|
+
// An engine handle holds live functions; its digest identity is the
|
|
387
|
+
// declaration - name, version, contract version, capability set, and
|
|
388
|
+
// what it declares about the app under test.
|
|
389
|
+
if (isEngineHandle(target.engine)) {
|
|
390
|
+
const { engine, ...rest } = target;
|
|
389
391
|
return {
|
|
390
392
|
...rest,
|
|
391
|
-
|
|
392
|
-
name:
|
|
393
|
-
...(
|
|
394
|
-
spiVersion:
|
|
395
|
-
capabilities: [...
|
|
393
|
+
engine: {
|
|
394
|
+
name: engine.name,
|
|
395
|
+
...(engine.version === undefined ? {} : { version: engine.version }),
|
|
396
|
+
spiVersion: engine.spiVersion,
|
|
397
|
+
capabilities: [...engine.capabilities].toSorted(),
|
|
398
|
+
app: digestAppDeclaration(engine.app ?? {}),
|
|
396
399
|
},
|
|
397
400
|
};
|
|
398
401
|
}
|