@loucompanion/forge-bridge 0.1.3-dev.9d8f2e7e5241 → 0.1.3-dev.a58d7cd1092f
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 +83 -0
- package/dist/bridge/bridge.base.d.ts +4 -1
- package/dist/bridge/bridge.service.d.ts +55 -3
- package/dist/bridge/bridge.service.js +834 -87
- package/dist/bridge/bridge.types.d.ts +89 -1
- package/dist/bridge/runner-host-capabilities.d.ts +15 -0
- package/dist/bridge/runner-host-capabilities.js +93 -0
- package/dist/cleanup/cleanup.service.d.ts +14 -2
- package/dist/cleanup/cleanup.service.js +496 -3
- package/dist/cleanup/cleanup.types.d.ts +10 -0
- package/dist/cleanup/index.d.ts +1 -1
- package/dist/cleanup/index.js +1 -1
- package/dist/cleanup/internals.d.ts +2 -2
- package/dist/cleanup/internals.js +1 -1
- package/dist/cli/cli.help.js +37 -3
- package/dist/cli/cli.service.d.ts +33 -2
- package/dist/cli/cli.service.js +209 -27
- package/dist/cli/local-config.d.ts +5 -0
- package/dist/cli/local-config.js +45 -1
- package/dist/runtime/aspnet-runtime-prebuild.d.ts +36 -0
- package/dist/runtime/aspnet-runtime-prebuild.js +340 -0
- package/dist/runtime/attempt-node-runtime-launcher.d.ts +17 -0
- package/dist/runtime/attempt-node-runtime-launcher.js +105 -0
- package/dist/runtime/attempt-process-supervisor-registry.d.ts +20 -0
- package/dist/runtime/attempt-process-supervisor-registry.js +64 -0
- package/dist/runtime/attempt-process-supervisor.d.ts +97 -0
- package/dist/runtime/attempt-process-supervisor.js +386 -0
- package/dist/runtime/docker-engine-probe.d.ts +83 -0
- package/dist/runtime/docker-engine-probe.js +300 -0
- package/dist/runtime/generated-output-reset.d.ts +10 -0
- package/dist/runtime/generated-output-reset.js +103 -0
- package/dist/runtime/host-listener-inspector.d.ts +46 -0
- package/dist/runtime/host-listener-inspector.js +349 -0
- package/dist/runtime/host-port-broker.d.ts +76 -0
- package/dist/runtime/host-port-broker.js +333 -0
- package/dist/runtime/host-runtime-contract.d.ts +47 -0
- package/dist/runtime/host-runtime-contract.js +700 -0
- package/dist/runtime/host-runtime-lease.d.ts +197 -0
- package/dist/runtime/host-runtime-lease.js +2203 -0
- package/dist/runtime/host-runtime-promotion.d.ts +23 -0
- package/dist/runtime/host-runtime-promotion.js +159 -0
- package/dist/runtime/memory-capacity.d.ts +68 -0
- package/dist/runtime/memory-capacity.js +121 -0
- package/dist/runtime/node-host-runtime-adapter.d.ts +54 -0
- package/dist/runtime/node-host-runtime-adapter.js +284 -0
- package/dist/runtime/playwright-host-runtime-adapter.d.ts +21 -0
- package/dist/runtime/playwright-host-runtime-adapter.js +231 -0
- package/dist/runtime/runtime-isolation-lease.d.ts +67 -0
- package/dist/runtime/runtime-isolation-lease.js +158 -0
- package/dist/runtime/workspace-resource-estimator.d.ts +17 -0
- package/dist/runtime/workspace-resource-estimator.js +174 -0
- package/dist/sandbox/preflight.d.ts +45 -1
- package/dist/sandbox/preflight.js +299 -34
- package/dist/sandbox/process.js +21 -2
- package/dist/sandbox/repository-lock.js +41 -3
- package/dist/sandbox/runtime.d.ts +66 -3
- package/dist/sandbox/runtime.js +670 -105
- package/dist/sandbox/sandbox.types.d.ts +10 -0
- package/dist/sandbox/smoke.d.ts +76 -0
- package/dist/sandbox/smoke.js +380 -0
- package/dist/sandbox/worker-supervisor.js +1 -1
- package/dist/sandbox/worker.js +2 -5
- package/dist/sandbox/workspace.service.d.ts +1 -0
- package/dist/sandbox/workspace.service.js +28 -5
- package/dist/session/managed-attempt-tools.d.ts +21 -0
- package/dist/session/managed-attempt-tools.js +912 -0
- package/dist/session/managed-build-coordinator.d.ts +5 -0
- package/dist/session/managed-build-coordinator.js +105 -0
- package/dist/session/managed-build-receipt.d.ts +21 -0
- package/dist/session/managed-build-receipt.js +335 -0
- package/dist/session/phase-completion.artifacts.d.ts +17 -0
- package/dist/session/phase-completion.artifacts.js +233 -24
- package/dist/session/phase-completion.d.ts +12 -2
- package/dist/session/phase-completion.helpers.d.ts +5 -0
- package/dist/session/phase-completion.helpers.js +51 -10
- package/dist/session/phase-completion.js +14 -3
- package/dist/session/phase-completion.prompts.d.ts +3 -3
- package/dist/session/phase-completion.prompts.js +24 -8
- package/dist/session/phase-completion.runtime.d.ts +13 -1
- package/dist/session/phase-completion.runtime.js +189 -8
- package/dist/session/phase-completion.schemas.js +117 -4
- package/dist/session/phase-completion.validation.d.ts +4 -1
- package/dist/session/phase-completion.validation.js +153 -22
- package/dist/session/pre-publication-validation.d.ts +46 -0
- package/dist/session/pre-publication-validation.js +208 -0
- package/dist/session/provider-cli/ci-repair-readiness.d.ts +16 -0
- package/dist/session/provider-cli/ci-repair-readiness.js +110 -0
- package/dist/session/provider-cli/provider-cli.artifacts.d.ts +3 -2
- package/dist/session/provider-cli/provider-cli.artifacts.js +65 -7
- package/dist/session/provider-cli/provider-cli.deterministic.js +47 -1
- package/dist/session/provider-cli/provider-cli.process.d.ts +25 -0
- package/dist/session/provider-cli/provider-cli.process.js +463 -34
- package/dist/session/provider-cli/provider-cli.prompts.d.ts +4 -1
- package/dist/session/provider-cli/provider-cli.prompts.js +108 -25
- package/dist/session/provider-cli/provider-cli.qa-sidecars.js +1 -0
- package/dist/session/provider-cli/provider-cli.runtime.d.ts +1 -1
- package/dist/session/provider-cli/provider-cli.runtime.js +6 -3
- package/dist/session/provider-cli/provider-cli.service.d.ts +22 -2
- package/dist/session/provider-cli/provider-cli.service.js +529 -100
- package/dist/session/provider-cli/provider-cli.types.d.ts +5 -0
- package/dist/session/provider-cli/provider-output-state.d.ts +2 -0
- package/dist/session/provider-cli/provider-output-state.js +91 -16
- package/dist/session/provider-cli/provider-usage.d.ts +7 -0
- package/dist/session/provider-cli/provider-usage.js +28 -5
- package/dist/session/session-redaction.d.ts +3 -0
- package/dist/session/session-redaction.js +38 -0
- package/dist/session/session.base.d.ts +6 -1
- package/dist/session/session.service.d.ts +8 -2
- package/dist/session/session.service.js +522 -41
- package/dist/session/session.types.d.ts +72 -0
- package/dist/session/session.types.js +74 -3
- package/dist/shared/format/format-bytes.d.ts +1 -0
- package/dist/shared/format/format-bytes.js +9 -0
- package/dist/shared/path/path.d.ts +1 -0
- package/dist/shared/path/path.js +21 -1
- package/dist/shared/process/dotnet-toolchain.d.ts +14 -0
- package/dist/shared/process/dotnet-toolchain.js +179 -0
- package/dist/shared/process/host-process-recovery.d.ts +12 -0
- package/dist/shared/process/host-process-recovery.js +78 -0
- package/dist/shared/process/host-process-supervisor.d.ts +59 -0
- package/dist/shared/process/host-process-supervisor.js +330 -0
- package/dist/shared/process/process.d.ts +1 -0
- package/dist/shared/process/process.js +1 -0
- package/dist/shared/redaction/redaction.js +12 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worktree/artifact-injector.d.ts +3 -2
- package/dist/worktree/artifact-injector.js +43 -4
- package/dist/worktree/dependency-warmer.d.ts +28 -1
- package/dist/worktree/dependency-warmer.js +308 -9
- package/dist/worktree/index.d.ts +2 -1
- package/dist/worktree/index.js +1 -0
- package/dist/worktree/prior-phase-artifact-hydrator.d.ts +2 -0
- package/dist/worktree/prior-phase-artifact-hydrator.js +136 -0
- package/dist/worktree/worktree-paths.d.ts +4 -0
- package/dist/worktree/worktree-paths.js +30 -0
- package/dist/worktree/worktree.base.d.ts +3 -0
- package/dist/worktree/worktree.service.d.ts +13 -2
- package/dist/worktree/worktree.service.js +238 -47
- package/dist/worktree/worktree.types.d.ts +5 -1
- package/package.json +5 -3
- package/runtime/windows-job-supervisor.ps1 +444 -0
- package/runtime/windows-listener-owner.ps1 +33 -0
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { withAspNetPrebuiltRunArguments } from "./aspnet-runtime-prebuild.js";
|
|
3
|
+
import { hostPortEnvironment, } from "./host-runtime-contract.js";
|
|
4
|
+
/**
|
|
5
|
+
* Launches an explicitly configured HTTP project command beneath the caller's
|
|
6
|
+
* attempt supervisor. It never shells a command and never promotes a project
|
|
7
|
+
* by framework detection alone.
|
|
8
|
+
*/
|
|
9
|
+
export class NodeHostRuntimeAdapter {
|
|
10
|
+
broker;
|
|
11
|
+
readinessTimeoutMs;
|
|
12
|
+
hardReadinessTimeoutMs;
|
|
13
|
+
readinessPollIntervalMs;
|
|
14
|
+
httpProbeTimeoutMs;
|
|
15
|
+
probeHttp;
|
|
16
|
+
constructor(broker, options = {}) {
|
|
17
|
+
this.broker = broker;
|
|
18
|
+
this.readinessTimeoutMs = boundedInteger(options.readinessTimeoutMs ?? 120_000, 100, 120_000, "readinessTimeoutMs");
|
|
19
|
+
this.hardReadinessTimeoutMs = boundedInteger(options.hardReadinessTimeoutMs ?? 600_000, this.readinessTimeoutMs, 900_000, "hardReadinessTimeoutMs");
|
|
20
|
+
this.readinessPollIntervalMs = boundedInteger(options.readinessPollIntervalMs ?? 100, 10, 5_000, "readinessPollIntervalMs");
|
|
21
|
+
this.httpProbeTimeoutMs = boundedInteger(options.httpProbeTimeoutMs ?? 1_000, 50, 10_000, "httpProbeTimeoutMs");
|
|
22
|
+
this.probeHttp = options.probeHttp ?? probeHttpReadiness;
|
|
23
|
+
}
|
|
24
|
+
async launch(owner, portConfig, command, launcher, signal) {
|
|
25
|
+
const framework = portConfig.adapter;
|
|
26
|
+
if (framework !== "node" && framework !== "vite" && framework !== "next" && framework !== "aspnetcore") {
|
|
27
|
+
throw new Error("node_runtime_adapter_invalid: port config must explicitly select node, vite, next, or aspnetcore.");
|
|
28
|
+
}
|
|
29
|
+
validateCommand(command);
|
|
30
|
+
if (!portConfig.baseUrlEnv || !portConfig.format) {
|
|
31
|
+
throw new Error("node_runtime_adapter_invalid: an HTTP baseUrlEnv and loopback format are required.");
|
|
32
|
+
}
|
|
33
|
+
let activeRequest = null;
|
|
34
|
+
const launched = await this.broker.launchWithRetry(owner, {
|
|
35
|
+
name: portConfig.name,
|
|
36
|
+
protocol: "tcp",
|
|
37
|
+
}, {
|
|
38
|
+
launch: async (port, launchSignal) => {
|
|
39
|
+
const request = buildNodeRuntimeLaunchRequest(framework, portConfig, command, port);
|
|
40
|
+
activeRequest = request;
|
|
41
|
+
return await launcher.launch(request, launchSignal);
|
|
42
|
+
},
|
|
43
|
+
verifyOwnership: async (value, _port, verifySignal) => {
|
|
44
|
+
if (!activeRequest)
|
|
45
|
+
throw new Error("node_runtime_adapter_state: launch request was not recorded.");
|
|
46
|
+
return await this.waitUntilReady(value, activeRequest, launcher, verifySignal);
|
|
47
|
+
},
|
|
48
|
+
stop: launcher.stop,
|
|
49
|
+
}, signal);
|
|
50
|
+
if (!activeRequest)
|
|
51
|
+
throw new Error("node_runtime_adapter_state: active launch request is missing.");
|
|
52
|
+
return { ...launched, request: activeRequest };
|
|
53
|
+
}
|
|
54
|
+
async waitUntilReady(value, request, launcher, signal) {
|
|
55
|
+
const startedAt = Date.now();
|
|
56
|
+
const hardDeadline = startedAt + this.hardReadinessTimeoutMs;
|
|
57
|
+
let idleDeadline = startedAt + this.readinessTimeoutMs;
|
|
58
|
+
let outputRevision = -1;
|
|
59
|
+
let observedOwnership = "unbound";
|
|
60
|
+
while (Date.now() <= idleDeadline && Date.now() <= hardDeadline) {
|
|
61
|
+
if (signal?.aborted)
|
|
62
|
+
throw cancellationError(signal);
|
|
63
|
+
const diagnostics = await launcher.readinessDiagnostics?.(value);
|
|
64
|
+
if (diagnostics?.exited) {
|
|
65
|
+
throw new Error(readinessFailure("host_runtime_process_exited", `the supervised process exited with code ${diagnostics.exitCode ?? "unknown"} before its assigned runtime became ready`, request, diagnostics));
|
|
66
|
+
}
|
|
67
|
+
if (diagnostics?.outputRevision !== undefined && diagnostics.outputRevision > outputRevision) {
|
|
68
|
+
outputRevision = diagnostics.outputRevision;
|
|
69
|
+
idleDeadline = Math.min(hardDeadline, Date.now() + this.readinessTimeoutMs);
|
|
70
|
+
}
|
|
71
|
+
const ownership = await launcher.inspectListener(value, request, signal);
|
|
72
|
+
observedOwnership = ownership;
|
|
73
|
+
if (ownership === "foreign")
|
|
74
|
+
return false;
|
|
75
|
+
if (ownership === "owned" && await this.probeHttp(request.baseUrl, this.httpProbeTimeoutMs, signal)) {
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
await wait(this.readinessPollIntervalMs, signal);
|
|
79
|
+
}
|
|
80
|
+
const diagnostics = await launcher.readinessDiagnostics?.(value);
|
|
81
|
+
if (diagnostics?.exited) {
|
|
82
|
+
throw new Error(readinessFailure("host_runtime_process_exited", `the supervised process exited with code ${diagnostics.exitCode ?? "unknown"} before its assigned runtime became ready`, request, diagnostics));
|
|
83
|
+
}
|
|
84
|
+
const code = observedOwnership === "owned"
|
|
85
|
+
? "host_runtime_http_readiness_timeout"
|
|
86
|
+
: "host_runtime_listener_timeout";
|
|
87
|
+
const reachedHardDeadline = Date.now() >= hardDeadline;
|
|
88
|
+
const detail = observedOwnership === "owned"
|
|
89
|
+
? reachedHardDeadline
|
|
90
|
+
? `the supervised process owned its assigned loopback port but HTTP readiness did not succeed within the ${this.hardReadinessTimeoutMs}ms hard limit`
|
|
91
|
+
: `the supervised process owned its assigned loopback port but made no readiness progress for ${this.readinessTimeoutMs}ms`
|
|
92
|
+
: reachedHardDeadline
|
|
93
|
+
? `the supervised process did not bind its assigned loopback port within the ${this.hardReadinessTimeoutMs}ms hard limit`
|
|
94
|
+
: `the supervised process did not bind its assigned loopback port and made no output progress for ${this.readinessTimeoutMs}ms`;
|
|
95
|
+
throw new Error(readinessFailure(code, detail, request, diagnostics));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function readinessFailure(code, detail, request, diagnostics) {
|
|
99
|
+
const evidence = [
|
|
100
|
+
boundedDiagnostic("stderr", diagnostics?.stderrTail),
|
|
101
|
+
boundedDiagnostic("stdout", diagnostics?.stdoutTail),
|
|
102
|
+
].filter((value) => value !== null);
|
|
103
|
+
return `${code}: ${detail} (${request.baseUrl}).${evidence.length > 0 ? ` ${evidence.join(" ")}` : ""}`;
|
|
104
|
+
}
|
|
105
|
+
function boundedDiagnostic(label, value) {
|
|
106
|
+
const normalized = value?.replaceAll(/\s+/gu, " ").trim();
|
|
107
|
+
if (!normalized)
|
|
108
|
+
return null;
|
|
109
|
+
return `${label} tail: ${normalized.slice(-2_000)}`;
|
|
110
|
+
}
|
|
111
|
+
export function buildNodeRuntimeLaunchRequest(framework, portConfig, command, port) {
|
|
112
|
+
validateCommand(command);
|
|
113
|
+
if (port.hostIp !== "127.0.0.1" || port.protocol !== "tcp") {
|
|
114
|
+
throw new Error("node_runtime_adapter_invalid: project adapters require a TCP IPv4 loopback reservation.");
|
|
115
|
+
}
|
|
116
|
+
if (!portConfig.baseUrlEnv || !portConfig.format) {
|
|
117
|
+
throw new Error("node_runtime_adapter_invalid: an HTTP baseUrlEnv and loopback format are required.");
|
|
118
|
+
}
|
|
119
|
+
rejectConflictingFrameworkArguments(framework, command.args);
|
|
120
|
+
const portEnvironment = hostPortEnvironment(portConfig, port.hostIp, port.hostPort);
|
|
121
|
+
const baseUrl = portEnvironment[portConfig.baseUrlEnv];
|
|
122
|
+
if (!baseUrl)
|
|
123
|
+
throw new Error("node_runtime_adapter_invalid: base URL environment could not be rendered.");
|
|
124
|
+
const args = framework === "aspnetcore"
|
|
125
|
+
? aspNetCoreArguments(command.command, command.args)
|
|
126
|
+
: [...command.args, ...frameworkArguments(framework, port.hostIp, port.hostPort)];
|
|
127
|
+
const adapterEnvironment = framework === "aspnetcore"
|
|
128
|
+
? {
|
|
129
|
+
ASPNETCORE_URLS: baseUrl,
|
|
130
|
+
Hosting__Port: String(port.hostPort),
|
|
131
|
+
Harness__Reconciler__Hosted: "false",
|
|
132
|
+
Projects__RuntimeReadiness__Hosted: "false",
|
|
133
|
+
Runners__ServerLocalHeartbeat__Hosted: "false",
|
|
134
|
+
FORGE_RUNTIME_MODE: "host",
|
|
135
|
+
FORGE_DISPATCH_ATTEMPT_ID: port.dispatchAttemptId,
|
|
136
|
+
DOTNET_CLI_USE_MSBUILD_SERVER: "0",
|
|
137
|
+
DOTNET_NOLOGO: "1",
|
|
138
|
+
}
|
|
139
|
+
: { HOST: port.hostIp };
|
|
140
|
+
const forgeEnvironment = {
|
|
141
|
+
FORGE_RUNTIME_PORT_NAME: portConfig.name,
|
|
142
|
+
FORGE_RUNTIME_PORT: String(port.hostPort),
|
|
143
|
+
FORGE_RUNTIME_BASE_URL: baseUrl,
|
|
144
|
+
};
|
|
145
|
+
const expansionScope = {
|
|
146
|
+
...command.environment,
|
|
147
|
+
...portEnvironment,
|
|
148
|
+
...adapterEnvironment,
|
|
149
|
+
...forgeEnvironment,
|
|
150
|
+
};
|
|
151
|
+
return {
|
|
152
|
+
framework,
|
|
153
|
+
command: command.command,
|
|
154
|
+
args,
|
|
155
|
+
cwd: path.resolve(command.cwd),
|
|
156
|
+
environment: {
|
|
157
|
+
...expandCommandEnvironment(command.environment, expansionScope),
|
|
158
|
+
...portEnvironment,
|
|
159
|
+
...adapterEnvironment,
|
|
160
|
+
...forgeEnvironment,
|
|
161
|
+
},
|
|
162
|
+
port,
|
|
163
|
+
baseUrl,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
function expandCommandEnvironment(environment, scope) {
|
|
167
|
+
return Object.fromEntries(Object.entries(environment ?? {}).map(([name, value]) => [
|
|
168
|
+
name,
|
|
169
|
+
value.replaceAll(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/gu, (_match, variable) => {
|
|
170
|
+
const resolved = scope[variable];
|
|
171
|
+
if (!resolved) {
|
|
172
|
+
throw new Error(`node_runtime_adapter_invalid: launch environment references unavailable variable '${variable}'.`);
|
|
173
|
+
}
|
|
174
|
+
return resolved;
|
|
175
|
+
}),
|
|
176
|
+
]));
|
|
177
|
+
}
|
|
178
|
+
function frameworkArguments(framework, hostIp, hostPort) {
|
|
179
|
+
if (framework === "vite")
|
|
180
|
+
return ["--host", hostIp, "--port", String(hostPort), "--strictPort"];
|
|
181
|
+
if (framework === "next")
|
|
182
|
+
return ["--hostname", hostIp, "--port", String(hostPort)];
|
|
183
|
+
return [];
|
|
184
|
+
}
|
|
185
|
+
function rejectConflictingFrameworkArguments(framework, args) {
|
|
186
|
+
if (framework === "aspnetcore") {
|
|
187
|
+
const forbidden = new Set(["--urls", "--launch-profile", "-lp", "--no-launch-profile"]);
|
|
188
|
+
const conflict = args.find(argument => forbidden.has(argument)
|
|
189
|
+
|| [...forbidden].some(flag => argument.startsWith(`${flag}=`)));
|
|
190
|
+
if (conflict) {
|
|
191
|
+
throw new Error(`node_runtime_adapter_invalid: command already supplies Forge-owned argument '${conflict}'.`);
|
|
192
|
+
}
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (framework === "node")
|
|
196
|
+
return;
|
|
197
|
+
const forbidden = framework === "vite"
|
|
198
|
+
? new Set(["--host", "--port", "--strictPort"])
|
|
199
|
+
: new Set(["--hostname", "--port", "-H", "-p"]);
|
|
200
|
+
const conflict = args.find(argument => forbidden.has(argument) || [...forbidden].some(flag => argument.startsWith(`${flag}=`)));
|
|
201
|
+
if (conflict) {
|
|
202
|
+
throw new Error(`node_runtime_adapter_invalid: command already supplies Forge-owned argument '${conflict}'.`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
function aspNetCoreArguments(command, args) {
|
|
206
|
+
const executable = path.basename(command).toLowerCase();
|
|
207
|
+
if (executable !== "dotnet" && executable !== "dotnet.exe") {
|
|
208
|
+
throw new Error("node_runtime_adapter_invalid: aspnetcore adapter requires the dotnet executable.");
|
|
209
|
+
}
|
|
210
|
+
if (args[0]?.toLowerCase() !== "run") {
|
|
211
|
+
throw new Error("node_runtime_adapter_invalid: aspnetcore adapter requires a dotnet run command.");
|
|
212
|
+
}
|
|
213
|
+
const prebuiltArgs = withAspNetPrebuiltRunArguments(command, args);
|
|
214
|
+
const applicationArguments = prebuiltArgs.indexOf("--");
|
|
215
|
+
const insertionIndex = applicationArguments < 0 ? prebuiltArgs.length : applicationArguments;
|
|
216
|
+
return [
|
|
217
|
+
...prebuiltArgs.slice(0, insertionIndex),
|
|
218
|
+
"--disable-build-servers",
|
|
219
|
+
"-p:UseSharedCompilation=false",
|
|
220
|
+
"-p:DefaultItemExcludesInProjectFolder=TestResults/**",
|
|
221
|
+
"--no-launch-profile",
|
|
222
|
+
...prebuiltArgs.slice(insertionIndex),
|
|
223
|
+
];
|
|
224
|
+
}
|
|
225
|
+
function validateCommand(command) {
|
|
226
|
+
if (!boundedText(command.command, 1024)
|
|
227
|
+
|| !path.isAbsolute(command.cwd)
|
|
228
|
+
|| command.args.length > 128
|
|
229
|
+
|| command.args.some(argument => !boundedText(argument, 4096))) {
|
|
230
|
+
throw new Error("node_runtime_adapter_invalid: command, arguments, or absolute working directory are invalid.");
|
|
231
|
+
}
|
|
232
|
+
for (const [name, value] of Object.entries(command.environment ?? {})) {
|
|
233
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]{0,127}$/u.test(name) || !boundedText(value, 8192)) {
|
|
234
|
+
throw new Error("node_runtime_adapter_invalid: command environment is invalid.");
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
async function probeHttpReadiness(url, timeoutMs, signal) {
|
|
239
|
+
const timeout = AbortSignal.timeout(timeoutMs);
|
|
240
|
+
const combined = signal ? AbortSignal.any([signal, timeout]) : timeout;
|
|
241
|
+
try {
|
|
242
|
+
const response = await fetch(url, { redirect: "manual", signal: combined });
|
|
243
|
+
await response.body?.cancel().catch(() => undefined);
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
catch (_error) {
|
|
247
|
+
if (signal?.aborted)
|
|
248
|
+
throw cancellationError(signal);
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
function wait(milliseconds, signal) {
|
|
253
|
+
if (signal?.aborted)
|
|
254
|
+
return Promise.reject(cancellationError(signal));
|
|
255
|
+
return new Promise((resolve, reject) => {
|
|
256
|
+
const timer = setTimeout(() => {
|
|
257
|
+
signal?.removeEventListener("abort", abort);
|
|
258
|
+
resolve();
|
|
259
|
+
}, milliseconds);
|
|
260
|
+
const abort = () => {
|
|
261
|
+
clearTimeout(timer);
|
|
262
|
+
reject(cancellationError(signal));
|
|
263
|
+
};
|
|
264
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
function boundedInteger(value, minimum, maximum, name) {
|
|
268
|
+
if (!Number.isSafeInteger(value) || value < minimum || value > maximum) {
|
|
269
|
+
throw new Error(`node_runtime_adapter_invalid: ${name} must be between ${minimum} and ${maximum}.`);
|
|
270
|
+
}
|
|
271
|
+
return value;
|
|
272
|
+
}
|
|
273
|
+
function boundedText(value, maximumLength) {
|
|
274
|
+
return typeof value === "string"
|
|
275
|
+
&& value.length > 0
|
|
276
|
+
&& value.length <= maximumLength
|
|
277
|
+
&& Array.from(value).every(character => {
|
|
278
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
279
|
+
return codePoint > 31 && codePoint !== 127;
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
function cancellationError(signal) {
|
|
283
|
+
return new Error(String(signal?.reason ?? "Node runtime launch cancelled."));
|
|
284
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { HostRuntimePlaywrightConfig } from "./host-runtime-contract.js";
|
|
2
|
+
export interface PlaywrightHostRuntimeContext {
|
|
3
|
+
readonly worktreePath: string;
|
|
4
|
+
readonly temporaryDirectory: string;
|
|
5
|
+
readonly testOutputDirectory: string;
|
|
6
|
+
readonly browserProfileDirectory: string;
|
|
7
|
+
readonly baseUrl: string;
|
|
8
|
+
readonly ownershipToken: string;
|
|
9
|
+
}
|
|
10
|
+
export interface PreparedPlaywrightHostRuntime {
|
|
11
|
+
readonly configPath: string;
|
|
12
|
+
readonly environment: Readonly<Record<string, string>>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Prepares a finite Playwright test boundary for a managed-host attempt.
|
|
16
|
+
* The generated overlay points tests at Forge's verified listener, disables
|
|
17
|
+
* Playwright-managed web servers, and keeps writable artifacts attempt-local.
|
|
18
|
+
*/
|
|
19
|
+
export declare class PlaywrightHostRuntimeAdapter {
|
|
20
|
+
prepare(config: HostRuntimePlaywrightConfig, context: PlaywrightHostRuntimeContext): Promise<PreparedPlaywrightHostRuntime>;
|
|
21
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
const maximumConfigBytes = 1024 * 1024;
|
|
6
|
+
/**
|
|
7
|
+
* Prepares a finite Playwright test boundary for a managed-host attempt.
|
|
8
|
+
* The generated overlay points tests at Forge's verified listener, disables
|
|
9
|
+
* Playwright-managed web servers, and keeps writable artifacts attempt-local.
|
|
10
|
+
*/
|
|
11
|
+
export class PlaywrightHostRuntimeAdapter {
|
|
12
|
+
async prepare(config, context) {
|
|
13
|
+
const worktreePath = path.resolve(context.worktreePath);
|
|
14
|
+
const sourceConfig = path.resolve(worktreePath, config.config);
|
|
15
|
+
const [realWorktree, realSource] = await Promise.all([
|
|
16
|
+
fs.realpath(worktreePath),
|
|
17
|
+
fs.realpath(sourceConfig),
|
|
18
|
+
]);
|
|
19
|
+
assertContained(realWorktree, realSource, "project Playwright config");
|
|
20
|
+
const sourceStat = await fs.stat(realSource);
|
|
21
|
+
if (!sourceStat.isFile() || sourceStat.size < 1 || sourceStat.size > maximumConfigBytes) {
|
|
22
|
+
throw new Error(`playwright_runtime_adapter_invalid: project config must be a regular 1-${maximumConfigBytes} byte file.`);
|
|
23
|
+
}
|
|
24
|
+
const sourceConfigText = await fs.readFile(realSource, "utf8");
|
|
25
|
+
const projectNames = [...sourceConfigText.matchAll(/\bname\s*:\s*["']([^"']+)["']/gu)]
|
|
26
|
+
.map(match => match[1])
|
|
27
|
+
.filter((name, index, names) => names.indexOf(name) === index);
|
|
28
|
+
const temporaryDirectory = await realContainedDirectory(context.temporaryDirectory, realWorktree, "temporary directory");
|
|
29
|
+
const testOutputDirectory = await realContainedDirectory(context.testOutputDirectory, realWorktree, "test output directory");
|
|
30
|
+
const browserProfileDirectory = await realContainedDirectory(context.browserProfileDirectory, realWorktree, "browser profile directory");
|
|
31
|
+
const attemptRoot = path.dirname(temporaryDirectory);
|
|
32
|
+
if (path.dirname(testOutputDirectory) !== attemptRoot || path.dirname(browserProfileDirectory) !== attemptRoot) {
|
|
33
|
+
throw new Error("playwright_runtime_adapter_invalid: writable directories must belong to the same attempt root.");
|
|
34
|
+
}
|
|
35
|
+
const baseUrl = loopbackHttpUrl(context.baseUrl);
|
|
36
|
+
if (!/^[0-9a-f]{32}$/u.test(context.ownershipToken)) {
|
|
37
|
+
throw new Error("playwright_runtime_adapter_invalid: ownership token must be a normalized dispatch attempt UUID.");
|
|
38
|
+
}
|
|
39
|
+
const overlayPath = path.join(temporaryDirectory, "forge.playwright.config.mjs");
|
|
40
|
+
const browserCache = await resolvePlaywrightBrowserCache(attemptRoot);
|
|
41
|
+
const browserInstallCommand = await resolvePlaywrightBrowserInstallCommand(context.worktreePath, path.dirname(realSource));
|
|
42
|
+
await fs.writeFile(overlayPath, renderOverlay({
|
|
43
|
+
projectConfigUrl: pathToFileURL(realSource).href,
|
|
44
|
+
projectConfigDirectory: path.dirname(realSource),
|
|
45
|
+
testOutputDirectory,
|
|
46
|
+
baseUrl,
|
|
47
|
+
workers: config.workers,
|
|
48
|
+
projectNames,
|
|
49
|
+
}), { encoding: "utf8", mode: 0o600 });
|
|
50
|
+
return {
|
|
51
|
+
configPath: overlayPath,
|
|
52
|
+
environment: {
|
|
53
|
+
FORGE_PLAYWRIGHT_CONFIG: overlayPath,
|
|
54
|
+
FORGE_PLAYWRIGHT_BASE_URL: baseUrl,
|
|
55
|
+
FORGE_E2E_WEB_URL: baseUrl,
|
|
56
|
+
FORGE_E2E_OWNERSHIP_MODE: "attempt",
|
|
57
|
+
FORGE_E2E_OWNERSHIP_TOKEN: context.ownershipToken,
|
|
58
|
+
FORGE_PLAYWRIGHT_BROWSER_READY: browserCache.ready ? "true" : "false",
|
|
59
|
+
FORGE_PLAYWRIGHT_BROWSER_INSTALL_COMMAND: browserInstallCommand,
|
|
60
|
+
PLAYWRIGHT_BROWSERS_PATH: browserCache.path,
|
|
61
|
+
PLAYWRIGHT_HTML_OUTPUT_DIR: path.join(testOutputDirectory, "html-report"),
|
|
62
|
+
PLAYWRIGHT_BLOB_OUTPUT_DIR: path.join(testOutputDirectory, "blob-report"),
|
|
63
|
+
PLAYWRIGHT_JUNIT_OUTPUT_NAME: path.join(testOutputDirectory, "results.xml"),
|
|
64
|
+
PLAYWRIGHT_JSON_OUTPUT_NAME: path.join(testOutputDirectory, "results.json"),
|
|
65
|
+
FORGE_BROWSER_PROFILE_DIR: browserProfileDirectory,
|
|
66
|
+
FORGE_TEST_OUTPUT_DIR: testOutputDirectory,
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async function resolvePlaywrightBrowserInstallCommand(worktreePath, configDirectory) {
|
|
72
|
+
const relativeDirectory = path.relative(worktreePath, configDirectory).replaceAll(path.sep, "/") || ".";
|
|
73
|
+
if (await isFile(path.join(configDirectory, "pnpm-lock.yaml"))) {
|
|
74
|
+
return `pnpm --dir ${quoteCommandArgument(relativeDirectory)} exec playwright install chromium`;
|
|
75
|
+
}
|
|
76
|
+
if (await isFile(path.join(configDirectory, "yarn.lock"))) {
|
|
77
|
+
return `yarn --cwd ${quoteCommandArgument(relativeDirectory)} playwright install chromium`;
|
|
78
|
+
}
|
|
79
|
+
return `npm --prefix ${quoteCommandArgument(relativeDirectory)} exec playwright install chromium`;
|
|
80
|
+
}
|
|
81
|
+
async function isFile(candidate) {
|
|
82
|
+
try {
|
|
83
|
+
return (await fs.stat(candidate)).isFile();
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function quoteCommandArgument(value) {
|
|
90
|
+
return /[\s"']/u.test(value) ? JSON.stringify(value) : value;
|
|
91
|
+
}
|
|
92
|
+
async function resolvePlaywrightBrowserCache(attemptRoot) {
|
|
93
|
+
const candidates = [
|
|
94
|
+
process.env.PLAYWRIGHT_BROWSERS_PATH,
|
|
95
|
+
process.env.LOCALAPPDATA ? path.join(process.env.LOCALAPPDATA, "ms-playwright") : null,
|
|
96
|
+
path.join(os.homedir(), ".cache", "ms-playwright"),
|
|
97
|
+
].filter((candidate) => Boolean(candidate?.trim()));
|
|
98
|
+
for (const candidate of [...new Set(candidates.map(candidate => path.resolve(candidate)))]) {
|
|
99
|
+
if (await hasChromiumExecutable(candidate)) {
|
|
100
|
+
return { path: candidate, ready: true };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const attemptCache = path.join(attemptRoot, "cache", "ms-playwright");
|
|
104
|
+
await fs.mkdir(attemptCache, { recursive: true });
|
|
105
|
+
return { path: attemptCache, ready: false };
|
|
106
|
+
}
|
|
107
|
+
async function hasChromiumExecutable(cachePath) {
|
|
108
|
+
let entries;
|
|
109
|
+
try {
|
|
110
|
+
entries = await fs.readdir(cachePath, { withFileTypes: true });
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
const executableSuffixes = process.platform === "win32"
|
|
116
|
+
? [path.join("chrome-win64", "chrome.exe"), path.join("chrome-win", "chrome.exe")]
|
|
117
|
+
: process.platform === "darwin"
|
|
118
|
+
? [path.join("chrome-mac", "Chromium.app", "Contents", "MacOS", "Chromium")]
|
|
119
|
+
: [path.join("chrome-linux", "chrome"), path.join("chrome-linux64", "chrome")];
|
|
120
|
+
for (const entry of entries) {
|
|
121
|
+
if (!entry.isDirectory() || !entry.name.startsWith("chromium-"))
|
|
122
|
+
continue;
|
|
123
|
+
for (const suffix of executableSuffixes) {
|
|
124
|
+
try {
|
|
125
|
+
const stat = await fs.stat(path.join(cachePath, entry.name, suffix));
|
|
126
|
+
if (stat.isFile())
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
// Continue through known Playwright layouts.
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
function renderOverlay(values) {
|
|
137
|
+
return [
|
|
138
|
+
"// Generated by forge-bridge for one fenced host attempt.",
|
|
139
|
+
`// forge-playwright-projects: ${JSON.stringify(values.projectNames)}`,
|
|
140
|
+
"import path from \"node:path\";",
|
|
141
|
+
`import projectConfig from ${JSON.stringify(values.projectConfigUrl)};`,
|
|
142
|
+
`const projectConfigDirectory = ${JSON.stringify(values.projectConfigDirectory)};`,
|
|
143
|
+
"const resolveConfigPath = value => typeof value === \"string\" && !path.isAbsolute(value)",
|
|
144
|
+
" ? path.resolve(projectConfigDirectory, value)",
|
|
145
|
+
" : value;",
|
|
146
|
+
"const resolveModulePath = value => typeof value === \"string\" && value.startsWith(\".\")",
|
|
147
|
+
" ? path.resolve(projectConfigDirectory, value)",
|
|
148
|
+
" : value;",
|
|
149
|
+
"const resolveUse = use => ({",
|
|
150
|
+
" ...(use ?? {}),",
|
|
151
|
+
" storageState: typeof use?.storageState === \"string\" ? resolveConfigPath(use.storageState) : use?.storageState,",
|
|
152
|
+
" baseURL: " + JSON.stringify(values.baseUrl) + ",",
|
|
153
|
+
"});",
|
|
154
|
+
"const forgeUse = resolveUse(projectConfig.use);",
|
|
155
|
+
"const forgeProjects = Array.isArray(projectConfig.projects)",
|
|
156
|
+
" ? projectConfig.projects.map(project => ({",
|
|
157
|
+
" ...project,",
|
|
158
|
+
" testDir: resolveConfigPath(project.testDir),",
|
|
159
|
+
" snapshotDir: resolveConfigPath(project.snapshotDir),",
|
|
160
|
+
" use: resolveUse(project.use),",
|
|
161
|
+
" }))",
|
|
162
|
+
" : projectConfig.projects;",
|
|
163
|
+
"const forgeReporter = Array.isArray(projectConfig.reporter)",
|
|
164
|
+
" ? projectConfig.reporter.map(reporter => Array.isArray(reporter)",
|
|
165
|
+
" ? [resolveModulePath(reporter[0]), ...reporter.slice(1)]",
|
|
166
|
+
" : resolveModulePath(reporter))",
|
|
167
|
+
" : resolveModulePath(projectConfig.reporter);",
|
|
168
|
+
"const forgeProviderOnlySpecs = (() => {",
|
|
169
|
+
" try {",
|
|
170
|
+
" const parsed = JSON.parse(process.env.FORGE_CI_REPAIR_PROVIDER_ONLY_SPECS ?? '[]');",
|
|
171
|
+
" return Array.isArray(parsed) ? parsed.filter(value => typeof value === 'string' && value.length > 0) : [];",
|
|
172
|
+
" } catch {",
|
|
173
|
+
" return [];",
|
|
174
|
+
" }",
|
|
175
|
+
"})();",
|
|
176
|
+
"const forgeTestIgnore = forgeProviderOnlySpecs.length === 0",
|
|
177
|
+
" ? projectConfig.testIgnore",
|
|
178
|
+
" : [...(Array.isArray(projectConfig.testIgnore) ? projectConfig.testIgnore : projectConfig.testIgnore ? [projectConfig.testIgnore] : []), ...forgeProviderOnlySpecs];",
|
|
179
|
+
"export default {",
|
|
180
|
+
" ...projectConfig,",
|
|
181
|
+
" testDir: resolveConfigPath(projectConfig.testDir),",
|
|
182
|
+
" snapshotDir: resolveConfigPath(projectConfig.snapshotDir),",
|
|
183
|
+
" globalSetup: resolveModulePath(projectConfig.globalSetup),",
|
|
184
|
+
" globalTeardown: resolveModulePath(projectConfig.globalTeardown),",
|
|
185
|
+
" reporter: forgeReporter,",
|
|
186
|
+
" testIgnore: forgeTestIgnore,",
|
|
187
|
+
` outputDir: ${JSON.stringify(values.testOutputDirectory)},`,
|
|
188
|
+
` workers: ${values.workers},`,
|
|
189
|
+
" webServer: undefined,",
|
|
190
|
+
" use: forgeUse,",
|
|
191
|
+
" projects: forgeProjects,",
|
|
192
|
+
"};",
|
|
193
|
+
"",
|
|
194
|
+
].join("\n");
|
|
195
|
+
}
|
|
196
|
+
async function realContainedDirectory(target, realWorktree, label) {
|
|
197
|
+
const realTarget = await fs.realpath(path.resolve(target));
|
|
198
|
+
if (realTarget === realWorktree || isContained(realWorktree, realTarget)) {
|
|
199
|
+
throw new Error(`playwright_runtime_adapter_invalid: ${label} must be attempt-private and outside the worktree.`);
|
|
200
|
+
}
|
|
201
|
+
const stat = await fs.stat(realTarget);
|
|
202
|
+
if (!stat.isDirectory())
|
|
203
|
+
throw new Error(`playwright_runtime_adapter_invalid: ${label} must be a directory.`);
|
|
204
|
+
return realTarget;
|
|
205
|
+
}
|
|
206
|
+
function assertContained(parent, child, label) {
|
|
207
|
+
if (!isContained(parent, child)) {
|
|
208
|
+
throw new Error(`playwright_runtime_adapter_invalid: ${label} must stay inside the prepared worktree.`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
function isContained(parent, child) {
|
|
212
|
+
const relative = path.relative(parent, child);
|
|
213
|
+
return Boolean(relative) && !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
214
|
+
}
|
|
215
|
+
function loopbackHttpUrl(value) {
|
|
216
|
+
let url;
|
|
217
|
+
try {
|
|
218
|
+
url = new URL(value);
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
throw new Error("playwright_runtime_adapter_invalid: base URL must be an HTTP loopback URL.");
|
|
222
|
+
}
|
|
223
|
+
if ((url.protocol !== "http:" && url.protocol !== "https:")
|
|
224
|
+
|| url.hostname !== "127.0.0.1"
|
|
225
|
+
|| !url.port
|
|
226
|
+
|| url.username
|
|
227
|
+
|| url.password) {
|
|
228
|
+
throw new Error("playwright_runtime_adapter_invalid: base URL must be an HTTP loopback URL.");
|
|
229
|
+
}
|
|
230
|
+
return url.href.replace(/\/$/u, "");
|
|
231
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export declare const runtimeIsolationLeaseStates: readonly ["reserved", "starting", "active", "releasing", "released", "degraded"];
|
|
2
|
+
export type RuntimeIsolationLeaseState = typeof runtimeIsolationLeaseStates[number];
|
|
3
|
+
export type RuntimeIsolationMode = "docker" | "host";
|
|
4
|
+
export interface RuntimeIsolationPort {
|
|
5
|
+
readonly name: string;
|
|
6
|
+
readonly protocol: "tcp" | "udp";
|
|
7
|
+
readonly hostIp: "127.0.0.1" | "::1";
|
|
8
|
+
readonly hostPort: number;
|
|
9
|
+
readonly targetPort: number;
|
|
10
|
+
}
|
|
11
|
+
export interface RuntimeIsolationPath {
|
|
12
|
+
readonly path: string;
|
|
13
|
+
readonly role: string;
|
|
14
|
+
}
|
|
15
|
+
export interface RuntimeIsolationDockerResource {
|
|
16
|
+
readonly type: "container" | "network" | "volume";
|
|
17
|
+
readonly name: string;
|
|
18
|
+
readonly role: string;
|
|
19
|
+
readonly id?: string | null;
|
|
20
|
+
}
|
|
21
|
+
export interface RuntimeIsolationResources {
|
|
22
|
+
readonly memoryReservationBytes: number;
|
|
23
|
+
readonly cpuMillis: number;
|
|
24
|
+
readonly pids: number;
|
|
25
|
+
readonly ports: readonly RuntimeIsolationPort[];
|
|
26
|
+
readonly paths: readonly RuntimeIsolationPath[];
|
|
27
|
+
readonly processSupervisorId: string | null;
|
|
28
|
+
readonly dockerResources: readonly RuntimeIsolationDockerResource[];
|
|
29
|
+
readonly singletonKeys: readonly string[];
|
|
30
|
+
}
|
|
31
|
+
export interface RuntimeIsolationLease {
|
|
32
|
+
readonly schemaVersion: 1;
|
|
33
|
+
readonly leaseId: string;
|
|
34
|
+
readonly dispatchAttemptId: string;
|
|
35
|
+
readonly executionLeaseId: string;
|
|
36
|
+
readonly fencingToken: string;
|
|
37
|
+
readonly runnerId: string;
|
|
38
|
+
readonly runnerInstanceId: string;
|
|
39
|
+
readonly worktreeKey: string;
|
|
40
|
+
readonly worktreePath: string;
|
|
41
|
+
readonly mode: RuntimeIsolationMode;
|
|
42
|
+
readonly state: RuntimeIsolationLeaseState;
|
|
43
|
+
readonly createdAt: string;
|
|
44
|
+
readonly stateChangedAt: string;
|
|
45
|
+
readonly expiresAt: string;
|
|
46
|
+
readonly releasedAt: string | null;
|
|
47
|
+
readonly resources: RuntimeIsolationResources;
|
|
48
|
+
}
|
|
49
|
+
export interface RuntimeIsolationLeaseInput {
|
|
50
|
+
readonly leaseId: string;
|
|
51
|
+
readonly dispatchAttemptId: string;
|
|
52
|
+
readonly executionLeaseId: string;
|
|
53
|
+
readonly fencingToken: string;
|
|
54
|
+
readonly runnerId: string;
|
|
55
|
+
readonly runnerInstanceId: string;
|
|
56
|
+
readonly worktreeKey: string;
|
|
57
|
+
readonly worktreePath: string;
|
|
58
|
+
readonly mode: RuntimeIsolationMode;
|
|
59
|
+
readonly expiresAt: string;
|
|
60
|
+
readonly resources: RuntimeIsolationResources;
|
|
61
|
+
readonly createdAt?: string;
|
|
62
|
+
}
|
|
63
|
+
export declare function createRuntimeIsolationLease(input: RuntimeIsolationLeaseInput): RuntimeIsolationLease;
|
|
64
|
+
export declare function transitionRuntimeIsolationLease(lease: RuntimeIsolationLease, nextState: RuntimeIsolationLeaseState, occurredAt?: string): RuntimeIsolationLease;
|
|
65
|
+
export declare function renewRuntimeIsolationLease(lease: RuntimeIsolationLease, fencingToken: string, expiresAt: string): RuntimeIsolationLease | null;
|
|
66
|
+
export declare function withRuntimeIsolationResources(lease: RuntimeIsolationLease, resources: RuntimeIsolationResources): RuntimeIsolationLease;
|
|
67
|
+
export declare function validRuntimeIsolationLease(value: unknown): value is RuntimeIsolationLease;
|