@loucompanion/forge-bridge 0.1.3-dev.fbe15023cdfe → 0.1.4-dev.bfa11ebe98c6
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 +88 -2
- package/dist/bridge/bridge-chat.types.d.ts +111 -0
- package/dist/bridge/bridge-chat.types.js +401 -0
- package/dist/bridge/bridge.base.d.ts +4 -1
- package/dist/bridge/bridge.service.d.ts +72 -3
- package/dist/bridge/bridge.service.js +1562 -152
- package/dist/bridge/bridge.types.d.ts +46 -1
- package/dist/bridge/dispatch-result-outbox.d.ts +25 -0
- package/dist/bridge/dispatch-result-outbox.js +62 -0
- package/dist/bridge/dispatch-result-worker.d.ts +2 -0
- package/dist/bridge/dispatch-result-worker.js +122 -0
- package/dist/bridge/internals.d.ts +2 -0
- package/dist/bridge/internals.js +1 -0
- package/dist/bridge/readiness-result-outbox.d.ts +21 -0
- package/dist/bridge/readiness-result-outbox.js +54 -0
- package/dist/bridge/readiness-result-worker.d.ts +2 -0
- package/dist/bridge/readiness-result-worker.js +121 -0
- 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 +1 -0
- package/dist/cleanup/cleanup.service.js +70 -9
- package/dist/cli/cli.help.js +2 -2
- package/dist/cli/cli.service.d.ts +14 -6
- package/dist/cli/cli.service.js +75 -16
- package/dist/cli/local-config.d.ts +1 -0
- package/dist/cli/local-config.js +4 -0
- package/dist/runtime/aspnet-runtime-prebuild.d.ts +36 -0
- package/dist/runtime/aspnet-runtime-prebuild.js +341 -0
- package/dist/runtime/attempt-node-runtime-launcher.d.ts +2 -1
- package/dist/runtime/attempt-node-runtime-launcher.js +57 -18
- package/dist/runtime/attempt-path-operation.d.ts +21 -0
- package/dist/runtime/attempt-path-operation.js +94 -0
- package/dist/runtime/attempt-private-directory.d.ts +6 -0
- package/dist/runtime/attempt-private-directory.js +54 -0
- package/dist/runtime/attempt-process-supervisor.d.ts +11 -0
- package/dist/runtime/attempt-process-supervisor.js +67 -20
- package/dist/runtime/canonical-source-file.d.ts +3 -0
- package/dist/runtime/canonical-source-file.js +54 -0
- package/dist/runtime/docker-engine-probe.d.ts +90 -0
- package/dist/runtime/docker-engine-probe.js +333 -0
- package/dist/runtime/generated-output-reset.d.ts +10 -0
- package/dist/runtime/generated-output-reset.js +62 -8
- package/dist/runtime/host-database-capability-boundary.d.ts +16 -0
- package/dist/runtime/host-database-capability-boundary.js +62 -0
- package/dist/runtime/host-diagnostics.d.ts +11 -0
- package/dist/runtime/host-diagnostics.js +191 -0
- package/dist/runtime/host-execution-root.d.ts +4 -0
- package/dist/runtime/host-execution-root.js +39 -0
- package/dist/runtime/host-listener-inspector.d.ts +8 -1
- package/dist/runtime/host-listener-inspector.js +121 -60
- package/dist/runtime/host-runtime-contract.d.ts +12 -1
- package/dist/runtime/host-runtime-contract.js +107 -53
- package/dist/runtime/host-runtime-lease.d.ts +90 -8
- package/dist/runtime/host-runtime-lease.js +1097 -108
- package/dist/runtime/host-runtime-promotion.d.ts +12 -3
- package/dist/runtime/host-runtime-promotion.js +59 -12
- package/dist/runtime/memory-capacity.d.ts +70 -0
- package/dist/runtime/memory-capacity.js +123 -0
- package/dist/runtime/native-test-postgres-fallback.d.ts +25 -0
- package/dist/runtime/native-test-postgres-fallback.js +91 -0
- package/dist/runtime/node-host-runtime-adapter.d.ts +3 -0
- package/dist/runtime/node-host-runtime-adapter.js +28 -11
- package/dist/runtime/pinned-directory-process.d.ts +9 -0
- package/dist/runtime/pinned-directory-process.js +61 -0
- package/dist/runtime/pinned-source-directory.d.ts +17 -0
- package/dist/runtime/pinned-source-directory.js +94 -0
- package/dist/runtime/playwright-host-runtime-adapter.d.ts +1 -0
- package/dist/runtime/playwright-host-runtime-adapter.js +53 -5
- package/dist/runtime/prepared-runtime-identity.d.ts +6 -0
- package/dist/runtime/prepared-runtime-identity.js +218 -0
- package/dist/runtime/prepared-runtime-readiness.d.ts +30 -0
- package/dist/runtime/prepared-runtime-readiness.js +285 -0
- package/dist/runtime/runtime-isolation-lease.js +4 -1
- 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 +31 -1
- package/dist/sandbox/preflight.js +237 -51
- package/dist/sandbox/repository-lock.d.ts +2 -1
- package/dist/sandbox/repository-lock.js +17 -8
- package/dist/sandbox/runtime.d.ts +6 -1
- package/dist/sandbox/runtime.js +132 -103
- package/dist/sandbox/sandbox.types.d.ts +7 -0
- package/dist/sandbox/workspace.service.js +2 -0
- package/dist/session/managed-attempt-tools.d.ts +17 -1
- package/dist/session/managed-attempt-tools.js +481 -31
- 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 +337 -0
- package/dist/session/managed-verification-timeouts.d.ts +8 -0
- package/dist/session/managed-verification-timeouts.js +17 -0
- package/dist/session/phase-completion.artifacts.d.ts +17 -0
- package/dist/session/phase-completion.artifacts.js +224 -22
- package/dist/session/phase-completion.d.ts +11 -1
- package/dist/session/phase-completion.helpers.d.ts +5 -0
- package/dist/session/phase-completion.helpers.js +30 -7
- package/dist/session/phase-completion.js +14 -3
- package/dist/session/phase-completion.prompts.js +17 -18
- package/dist/session/phase-completion.runtime.d.ts +3 -1
- package/dist/session/phase-completion.runtime.js +118 -16
- package/dist/session/phase-completion.schemas.js +49 -2
- package/dist/session/phase-completion.validation.d.ts +1 -0
- package/dist/session/phase-completion.validation.js +111 -21
- package/dist/session/pre-publication-validation.d.ts +75 -0
- package/dist/session/pre-publication-validation.js +657 -0
- package/dist/session/provider-cli/bridge-cli.conformance-fixtures.d.ts +72 -0
- package/dist/session/provider-cli/bridge-cli.conformance-fixtures.js +462 -0
- package/dist/session/provider-cli/ci-repair-readiness.d.ts +8 -0
- package/dist/session/provider-cli/ci-repair-readiness.js +23 -3
- package/dist/session/provider-cli/provider-cli.artifacts.d.ts +4 -2
- package/dist/session/provider-cli/provider-cli.artifacts.js +47 -13
- package/dist/session/provider-cli/provider-cli.deterministic.js +34 -2
- package/dist/session/provider-cli/provider-cli.process.d.ts +51 -0
- package/dist/session/provider-cli/provider-cli.process.js +422 -22
- package/dist/session/provider-cli/provider-cli.prompts.d.ts +5 -0
- package/dist/session/provider-cli/provider-cli.prompts.js +136 -16
- package/dist/session/provider-cli/provider-cli.repair-epoch.d.ts +6 -0
- package/dist/session/provider-cli/provider-cli.repair-epoch.js +11 -0
- package/dist/session/provider-cli/provider-cli.runtime.d.ts +8 -1
- package/dist/session/provider-cli/provider-cli.runtime.js +40 -3
- package/dist/session/provider-cli/provider-cli.service.d.ts +30 -5
- package/dist/session/provider-cli/provider-cli.service.js +771 -68
- package/dist/session/provider-cli/provider-cli.types.d.ts +2 -0
- package/dist/session/provider-cli/provider-output-state.d.ts +10 -0
- package/dist/session/provider-cli/provider-output-state.js +361 -17
- 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.js +434 -9
- package/dist/session/session.service.js +145 -23
- package/dist/session/session.types.d.ts +147 -2
- package/dist/session/session.types.js +587 -7
- package/dist/session/unreferenced-exports.d.ts +12 -0
- package/dist/session/unreferenced-exports.js +59 -0
- package/dist/shared/format/format-bytes.d.ts +1 -0
- package/dist/shared/format/format-bytes.js +9 -0
- package/dist/shared/git/git.js +2 -1
- package/dist/shared/path/path.d.ts +1 -0
- package/dist/shared/path/path.js +18 -0
- package/dist/shared/process/dotnet-toolchain.d.ts +17 -0
- package/dist/shared/process/dotnet-toolchain.js +307 -0
- package/dist/shared/process/host-process-supervisor.d.ts +2 -0
- package/dist/shared/process/host-process-supervisor.js +57 -9
- package/dist/shared/process/process.d.ts +2 -0
- package/dist/shared/process/process.js +12 -7
- package/dist/shared/provider-capabilities.d.ts +3 -0
- package/dist/shared/provider-capabilities.js +85 -0
- package/dist/shared/redaction/redaction.d.ts +1 -0
- package/dist/shared/redaction/redaction.js +39 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worktree/dependency-warmer.d.ts +41 -9
- package/dist/worktree/dependency-warmer.js +497 -22
- package/dist/worktree/index.d.ts +1 -1
- package/dist/worktree/story-qa-reference-hydrator.d.ts +4 -0
- package/dist/worktree/story-qa-reference-hydrator.js +253 -0
- package/dist/worktree/worktree.service.d.ts +7 -0
- package/dist/worktree/worktree.service.js +253 -21
- package/dist/worktree/worktree.types.d.ts +7 -1
- package/package.json +7 -5
- package/runtime/windows-job-supervisor.ps1 +380 -14
|
@@ -2,7 +2,11 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { isAlias, parseDocument, visit } from "yaml";
|
|
5
|
+
import { readCanonicalSourceFile } from "./canonical-source-file.js";
|
|
6
|
+
import { isGeneratedOutputDirectoryName } from "./generated-output-reset.js";
|
|
5
7
|
export const hostRuntimeContractPath = ".forge/forge.runtime.yml";
|
|
8
|
+
export const hostRuntimeTestPostgresAdminEnvironment = "FORGE_TEST_POSTGRES_ADMIN_CONNECTION_STRING";
|
|
9
|
+
export const hostRuntimeLegacyTestPostgresEnvironment = "FORGE_TEST_POSTGRES_CONNECTION_STRING";
|
|
6
10
|
const maximumContractBytes = 64 * 1024;
|
|
7
11
|
const maximumDepth = 12;
|
|
8
12
|
const ignoredDiscoveryDirectories = new Set([
|
|
@@ -21,35 +25,31 @@ const runtimeKeys = new Set(["hostConcurrency", "ports", "playwright", "browserP
|
|
|
21
25
|
const portKeys = new Set(["adapter", "launch", "env", "baseUrlEnv", "format"]);
|
|
22
26
|
const launchKeys = new Set(["command", "args", "workingDirectory", "environment"]);
|
|
23
27
|
const playwrightKeys = new Set(["config", "baseUrlPort", "workers"]);
|
|
24
|
-
export async function loadHostRuntimeContract(worktreePath) {
|
|
28
|
+
export async function loadHostRuntimeContract(worktreePath, snapshot, directorySnapshot) {
|
|
29
|
+
worktreePath = await fs.realpath(worktreePath);
|
|
25
30
|
const target = path.join(path.resolve(worktreePath), hostRuntimeContractPath);
|
|
26
|
-
let
|
|
31
|
+
let source;
|
|
27
32
|
try {
|
|
28
|
-
|
|
33
|
+
const captured = snapshot ? await snapshot(target) : await readCanonicalSourceFile(target, maximumContractBytes);
|
|
34
|
+
if (captured === null)
|
|
35
|
+
return await discoverHostRuntimeContract(worktreePath, snapshot, directorySnapshot);
|
|
36
|
+
source = captured;
|
|
29
37
|
}
|
|
30
38
|
catch (error) {
|
|
31
39
|
if (isFileNotFound(error))
|
|
32
|
-
return await discoverHostRuntimeContract(worktreePath);
|
|
40
|
+
return await discoverHostRuntimeContract(worktreePath, snapshot, directorySnapshot);
|
|
33
41
|
throw error;
|
|
34
42
|
}
|
|
35
|
-
if (!targetStat.isFile() || targetStat.size < 1 || targetStat.size > maximumContractBytes) {
|
|
36
|
-
throw new Error(`runtime_contract_invalid: forge.runtime.yml must be a regular 1-${maximumContractBytes} byte file.`);
|
|
37
|
-
}
|
|
38
|
-
const [realWorktree, realTarget] = await Promise.all([fs.realpath(worktreePath), fs.realpath(target)]);
|
|
39
|
-
const relative = path.relative(realWorktree, realTarget);
|
|
40
|
-
if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
41
|
-
throw new Error("runtime_contract_invalid: forge.runtime.yml must remain inside the prepared worktree.");
|
|
42
|
-
}
|
|
43
43
|
return {
|
|
44
|
-
contract: validateHostRuntimeContract(
|
|
44
|
+
contract: validateHostRuntimeContract(source.toString("utf8")),
|
|
45
45
|
source: "project",
|
|
46
46
|
};
|
|
47
47
|
}
|
|
48
|
-
async function discoverHostRuntimeContract(worktreePath) {
|
|
48
|
+
async function discoverHostRuntimeContract(worktreePath, snapshot, directorySnapshot) {
|
|
49
49
|
const root = path.resolve(worktreePath);
|
|
50
50
|
const [nodeDiscovery, aspNetDiscovery] = await Promise.all([
|
|
51
|
-
discoverNodeRuntimes(root),
|
|
52
|
-
discoverAspNetRuntimes(root),
|
|
51
|
+
discoverNodeRuntimes(root, snapshot, directorySnapshot),
|
|
52
|
+
discoverAspNetRuntimes(root, snapshot, directorySnapshot),
|
|
53
53
|
]);
|
|
54
54
|
if (nodeDiscovery.ambiguous || aspNetDiscovery.ambiguous) {
|
|
55
55
|
return ambiguousRuntimeDiscovery();
|
|
@@ -99,16 +99,16 @@ async function discoverHostRuntimeContract(worktreePath) {
|
|
|
99
99
|
},
|
|
100
100
|
};
|
|
101
101
|
}
|
|
102
|
-
async function discoverNodeRuntimes(root) {
|
|
102
|
+
async function discoverNodeRuntimes(root, snapshot, directorySnapshot) {
|
|
103
103
|
const manifests = [...new Set([
|
|
104
104
|
path.join(root, "package.json"),
|
|
105
|
-
...await findFiles(root, file => path.basename(file).toLowerCase() === "package.json", 4, 500),
|
|
105
|
+
...await findFiles(root, file => path.basename(file).toLowerCase() === "package.json", 4, 500, directorySnapshot),
|
|
106
106
|
])];
|
|
107
|
-
const candidates = (await Promise.all(manifests.map(async (manifestPath) => await discoverNodeRuntime(root, manifestPath)))).filter((candidate) => candidate !== null);
|
|
107
|
+
const candidates = (await Promise.all(manifests.map(async (manifestPath) => await discoverNodeRuntime(root, manifestPath, snapshot, directorySnapshot)))).filter((candidate) => candidate !== null);
|
|
108
108
|
return { candidates: candidates.length === 1 ? candidates : [], ambiguous: candidates.length > 1 };
|
|
109
109
|
}
|
|
110
|
-
async function discoverNodeRuntime(root, manifestPath) {
|
|
111
|
-
const source = await readBoundedOptionalFile(manifestPath, 256 * 1024);
|
|
110
|
+
async function discoverNodeRuntime(root, manifestPath, snapshot, directorySnapshot) {
|
|
111
|
+
const source = await readBoundedOptionalFile(manifestPath, 256 * 1024, snapshot);
|
|
112
112
|
if (source === null)
|
|
113
113
|
return null;
|
|
114
114
|
let manifest;
|
|
@@ -130,15 +130,15 @@ async function discoverNodeRuntime(root, manifestPath) {
|
|
|
130
130
|
const next = Object.hasOwn(dependencies, "next") || /(^|\s)next(?:\s|$)/u.test(script);
|
|
131
131
|
if (vite === next)
|
|
132
132
|
return null;
|
|
133
|
-
const packageManager = await nearestPackageManager(root, path.dirname(manifestPath));
|
|
133
|
+
const packageManager = await nearestPackageManager(root, path.dirname(manifestPath), snapshot);
|
|
134
134
|
if (!packageManager)
|
|
135
135
|
return null;
|
|
136
136
|
const adapter = vite ? "vite" : "next";
|
|
137
137
|
const playwrightConfigs = [...new Set((await Promise.all([
|
|
138
138
|
...["playwright.config.ts", "playwright.config.js", "playwright.config.mjs", "playwright.config.cjs"]
|
|
139
139
|
.flatMap(file => [file, `e2e/${file}`, `tests/e2e/${file}`])
|
|
140
|
-
.map(async (candidate) => await optionalFileExists(path.join(root, candidate)) ? path.join(root, candidate) : null),
|
|
141
|
-
findFiles(root, file => /^playwright\.config\.(?:ts|js|mjs|cjs)$/iu.test(path.basename(file)), 4, 2_000),
|
|
140
|
+
.map(async (candidate) => await optionalFileExists(path.join(root, candidate), snapshot) ? path.join(root, candidate) : null),
|
|
141
|
+
findFiles(root, file => /^playwright\.config\.(?:ts|js|mjs|cjs)$/iu.test(path.basename(file)), 4, 2_000, directorySnapshot),
|
|
142
142
|
])).flat().filter((value) => value !== null))]
|
|
143
143
|
.map(config => portableRelativePath(root, config));
|
|
144
144
|
const playwright = playwrightConfigs.length === 1
|
|
@@ -182,11 +182,11 @@ async function discoverNodeRuntime(root, manifestPath) {
|
|
|
182
182
|
}),
|
|
183
183
|
};
|
|
184
184
|
}
|
|
185
|
-
async function discoverAspNetRuntimes(root) {
|
|
186
|
-
const projects = await findFiles(root, file => file.toLowerCase().endsWith(".csproj"), 3, 200);
|
|
185
|
+
async function discoverAspNetRuntimes(root, snapshot, directorySnapshot) {
|
|
186
|
+
const projects = await findFiles(root, file => file.toLowerCase().endsWith(".csproj"), 3, 200, directorySnapshot);
|
|
187
187
|
const webProjects = [];
|
|
188
188
|
for (const projectPath of projects) {
|
|
189
|
-
const source = await readBoundedOptionalFile(projectPath, 512 * 1024);
|
|
189
|
+
const source = await readBoundedOptionalFile(projectPath, 512 * 1024, snapshot);
|
|
190
190
|
if (source && /<Project\s+Sdk=["']Microsoft\.NET\.Sdk\.Web["']/iu.test(source)) {
|
|
191
191
|
webProjects.push({ relativePath: path.relative(root, projectPath).replaceAll("\\", "/"), source });
|
|
192
192
|
}
|
|
@@ -194,7 +194,7 @@ async function discoverAspNetRuntimes(root) {
|
|
|
194
194
|
if (webProjects.length !== 1)
|
|
195
195
|
return { candidates: [], ambiguous: webProjects.length > 1 };
|
|
196
196
|
const project = webProjects[0];
|
|
197
|
-
const launchProfile = await discoverAspNetLaunchProfile(root, project.relativePath);
|
|
197
|
+
const launchProfile = await discoverAspNetLaunchProfile(root, project.relativePath, snapshot);
|
|
198
198
|
const contract = {
|
|
199
199
|
schemaVersion: 1,
|
|
200
200
|
runtime: {
|
|
@@ -288,15 +288,15 @@ function ambiguousRuntimeDiscovery() {
|
|
|
288
288
|
},
|
|
289
289
|
};
|
|
290
290
|
}
|
|
291
|
-
async function nearestPackageManager(root, start) {
|
|
291
|
+
async function nearestPackageManager(root, start, snapshot) {
|
|
292
292
|
let directory = path.resolve(start);
|
|
293
293
|
const resolvedRoot = path.resolve(root);
|
|
294
294
|
while (directory === resolvedRoot || directory.startsWith(`${resolvedRoot}${path.sep}`)) {
|
|
295
295
|
const lockfiles = (await Promise.all([
|
|
296
|
-
optionalFileExists(path.join(directory, "pnpm-lock.yaml")).then(exists => exists ? "pnpm" : null),
|
|
297
|
-
optionalFileExists(path.join(directory, "yarn.lock")).then(exists => exists ? "yarn" : null),
|
|
298
|
-
optionalFileExists(path.join(directory, "package-lock.json")).then(exists => exists ? "npm" : null),
|
|
299
|
-
optionalFileExists(path.join(directory, "npm-shrinkwrap.json")).then(exists => exists ? "npm" : null),
|
|
296
|
+
optionalFileExists(path.join(directory, "pnpm-lock.yaml"), snapshot).then(exists => exists ? "pnpm" : null),
|
|
297
|
+
optionalFileExists(path.join(directory, "yarn.lock"), snapshot).then(exists => exists ? "yarn" : null),
|
|
298
|
+
optionalFileExists(path.join(directory, "package-lock.json"), snapshot).then(exists => exists ? "npm" : null),
|
|
299
|
+
optionalFileExists(path.join(directory, "npm-shrinkwrap.json"), snapshot).then(exists => exists ? "npm" : null),
|
|
300
300
|
])).filter((value) => value !== null);
|
|
301
301
|
const packageManagers = [...new Set(lockfiles)];
|
|
302
302
|
if (packageManagers.length > 1)
|
|
@@ -312,9 +312,9 @@ async function nearestPackageManager(root, start) {
|
|
|
312
312
|
function portableRelativePath(root, target) {
|
|
313
313
|
return path.relative(root, target).replaceAll("\\", "/");
|
|
314
314
|
}
|
|
315
|
-
async function discoverAspNetLaunchProfile(root, projectRelativePath) {
|
|
315
|
+
async function discoverAspNetLaunchProfile(root, projectRelativePath, snapshot) {
|
|
316
316
|
const target = path.join(root, path.dirname(projectRelativePath), "Properties", "launchSettings.json");
|
|
317
|
-
const source = await readBoundedOptionalFile(target, 512 * 1024);
|
|
317
|
+
const source = await readBoundedOptionalFile(target, 512 * 1024, snapshot);
|
|
318
318
|
if (!source)
|
|
319
319
|
return { name: null, source: null, environment: {} };
|
|
320
320
|
try {
|
|
@@ -354,6 +354,7 @@ function safeAspNetLaunchEnvironment(name, value) {
|
|
|
354
354
|
return false;
|
|
355
355
|
const normalized = name.toUpperCase();
|
|
356
356
|
return !normalized.startsWith("FORGE_")
|
|
357
|
+
&& !isUnsafeDotnetEnvironmentName(name)
|
|
357
358
|
&& !new Set([
|
|
358
359
|
"ASPNETCORE_URLS",
|
|
359
360
|
"ASPNETCORE_HTTP_PORTS",
|
|
@@ -368,34 +369,47 @@ function safeAspNetLaunchEnvironment(name, value) {
|
|
|
368
369
|
"TMPDIR",
|
|
369
370
|
]).has(normalized);
|
|
370
371
|
}
|
|
371
|
-
async function findFiles(root, accepts, maximumDepth, maximumEntries) {
|
|
372
|
+
async function findFiles(root, accepts, maximumDepth, maximumEntries, snapshot) {
|
|
372
373
|
const results = [];
|
|
373
374
|
let examined = 0;
|
|
374
|
-
const
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
375
|
+
const directories = [{ directory: root, depth: 0 }];
|
|
376
|
+
while (directories.length > 0 && examined < maximumEntries) {
|
|
377
|
+
const current = directories.shift();
|
|
378
|
+
if (current.depth > maximumDepth)
|
|
379
|
+
continue;
|
|
380
|
+
const entries = snapshot
|
|
381
|
+
? await snapshot(current.directory)
|
|
382
|
+
: await fs.readdir(current.directory, { withFileTypes: true }).then(found => found.map(entry => ({
|
|
383
|
+
name: entry.name,
|
|
384
|
+
isDirectory: entry.isDirectory(),
|
|
385
|
+
isFile: entry.isFile(),
|
|
386
|
+
}))).catch(() => []);
|
|
387
|
+
const orderedEntries = [...entries].sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
|
|
388
|
+
for (const entry of orderedEntries) {
|
|
389
|
+
if (entry.isDirectory
|
|
390
|
+
&& (ignoredDiscoveryDirectories.has(entry.name.toLowerCase()) || isGeneratedOutputDirectoryName(entry.name)))
|
|
382
391
|
continue;
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
392
|
+
if (examined++ >= maximumEntries)
|
|
393
|
+
break;
|
|
394
|
+
const target = path.join(current.directory, entry.name);
|
|
395
|
+
if (entry.isDirectory)
|
|
396
|
+
directories.push({ directory: target, depth: current.depth + 1 });
|
|
397
|
+
else if (entry.isFile && accepts(target))
|
|
387
398
|
results.push(target);
|
|
388
399
|
}
|
|
389
|
-
}
|
|
390
|
-
await visitDirectory(root, 0);
|
|
400
|
+
}
|
|
391
401
|
return results.sort((left, right) => left.localeCompare(right));
|
|
392
402
|
}
|
|
393
|
-
async function readBoundedOptionalFile(target, maximumBytes) {
|
|
403
|
+
async function readBoundedOptionalFile(target, maximumBytes, snapshot) {
|
|
404
|
+
if (snapshot) {
|
|
405
|
+
const source = await snapshot(target);
|
|
406
|
+
return source && source.length > 0 && source.length <= maximumBytes ? source.toString("utf8") : null;
|
|
407
|
+
}
|
|
394
408
|
try {
|
|
395
409
|
const stat = await fs.stat(target);
|
|
396
410
|
if (!stat.isFile() || stat.size < 1 || stat.size > maximumBytes)
|
|
397
411
|
return null;
|
|
398
|
-
return await
|
|
412
|
+
return (await readCanonicalSourceFile(target, maximumBytes)).toString("utf8");
|
|
399
413
|
}
|
|
400
414
|
catch (error) {
|
|
401
415
|
if (isFileNotFound(error))
|
|
@@ -403,7 +417,9 @@ async function readBoundedOptionalFile(target, maximumBytes) {
|
|
|
403
417
|
throw error;
|
|
404
418
|
}
|
|
405
419
|
}
|
|
406
|
-
async function optionalFileExists(target) {
|
|
420
|
+
async function optionalFileExists(target, snapshot) {
|
|
421
|
+
if (snapshot)
|
|
422
|
+
return await snapshot(target) !== null;
|
|
407
423
|
try {
|
|
408
424
|
return (await fs.stat(target)).isFile();
|
|
409
425
|
}
|
|
@@ -574,6 +590,7 @@ function parseLaunch(value, adapter, name) {
|
|
|
574
590
|
throw new Error(`runtime_contract_invalid: runtime.ports.${name}.launch.args must contain at most 128 strings.`);
|
|
575
591
|
}
|
|
576
592
|
const parsedArgs = args.map((argument, index) => boundedString(argument, 4096, `runtime.ports.${name}.launch.args[${index}]`));
|
|
593
|
+
rejectProjectAuthoredForgeArguments(parsedArgs, adapter, name);
|
|
577
594
|
const portableDirectory = launch.workingDirectory === undefined || launch.workingDirectory === null
|
|
578
595
|
? "."
|
|
579
596
|
: containedPortablePath(launch.workingDirectory, `runtime.ports.${name}.launch.workingDirectory`);
|
|
@@ -596,9 +613,46 @@ function parseLaunchEnvironment(value, portName) {
|
|
|
596
613
|
if (!/^[A-Za-z_][A-Za-z0-9_]{0,127}$/u.test(name)) {
|
|
597
614
|
throw new Error(`runtime_contract_invalid: launch environment name '${name}' is invalid.`);
|
|
598
615
|
}
|
|
616
|
+
if (name.toLowerCase().startsWith("forge_") || isUnsafeDotnetEnvironmentName(name)) {
|
|
617
|
+
throw new Error(`runtime_contract_invalid: launch environment name '${name}' is host-owned or enables pre-startup code injection and cannot be project-authored.`);
|
|
618
|
+
}
|
|
599
619
|
return [name, boundedString(raw, 8192, `runtime.ports.${portName}.launch.environment.${name}`)];
|
|
600
620
|
}));
|
|
601
621
|
}
|
|
622
|
+
function isUnsafeDotnetEnvironmentName(name) {
|
|
623
|
+
const normalized = name.toUpperCase();
|
|
624
|
+
return normalized === "DOTNET_STARTUP_HOOKS"
|
|
625
|
+
|| normalized === "DOTNET_ADDITIONAL_DEPS"
|
|
626
|
+
|| normalized === "DOTNET_SHARED_STORE"
|
|
627
|
+
|| normalized === "DOTNET_HOST_PATH"
|
|
628
|
+
|| normalized === "MSBUILD_EXE_PATH"
|
|
629
|
+
|| normalized === "MSBUILDSDKSPATH"
|
|
630
|
+
|| normalized === "NUGET_PLUGIN_PATHS"
|
|
631
|
+
|| normalized === "ASPNETCORE_HOSTINGSTARTUPASSEMBLIES"
|
|
632
|
+
|| normalized === "ASPNETCORE_HOSTINGSTARTUPEXCLUDEASSEMBLIES"
|
|
633
|
+
|| normalized.startsWith("CUSTOMBEFOREMICROSOFTCOMMON")
|
|
634
|
+
|| normalized.startsWith("CUSTOMAFTERMICROSOFTCOMMON")
|
|
635
|
+
|| normalized.startsWith("DOTNET_ROOT")
|
|
636
|
+
|| normalized.startsWith("CORECLR_")
|
|
637
|
+
|| normalized.startsWith("COR_")
|
|
638
|
+
|| normalized.startsWith("COMPLUS_")
|
|
639
|
+
|| normalized.startsWith("LD_")
|
|
640
|
+
|| normalized.startsWith("DYLD_");
|
|
641
|
+
}
|
|
642
|
+
function rejectProjectAuthoredForgeArguments(args, adapter, portName) {
|
|
643
|
+
if (adapter !== "aspnetcore")
|
|
644
|
+
return;
|
|
645
|
+
const applicationArgumentIndex = args.indexOf("--");
|
|
646
|
+
if (applicationArgumentIndex < 0)
|
|
647
|
+
return;
|
|
648
|
+
const forbidden = args.slice(applicationArgumentIndex + 1).find(argument => {
|
|
649
|
+
const key = argument.split("=", 1)[0].replace(/^(?:--?|\/)/u, "");
|
|
650
|
+
return /^forge(?:[_:.-]|$)/iu.test(key);
|
|
651
|
+
});
|
|
652
|
+
if (forbidden) {
|
|
653
|
+
throw new Error(`runtime_contract_invalid: runtime.ports.${portName}.launch.args cannot override the host-owned FORGE_* namespace.`);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
602
656
|
function containedPortablePath(value, label) {
|
|
603
657
|
const original = boundedString(value, 1024, label);
|
|
604
658
|
const portable = original.replaceAll("\\", "/");
|
|
@@ -4,8 +4,10 @@ import { type RuntimeIsolationLease } from "./runtime-isolation-lease.js";
|
|
|
4
4
|
import { type LoadedHostRuntimeContract } from "./host-runtime-contract.js";
|
|
5
5
|
import { HostPortBroker } from "./host-port-broker.js";
|
|
6
6
|
import { NodeHostRuntimeAdapter, type NodeRuntimeAdapterLaunch, type NodeRuntimeCommand, type NodeRuntimeCommandLauncher } from "./node-host-runtime-adapter.js";
|
|
7
|
+
import type { AttemptProcessTreeMemoryTelemetry } from "./attempt-process-supervisor.js";
|
|
7
8
|
import { AttemptProcessSupervisorRegistry } from "./attempt-process-supervisor-registry.js";
|
|
8
9
|
import { HostListenerInspector } from "./host-listener-inspector.js";
|
|
10
|
+
import { type MemoryAdmissionDiagnostics } from "./memory-capacity.js";
|
|
9
11
|
import { PlaywrightHostRuntimeAdapter } from "./playwright-host-runtime-adapter.js";
|
|
10
12
|
import { HostParallelPromotionStore } from "./host-runtime-promotion.js";
|
|
11
13
|
import { type HostProcessRecovery } from "../shared/process/host-process-recovery.js";
|
|
@@ -15,6 +17,18 @@ export interface HostRuntimeLeaseStart {
|
|
|
15
17
|
readonly runtimeContract: LoadedHostRuntimeContract;
|
|
16
18
|
readonly executionClass: HostRuntimeExecutionClass;
|
|
17
19
|
}
|
|
20
|
+
export interface HostRuntimeAdmissionCheckpoint {
|
|
21
|
+
readonly code: "host_runtime_serial_admission_waiting" | "host_runtime_prepared_probe_started" | "host_runtime_prepared_probe_progress" | "host_runtime_prepared_probe_completed" | "host_runtime_prepared_admission_waiting" | "host_runtime_prepared_admission_acquired";
|
|
22
|
+
readonly message: string;
|
|
23
|
+
readonly expiresAt: string;
|
|
24
|
+
}
|
|
25
|
+
export type HostRuntimeAdmissionCheckpointHandler = (checkpoint: HostRuntimeAdmissionCheckpoint) => void | Promise<void>;
|
|
26
|
+
export interface HostRuntimeProjectCheckpoint {
|
|
27
|
+
readonly code: "host_runtime_build_started" | "host_runtime_build_cached" | "host_runtime_build_completed";
|
|
28
|
+
readonly message: string;
|
|
29
|
+
readonly data?: Readonly<Record<string, unknown>>;
|
|
30
|
+
}
|
|
31
|
+
export type HostRuntimeProjectCheckpointHandler = (checkpoint: HostRuntimeProjectCheckpoint) => void | Promise<void>;
|
|
18
32
|
export type HostRuntimeExecutionClass = "analysis_only" | "managed_runtime";
|
|
19
33
|
export type HostRuntimeSafetyClass = "host_serial_only" | "host_parallel_candidate" | "host_parallel_safe";
|
|
20
34
|
export interface HostRuntimeProjectCapacitySnapshot {
|
|
@@ -29,12 +43,25 @@ export interface HostRuntimeProjectCapacitySnapshot {
|
|
|
29
43
|
readonly promotedAt?: string | null;
|
|
30
44
|
}
|
|
31
45
|
export interface HostRuntimeCapacitySnapshot {
|
|
46
|
+
readonly preparedWorktreeReadiness?: boolean;
|
|
32
47
|
readonly activeAttempts: number;
|
|
33
48
|
readonly reservedAttempts: number;
|
|
34
49
|
readonly waitingAttempts: number;
|
|
35
50
|
readonly degradedAttempts: number;
|
|
36
51
|
readonly exclusiveReservations: number;
|
|
37
52
|
readonly sharedReservations: number;
|
|
53
|
+
readonly hardMaxAttempts: number;
|
|
54
|
+
readonly effectiveMaxAttempts: number;
|
|
55
|
+
readonly availableAttempts: number;
|
|
56
|
+
readonly totalMemoryBytes: number;
|
|
57
|
+
readonly availableMemoryBytes: number;
|
|
58
|
+
readonly hostReserveBytes: number;
|
|
59
|
+
readonly availableCpuMillis: number;
|
|
60
|
+
readonly availableWorkspaceBytes: number;
|
|
61
|
+
readonly activeReservationMemoryBytes: number;
|
|
62
|
+
readonly memoryAdmission: MemoryAdmissionDiagnostics;
|
|
63
|
+
readonly unavailableCode?: string | null;
|
|
64
|
+
readonly unavailableReason?: string | null;
|
|
38
65
|
readonly projects: readonly HostRuntimeProjectCapacitySnapshot[];
|
|
39
66
|
}
|
|
40
67
|
export interface HostRuntimeReapOptions {
|
|
@@ -48,21 +75,41 @@ export interface HostRuntimeReapReport {
|
|
|
48
75
|
readonly processesTerminated: number;
|
|
49
76
|
readonly reusedPidsProtected: number;
|
|
50
77
|
readonly degraded: number;
|
|
78
|
+
/**
|
|
79
|
+
* Active attempts of a previous runner instance whose every supervised
|
|
80
|
+
* process tree was proved absent, terminated or PID-reused. Forge may settle
|
|
81
|
+
* these exact attempts instead of leaving their launch outcome unknown.
|
|
82
|
+
*/
|
|
83
|
+
readonly recoveredAttempts?: readonly HostRuntimeRecoveredAttempt[];
|
|
84
|
+
}
|
|
85
|
+
export interface HostRuntimeRecoveredAttempt {
|
|
86
|
+
readonly dispatchAttemptId: string;
|
|
87
|
+
readonly fencingToken: string;
|
|
88
|
+
readonly runnerInstanceId: string;
|
|
51
89
|
}
|
|
52
90
|
export interface HostNodeRuntimeLaunch<T> extends NodeRuntimeAdapterLaunch<T> {
|
|
53
91
|
readonly runtimeLease: RuntimeIsolationLease;
|
|
54
92
|
}
|
|
55
93
|
export interface HostRuntimeLeaseRuntime {
|
|
94
|
+
acquirePreparation?(dispatch: RunOnceDispatchPacket, signal?: AbortSignal): Promise<readonly string[]>;
|
|
56
95
|
acquire(dispatch: RunOnceDispatchPacket, signal?: AbortSignal): Promise<readonly string[]>;
|
|
57
|
-
start(dispatch: RunOnceDispatchPacket, worktreePath: string, signal?: AbortSignal): Promise<HostRuntimeLeaseStart>;
|
|
58
|
-
startConfiguredProject(dispatchAttemptId: string, fencingToken: string, signal?: AbortSignal): Promise<HostRuntimeLeaseStart>;
|
|
96
|
+
start(dispatch: RunOnceDispatchPacket, worktreePath: string, signal?: AbortSignal, onAdmissionCheckpoint?: HostRuntimeAdmissionCheckpointHandler): Promise<HostRuntimeLeaseStart>;
|
|
97
|
+
startConfiguredProject(dispatchAttemptId: string, fencingToken: string, signal?: AbortSignal, onProjectCheckpoint?: HostRuntimeProjectCheckpointHandler): Promise<HostRuntimeLeaseStart>;
|
|
59
98
|
updateLease(dispatchAttemptId: string, fencingToken: string, expiresAt: string): Promise<void>;
|
|
60
99
|
providerProcessRunner?(dispatchAttemptId: string, fencingToken: string): ProviderProcessRunner | null;
|
|
100
|
+
providerMemoryTelemetry?(dispatchAttemptId: string, fencingToken: string): AttemptProcessTreeMemoryTelemetry | null;
|
|
61
101
|
release(dispatchAttemptId: string, fencingToken: string): Promise<RuntimeIsolationLease | null>;
|
|
62
|
-
invalidatePromotion?(repoId: string): Promise<void>;
|
|
102
|
+
invalidatePromotion?(repoId: string, scope?: "all" | "discovery"): Promise<void>;
|
|
103
|
+
hasForegroundDemand?(): boolean;
|
|
63
104
|
reap?(options: HostRuntimeReapOptions): Promise<HostRuntimeReapReport>;
|
|
64
105
|
snapshot?(): HostRuntimeCapacitySnapshot;
|
|
65
106
|
}
|
|
107
|
+
export interface HostCapacityResourceSample {
|
|
108
|
+
readonly totalMemoryBytes: number;
|
|
109
|
+
readonly availableMemoryBytes: number;
|
|
110
|
+
readonly totalCpuMillis: number;
|
|
111
|
+
readonly availableWorkspaceBytes: number;
|
|
112
|
+
}
|
|
66
113
|
export declare class HostRuntimeLeaseManager implements HostRuntimeLeaseRuntime {
|
|
67
114
|
private readonly stateRoot;
|
|
68
115
|
private readonly processSupervisors;
|
|
@@ -71,30 +118,57 @@ export declare class HostRuntimeLeaseManager implements HostRuntimeLeaseRuntime
|
|
|
71
118
|
private readonly promotionStore;
|
|
72
119
|
private readonly playwrightAdapter;
|
|
73
120
|
private readonly processRecovery;
|
|
121
|
+
private readonly capacityResourceSample?;
|
|
122
|
+
private readonly preparedReadiness;
|
|
74
123
|
private readonly active;
|
|
75
124
|
private readonly pendingRenewals;
|
|
125
|
+
private readonly acquiringAdmissions;
|
|
76
126
|
private readonly admissions;
|
|
77
127
|
private readonly singletonOwners;
|
|
78
128
|
private readonly singletonWaiters;
|
|
79
129
|
private readonly parallelProjectOwners;
|
|
80
130
|
private readonly parallelProjectWaiters;
|
|
131
|
+
private readonly capacityReservations;
|
|
132
|
+
private readonly capacityWaiters;
|
|
133
|
+
private capacityWaiterScanCursor;
|
|
134
|
+
private readonly backgroundAdmissionWaiters;
|
|
81
135
|
private readonly degraded;
|
|
82
136
|
private readonly knownPromotions;
|
|
137
|
+
private ledgerWriteTail;
|
|
138
|
+
private workspaceAvailableBytes;
|
|
83
139
|
private readonly portBroker;
|
|
84
140
|
private readonly nodeAdapter;
|
|
85
|
-
|
|
141
|
+
private readonly capacityConfiguration;
|
|
142
|
+
private readonly memoryAvailability;
|
|
143
|
+
private readonly executionRoot;
|
|
144
|
+
constructor(stateRoot: string, portBroker?: HostPortBroker, nodeAdapter?: NodeHostRuntimeAdapter, processSupervisors?: AttemptProcessSupervisorRegistry, processRunner?: ProviderProcessRunner, listenerInspector?: HostListenerInspector, promotionStore?: HostParallelPromotionStore, playwrightAdapter?: PlaywrightHostRuntimeAdapter, processRecovery?: HostProcessRecovery, capacityResourceSample?: HostCapacityResourceSample | undefined, executionRoot?: string);
|
|
86
145
|
reap(options: HostRuntimeReapOptions): Promise<HostRuntimeReapReport>;
|
|
87
146
|
snapshot(): HostRuntimeCapacitySnapshot;
|
|
147
|
+
hasForegroundDemand(): boolean;
|
|
148
|
+
acquirePreparation(dispatch: RunOnceDispatchPacket, signal?: AbortSignal): Promise<readonly string[]>;
|
|
88
149
|
acquire(dispatch: RunOnceDispatchPacket, signal?: AbortSignal): Promise<readonly string[]>;
|
|
150
|
+
private reserveHostCapacity;
|
|
151
|
+
private reserveBackgroundAdmission;
|
|
152
|
+
private hostCapacityAvailable;
|
|
153
|
+
private hostCapacityImpossibleReason;
|
|
154
|
+
private hostResourceSnapshot;
|
|
155
|
+
private refreshWorkspaceCapacity;
|
|
156
|
+
private releaseHostCapacity;
|
|
157
|
+
private drainCapacityWaiters;
|
|
158
|
+
private capacityWaitersForScan;
|
|
159
|
+
private detachCapacityWaiter;
|
|
89
160
|
private reserveSingletonKeys;
|
|
90
161
|
private reserveParallelProject;
|
|
91
|
-
|
|
162
|
+
private upgradeParallelProjectToSingleton;
|
|
163
|
+
start(dispatch: RunOnceDispatchPacket, worktreePath: string, signal?: AbortSignal, onAdmissionCheckpoint?: HostRuntimeAdmissionCheckpointHandler): Promise<HostRuntimeLeaseStart>;
|
|
92
164
|
updateLease(dispatchAttemptId: string, fencingToken: string, expiresAt: string): Promise<void>;
|
|
93
|
-
startConfiguredProject(dispatchAttemptId: string, fencingToken: string, signal?: AbortSignal): Promise<HostRuntimeLeaseStart>;
|
|
165
|
+
startConfiguredProject(dispatchAttemptId: string, fencingToken: string, signal?: AbortSignal, onProjectCheckpoint?: HostRuntimeProjectCheckpointHandler): Promise<HostRuntimeLeaseStart>;
|
|
94
166
|
providerProcessRunner(dispatchAttemptId: string, fencingToken: string): ProviderProcessRunner | null;
|
|
167
|
+
private markCapacityReservationRunning;
|
|
168
|
+
providerMemoryTelemetry(dispatchAttemptId: string, fencingToken: string): AttemptProcessTreeMemoryTelemetry | null;
|
|
95
169
|
private promoteVerifiedRuntime;
|
|
96
170
|
launchNodeRuntime<T>(dispatchAttemptId: string, fencingToken: string, portName: string, command: NodeRuntimeCommand, launcher: NodeRuntimeCommandLauncher<T>, signal?: AbortSignal): Promise<HostNodeRuntimeLaunch<T>>;
|
|
97
|
-
release(dispatchAttemptId: string, fencingToken: string): Promise<RuntimeIsolationLease | null>;
|
|
171
|
+
release(dispatchAttemptId: string, fencingToken: string, preservePreparation?: boolean): Promise<RuntimeIsolationLease | null>;
|
|
98
172
|
private readLedgers;
|
|
99
173
|
private recoverPersistedProcesses;
|
|
100
174
|
private markLedgerDegraded;
|
|
@@ -102,6 +176,7 @@ export declare class HostRuntimeLeaseManager implements HostRuntimeLeaseRuntime
|
|
|
102
176
|
private forgetDegraded;
|
|
103
177
|
private attemptRoot;
|
|
104
178
|
private writeLedger;
|
|
179
|
+
private writeLedgerAtomic;
|
|
105
180
|
private ledgerPath;
|
|
106
181
|
private singletonKeysAvailable;
|
|
107
182
|
private assignSingletonKeys;
|
|
@@ -118,9 +193,13 @@ export declare class HostRuntimeLeaseManager implements HostRuntimeLeaseRuntime
|
|
|
118
193
|
private cancelAdmissionWaiters;
|
|
119
194
|
private drainSingletonWaiters;
|
|
120
195
|
private drainParallelProjectWaiters;
|
|
196
|
+
private drainBackgroundAdmissionWaiters;
|
|
121
197
|
private refreshKnownPromotions;
|
|
122
|
-
invalidatePromotion(repoId: string): Promise<void>;
|
|
198
|
+
invalidatePromotion(repoId: string, scope?: "all" | "discovery"): Promise<void>;
|
|
123
199
|
private drainAdmissionWaiters;
|
|
200
|
+
private reconcileReservationOwners;
|
|
201
|
+
private reservationOwnerIsLive;
|
|
202
|
+
private hasAttemptRepresentation;
|
|
124
203
|
}
|
|
125
204
|
export declare class HostRuntimeLeaseCleanupError extends Error {
|
|
126
205
|
readonly lease: RuntimeIsolationLease;
|
|
@@ -129,5 +208,8 @@ export declare class HostRuntimeLeaseCleanupError extends Error {
|
|
|
129
208
|
export declare class HostRuntimeAdmissionExpiredError extends Error {
|
|
130
209
|
constructor();
|
|
131
210
|
}
|
|
211
|
+
export declare class HostRuntimeCapacityRequestImpossibleError extends Error {
|
|
212
|
+
constructor(reason: string);
|
|
213
|
+
}
|
|
132
214
|
export declare function hostRuntimeEventResources(lease: RuntimeIsolationLease, runtimeContract?: LoadedHostRuntimeContract, executionClass?: HostRuntimeExecutionClass): Record<string, unknown>;
|
|
133
215
|
export declare function hostRuntimeSingletonKeys(dispatch: RunOnceDispatchPacket): readonly string[];
|