@loucompanion/forge-bridge 0.1.3-dev.f66395c3ac21 → 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 +165 -1
- 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 +9 -1
- package/dist/bridge/bridge.service.d.ts +129 -3
- package/dist/bridge/bridge.service.js +2426 -70
- package/dist/bridge/bridge.types.d.ts +218 -2
- 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 +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.d.ts +2 -0
- package/dist/cli/cli.help.js +217 -0
- package/dist/cli/cli.service.d.ts +55 -7
- package/dist/cli/cli.service.js +429 -219
- package/dist/cli/local-config.d.ts +7 -0
- package/dist/cli/local-config.js +51 -3
- 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 +18 -0
- package/dist/runtime/attempt-node-runtime-launcher.js +140 -0
- 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-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 +397 -0
- 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 +20 -0
- package/dist/runtime/generated-output-reset.js +157 -0
- 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 +51 -0
- package/dist/runtime/host-listener-inspector.js +385 -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 +58 -0
- package/dist/runtime/host-runtime-contract.js +754 -0
- package/dist/runtime/host-runtime-lease.d.ts +215 -0
- package/dist/runtime/host-runtime-lease.js +2449 -0
- package/dist/runtime/host-runtime-promotion.d.ts +32 -0
- package/dist/runtime/host-runtime-promotion.js +206 -0
- 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 +54 -0
- package/dist/runtime/node-host-runtime-adapter.js +285 -0
- 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 +21 -0
- package/dist/runtime/playwright-host-runtime-adapter.js +268 -0
- 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.d.ts +67 -0
- package/dist/runtime/runtime-isolation-lease.js +161 -0
- package/dist/runtime/workspace-resource-estimator.d.ts +17 -0
- package/dist/runtime/workspace-resource-estimator.js +174 -0
- package/dist/sandbox/command-runner.d.ts +4 -0
- package/dist/sandbox/command-runner.js +47 -0
- package/dist/sandbox/contract.d.ts +5 -0
- package/dist/sandbox/contract.js +276 -0
- package/dist/sandbox/event-budget.d.ts +11 -0
- package/dist/sandbox/event-budget.js +27 -0
- package/dist/sandbox/image-policy.d.ts +35 -0
- package/dist/sandbox/image-policy.js +247 -0
- package/dist/sandbox/index.d.ts +5 -0
- package/dist/sandbox/index.js +5 -0
- package/dist/sandbox/lease-watchdog.d.ts +18 -0
- package/dist/sandbox/lease-watchdog.js +67 -0
- package/dist/sandbox/preflight.d.ts +84 -0
- package/dist/sandbox/preflight.js +521 -0
- package/dist/sandbox/process.d.ts +20 -0
- package/dist/sandbox/process.js +209 -0
- package/dist/sandbox/provider-auth/codex.d.ts +26 -0
- package/dist/sandbox/provider-auth/codex.js +212 -0
- package/dist/sandbox/repository-lock.d.ts +6 -0
- package/dist/sandbox/repository-lock.js +128 -0
- package/dist/sandbox/runtime.d.ts +302 -0
- package/dist/sandbox/runtime.js +2367 -0
- package/dist/sandbox/sandbox.types.d.ts +93 -0
- package/dist/sandbox/sandbox.types.js +1 -0
- package/dist/sandbox/smoke.d.ts +76 -0
- package/dist/sandbox/smoke.js +380 -0
- package/dist/sandbox/worker-supervisor.d.ts +13 -0
- package/dist/sandbox/worker-supervisor.js +36 -0
- package/dist/sandbox/worker.d.ts +1 -0
- package/dist/sandbox/worker.js +225 -0
- package/dist/sandbox/workspace.service.d.ts +13 -0
- package/dist/sandbox/workspace.service.js +74 -0
- package/dist/session/managed-attempt-tools.d.ts +28 -0
- package/dist/session/managed-attempt-tools.js +1011 -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 +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 +27 -0
- package/dist/session/phase-completion.artifacts.js +829 -0
- package/dist/session/phase-completion.d.ts +53 -42
- package/dist/session/phase-completion.helpers.d.ts +117 -0
- package/dist/session/phase-completion.helpers.js +707 -0
- package/dist/session/phase-completion.js +125 -3007
- package/dist/session/phase-completion.prompts.d.ts +14 -0
- package/dist/session/phase-completion.prompts.js +233 -0
- package/dist/session/phase-completion.runtime.d.ts +49 -0
- package/dist/session/phase-completion.runtime.js +833 -0
- package/dist/session/phase-completion.schemas.d.ts +3 -0
- package/dist/session/phase-completion.schemas.js +859 -0
- package/dist/session/phase-completion.validation.d.ts +38 -0
- package/dist/session/phase-completion.validation.js +918 -0
- 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 +16 -0
- package/dist/session/provider-cli/ci-repair-readiness.js +110 -0
- package/dist/session/provider-cli/provider-cli.artifacts.d.ts +95 -0
- package/dist/session/provider-cli/provider-cli.artifacts.js +812 -0
- package/dist/session/provider-cli/provider-cli.base.d.ts +1 -0
- package/dist/session/provider-cli/provider-cli.deterministic-payloads.d.ts +12 -0
- package/dist/session/provider-cli/provider-cli.deterministic-payloads.js +279 -0
- package/dist/session/provider-cli/provider-cli.deterministic.d.ts +14 -0
- package/dist/session/provider-cli/provider-cli.deterministic.js +407 -0
- package/dist/session/provider-cli/provider-cli.process.d.ts +99 -0
- package/dist/session/provider-cli/provider-cli.process.js +874 -0
- package/dist/session/provider-cli/provider-cli.prompts.d.ts +52 -0
- package/dist/session/provider-cli/provider-cli.prompts.js +715 -0
- package/dist/session/provider-cli/provider-cli.qa-sidecars.d.ts +11 -0
- package/dist/session/provider-cli/provider-cli.qa-sidecars.js +66 -0
- 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 +16 -0
- package/dist/session/provider-cli/provider-cli.runtime.js +78 -0
- package/dist/session/provider-cli/provider-cli.service.d.ts +47 -53
- package/dist/session/provider-cli/provider-cli.service.js +1257 -2497
- package/dist/session/provider-cli/provider-cli.types.d.ts +8 -0
- package/dist/session/provider-cli/provider-output-state.d.ts +32 -0
- package/dist/session/provider-cli/provider-output-state.js +817 -0
- 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 +448 -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 +586 -59
- package/dist/session/session.types.d.ts +324 -3
- package/dist/session/session.types.js +939 -10
- 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 +21 -1
- 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-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 +3 -0
- package/dist/shared/process/process.js +13 -7
- package/dist/shared/process/windows-launch.d.ts +8 -0
- package/dist/shared/process/windows-launch.js +41 -0
- package/dist/shared/provider-capabilities.d.ts +4 -0
- package/dist/shared/provider-capabilities.js +88 -0
- package/dist/shared/redaction/redaction.d.ts +1 -0
- package/dist/shared/redaction/redaction.js +47 -4
- 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 +82 -10
- package/dist/worktree/dependency-warmer.d.ts +53 -1
- package/dist/worktree/dependency-warmer.js +619 -11
- 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/story-qa-reference-hydrator.d.ts +4 -0
- package/dist/worktree/story-qa-reference-hydrator.js +253 -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 +16 -3
- package/dist/worktree/worktree.service.js +334 -54
- package/dist/worktree/worktree.types.d.ts +9 -1
- package/package.json +12 -7
- package/runtime/windows-job-supervisor.ps1 +722 -0
- package/runtime/windows-listener-owner.ps1 +33 -0
|
@@ -1,7 +1,14 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
1
3
|
import fs from "node:fs/promises";
|
|
4
|
+
import os from "node:os";
|
|
2
5
|
import path from "node:path";
|
|
6
|
+
import { assertHostDatabaseCapabilityBoundary } from "../runtime/host-database-capability-boundary.js";
|
|
7
|
+
import { RepositoryCoordinatorLock } from "../sandbox/repository-lock.js";
|
|
3
8
|
import { NodeProcessRunner } from "../shared/process/process.js";
|
|
9
|
+
import { buildWindowsCmdLaunch } from "../shared/process/windows-launch.js";
|
|
4
10
|
import { isSubPath } from "../shared/path/path.js";
|
|
11
|
+
import { defaultWorktreeCoordinatorStateRoot } from "./worktree-paths.js";
|
|
5
12
|
const SKIP_DIR_NAMES = new Set([
|
|
6
13
|
".git",
|
|
7
14
|
"node_modules",
|
|
@@ -20,6 +27,12 @@ const NATIVE_MODULE_HINTS = [
|
|
|
20
27
|
"better-sqlite3",
|
|
21
28
|
"@swc/core"
|
|
22
29
|
];
|
|
30
|
+
const MAX_COMMAND_FAILURE_OUTPUT_LENGTH = 8_000;
|
|
31
|
+
const DEPENDENCY_RECEIPT_SCHEMA_VERSION = 1;
|
|
32
|
+
const DEPENDENCY_PREPARATION_CONTRACT = "managed-worktree-isolated-v1";
|
|
33
|
+
const WINDOWS_COMMAND_ENVIRONMENT_PATH_LIMIT = 8_191;
|
|
34
|
+
const WINDOWS_PACKAGE_MANAGER_INTERNAL_PATH_RESERVE = 512;
|
|
35
|
+
const dependencyPreparationCoordinator = new RepositoryCoordinatorLock(defaultWorktreeCoordinatorStateRoot());
|
|
23
36
|
export async function detectWarmPaths(repoRoot, warmPathOverrides = []) {
|
|
24
37
|
const entries = new Map();
|
|
25
38
|
await collectDetectedPaths(repoRoot, repoRoot, entries, 0);
|
|
@@ -47,15 +60,51 @@ export async function warmWorktreeDependencies(request, executor = defaultComman
|
|
|
47
60
|
const linked = [];
|
|
48
61
|
const skipped = [];
|
|
49
62
|
const fallbacks = [];
|
|
63
|
+
const receipts = [];
|
|
64
|
+
const restoredDotNetRoots = new Set();
|
|
65
|
+
const installedNodeRoots = new Set();
|
|
50
66
|
for (const entry of warmPaths) {
|
|
51
67
|
const sourcePath = path.join(baseCheckoutPath, entry.relativePath);
|
|
52
68
|
const targetPath = path.join(worktreePath, entry.relativePath);
|
|
53
69
|
assertWithinRoot(allowedRoot, targetPath, "Warm target");
|
|
70
|
+
if (entry.ecosystem === "node" && path.basename(entry.relativePath) === "node_modules") {
|
|
71
|
+
await removeLegacyDirectoryLink(targetPath, allowedRoot);
|
|
72
|
+
const installPackageRoot = await resolveNodeInstallPackageRoot(worktreePath, entry.packageRoot);
|
|
73
|
+
const packageRoot = path.resolve(worktreePath, installPackageRoot === "." ? "" : installPackageRoot);
|
|
74
|
+
const packageKey = process.platform === "win32" ? packageRoot.toLowerCase() : packageRoot;
|
|
75
|
+
if (installedNodeRoots.has(packageKey)) {
|
|
76
|
+
skipped.push(entry.relativePath);
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
installedNodeRoots.add(packageKey);
|
|
80
|
+
const preparation = await ensureInstallFallback({ ...entry, packageRoot: installPackageRoot }, worktreePath, "Managed worktrees require isolated Node dependencies.", executor, request.preparationPolicy);
|
|
81
|
+
receipts.push(preparation.receipt);
|
|
82
|
+
if (preparation.install)
|
|
83
|
+
fallbacks.push(preparation.install);
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (entry.ecosystem === "dotnet") {
|
|
87
|
+
await removeLegacyDirectoryLink(targetPath, allowedRoot);
|
|
88
|
+
const packageRoot = path.resolve(worktreePath, entry.packageRoot === "." ? "" : entry.packageRoot);
|
|
89
|
+
const packageKey = process.platform === "win32" ? packageRoot.toLowerCase() : packageRoot;
|
|
90
|
+
if (restoredDotNetRoots.has(packageKey)) {
|
|
91
|
+
skipped.push(entry.relativePath);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
restoredDotNetRoots.add(packageKey);
|
|
95
|
+
const preparation = await ensureInstallFallback(entry, worktreePath, "Managed worktrees require isolated .NET build outputs.", executor, request.preparationPolicy);
|
|
96
|
+
receipts.push(preparation.receipt);
|
|
97
|
+
if (preparation.install)
|
|
98
|
+
fallbacks.push(preparation.install);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
54
101
|
const sourceExists = await directoryExists(sourcePath);
|
|
55
102
|
const fallbackReason = await resolveFallbackReason(entry, baseCheckoutPath, worktreePath, sourceExists);
|
|
56
103
|
if (fallbackReason) {
|
|
57
|
-
const
|
|
58
|
-
|
|
104
|
+
const preparation = await ensureInstallFallback(entry, worktreePath, fallbackReason, executor, request.preparationPolicy);
|
|
105
|
+
receipts.push(preparation.receipt);
|
|
106
|
+
if (preparation.install)
|
|
107
|
+
fallbacks.push(preparation.install);
|
|
59
108
|
continue;
|
|
60
109
|
}
|
|
61
110
|
if (!sourceExists) {
|
|
@@ -65,7 +114,17 @@ export async function warmWorktreeDependencies(request, executor = defaultComman
|
|
|
65
114
|
await createDirectoryLink(sourcePath, targetPath, allowedRoot);
|
|
66
115
|
linked.push(entry.relativePath);
|
|
67
116
|
}
|
|
68
|
-
return { linked, skipped, fallbacks };
|
|
117
|
+
return { linked, skipped, fallbacks, receipts };
|
|
118
|
+
}
|
|
119
|
+
async function removeLegacyDirectoryLink(targetPath, allowedRoot) {
|
|
120
|
+
const resolvedTarget = path.resolve(targetPath);
|
|
121
|
+
const resolvedRoot = path.resolve(allowedRoot);
|
|
122
|
+
assertWithinRoot(resolvedRoot, resolvedTarget, "Legacy warm target");
|
|
123
|
+
const stat = await fs.lstat(resolvedTarget).catch(() => null);
|
|
124
|
+
if (!stat?.isSymbolicLink()) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
await fs.rm(resolvedTarget, { force: true });
|
|
69
128
|
}
|
|
70
129
|
export async function createDirectoryLink(sourcePath, targetPath, allowedRoot) {
|
|
71
130
|
const resolvedSource = path.resolve(sourcePath);
|
|
@@ -78,7 +137,10 @@ export async function createDirectoryLink(sourcePath, targetPath, allowedRoot) {
|
|
|
78
137
|
if (!stat?.isDirectory()) {
|
|
79
138
|
throw new Error(`Link source must be an existing directory: ${resolvedSource}`);
|
|
80
139
|
}
|
|
81
|
-
await fs.
|
|
140
|
+
const existingTarget = await fs.lstat(resolvedTarget).catch(() => null);
|
|
141
|
+
await fs.rm(resolvedTarget, existingTarget?.isSymbolicLink()
|
|
142
|
+
? { force: true }
|
|
143
|
+
: { recursive: true, force: true });
|
|
82
144
|
await fs.mkdir(path.dirname(resolvedTarget), { recursive: true });
|
|
83
145
|
if (process.platform === "win32") {
|
|
84
146
|
await fs.symlink(resolvedSource, resolvedTarget, "junction");
|
|
@@ -179,20 +241,249 @@ export async function lockfilesDiffer(baseCheckoutPath, worktreePath, packageRoo
|
|
|
179
241
|
}
|
|
180
242
|
return false;
|
|
181
243
|
}
|
|
244
|
+
async function ensureInstallFallback(entry, worktreePath, reason, executor, policy) {
|
|
245
|
+
const startedAt = performance.now();
|
|
246
|
+
const cwd = path.resolve(worktreePath, entry.packageRoot === "." ? "" : entry.packageRoot);
|
|
247
|
+
assertWithinRoot(path.resolve(worktreePath), cwd, "Dependency package root");
|
|
248
|
+
const command = await installFallbackCommand(entry, cwd);
|
|
249
|
+
const outputMarker = dependencyOutputMarker(entry.ecosystem, cwd);
|
|
250
|
+
const receiptPath = path.join(outputMarker, ".forge-dependency-preparation.json");
|
|
251
|
+
const fingerprint = await dependencyPreparationFingerprint(entry, worktreePath, cwd, command, policy);
|
|
252
|
+
return await dependencyPreparationCoordinator.withLock(receiptPath, async () => {
|
|
253
|
+
const existing = await readDependencyPreparationReceipt(receiptPath);
|
|
254
|
+
if (existing?.fingerprint === fingerprint
|
|
255
|
+
&& existing.ecosystem === entry.ecosystem
|
|
256
|
+
&& existing.packageRoot === entry.packageRoot
|
|
257
|
+
&& existing.command === command
|
|
258
|
+
&& await directoryExists(outputMarker)) {
|
|
259
|
+
await markNpmCiRestored(outputMarker, command);
|
|
260
|
+
return {
|
|
261
|
+
install: null,
|
|
262
|
+
receipt: {
|
|
263
|
+
ecosystem: entry.ecosystem,
|
|
264
|
+
packageRoot: entry.packageRoot,
|
|
265
|
+
command,
|
|
266
|
+
cacheHit: true,
|
|
267
|
+
fingerprint,
|
|
268
|
+
receiptPath,
|
|
269
|
+
durationMs: elapsedMilliseconds(startedAt),
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
const install = await runInstallFallback(entry, worktreePath, reason, executor);
|
|
274
|
+
// npm is allowed to omit node_modules when the package graph is empty. Keep
|
|
275
|
+
// an isolated, ignored output directory so the durable receipt still has a
|
|
276
|
+
// concrete marker whose removal invalidates reuse.
|
|
277
|
+
if (entry.ecosystem === "node") {
|
|
278
|
+
await fs.mkdir(outputMarker, { recursive: true });
|
|
279
|
+
}
|
|
280
|
+
if (!(await directoryExists(outputMarker))) {
|
|
281
|
+
throw new Error(`dependency_preparation_output_missing: ${install.command} succeeded without creating ${outputMarker}.`);
|
|
282
|
+
}
|
|
283
|
+
const receipt = {
|
|
284
|
+
schemaVersion: DEPENDENCY_RECEIPT_SCHEMA_VERSION,
|
|
285
|
+
fingerprint,
|
|
286
|
+
ecosystem: entry.ecosystem,
|
|
287
|
+
packageRoot: entry.packageRoot,
|
|
288
|
+
command,
|
|
289
|
+
outputMarker: path.relative(cwd, outputMarker).replaceAll("\\", "/"),
|
|
290
|
+
completedAt: new Date().toISOString(),
|
|
291
|
+
};
|
|
292
|
+
await persistDependencyPreparationReceipt(receiptPath, receipt);
|
|
293
|
+
await markNpmCiRestored(outputMarker, command);
|
|
294
|
+
return {
|
|
295
|
+
install,
|
|
296
|
+
receipt: {
|
|
297
|
+
ecosystem: entry.ecosystem,
|
|
298
|
+
packageRoot: entry.packageRoot,
|
|
299
|
+
command,
|
|
300
|
+
cacheHit: false,
|
|
301
|
+
fingerprint,
|
|
302
|
+
receiptPath,
|
|
303
|
+
durationMs: elapsedMilliseconds(startedAt),
|
|
304
|
+
},
|
|
305
|
+
};
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
async function markNpmCiRestored(outputMarker, command) {
|
|
309
|
+
if (command !== "npm ci")
|
|
310
|
+
return;
|
|
311
|
+
// Forge.Api's RestoreForgeBridge target uses this stamp as its incremental
|
|
312
|
+
// output. Worktree preparation has already installed and verified the same
|
|
313
|
+
// package graph, so an immediate dotnet build must not repeat npm ci.
|
|
314
|
+
await fs.writeFile(path.join(outputMarker, ".forge-restore.stamp"), "", { mode: 0o600 });
|
|
315
|
+
}
|
|
316
|
+
function elapsedMilliseconds(startedAt) {
|
|
317
|
+
return Math.max(0, Math.round((performance.now() - startedAt) * 1000) / 1000);
|
|
318
|
+
}
|
|
319
|
+
async function installFallbackCommand(entry, cwd) {
|
|
320
|
+
if (entry.ecosystem === "node") {
|
|
321
|
+
if (await pathExists(path.join(cwd, "pnpm-lock.yaml")))
|
|
322
|
+
return "pnpm install --frozen-lockfile";
|
|
323
|
+
return await pathExists(path.join(cwd, "package-lock.json")) ? "npm ci" : "npm install";
|
|
324
|
+
}
|
|
325
|
+
if (entry.ecosystem === "python") {
|
|
326
|
+
return await pathExists(path.join(cwd, "requirements.txt"))
|
|
327
|
+
? "python -m venv .venv && pip install -r requirements.txt"
|
|
328
|
+
: "python -m venv .venv";
|
|
329
|
+
}
|
|
330
|
+
return "dotnet restore";
|
|
331
|
+
}
|
|
332
|
+
function dependencyOutputMarker(ecosystem, cwd) {
|
|
333
|
+
if (ecosystem === "node")
|
|
334
|
+
return path.join(cwd, "node_modules");
|
|
335
|
+
if (ecosystem === "python")
|
|
336
|
+
return path.join(cwd, ".venv");
|
|
337
|
+
return path.join(cwd, "obj");
|
|
338
|
+
}
|
|
339
|
+
async function dependencyPreparationFingerprint(entry, worktreePath, cwd, command, policy) {
|
|
340
|
+
const inputFiles = await dependencyInputFiles(entry.ecosystem, worktreePath, cwd);
|
|
341
|
+
const inputDigests = await Promise.all(inputFiles.map(async (file) => ({
|
|
342
|
+
path: path.relative(worktreePath, file).replaceAll("\\", "/"),
|
|
343
|
+
digest: await sha256File(file),
|
|
344
|
+
})));
|
|
345
|
+
const toolchainVersion = await dependencyToolchainVersion(entry.ecosystem, cwd, policy);
|
|
346
|
+
const trustDomain = policy?.trustDomain ?? process.env.FORGE_TRUST_DOMAIN_ID ?? "default";
|
|
347
|
+
const invalidationEpoch = policy?.invalidationEpoch ?? process.env.FORGE_DEPENDENCY_CACHE_EPOCH ?? "default";
|
|
348
|
+
const contract = {
|
|
349
|
+
schemaVersion: DEPENDENCY_RECEIPT_SCHEMA_VERSION,
|
|
350
|
+
preparationContract: DEPENDENCY_PREPARATION_CONTRACT,
|
|
351
|
+
ecosystem: entry.ecosystem,
|
|
352
|
+
packageRoot: entry.packageRoot.replaceAll("\\", "/"),
|
|
353
|
+
command,
|
|
354
|
+
inputDigests,
|
|
355
|
+
toolchainVersion,
|
|
356
|
+
nodeRuntime: process.version,
|
|
357
|
+
platform: process.platform,
|
|
358
|
+
architecture: process.arch,
|
|
359
|
+
osRelease: os.release(),
|
|
360
|
+
isolation: policy?.isolation ?? process.env.FORGE_RUNTIME_MODE ?? "managed_host_process",
|
|
361
|
+
trustDomainFingerprint: sha256Text(trustDomain),
|
|
362
|
+
invalidationEpochFingerprint: sha256Text(invalidationEpoch),
|
|
363
|
+
};
|
|
364
|
+
return `sha256:${sha256Text(JSON.stringify(contract))}`;
|
|
365
|
+
}
|
|
366
|
+
async function dependencyInputFiles(ecosystem, worktreePath, cwd) {
|
|
367
|
+
const result = new Set();
|
|
368
|
+
const directNames = ecosystem === "node"
|
|
369
|
+
? ["package.json", "package-lock.json", "npm-shrinkwrap.json", "pnpm-lock.yaml", "pnpm-workspace.yaml", "yarn.lock", ".npmrc", ".yarnrc", ".yarnrc.yml", ".nvmrc", ".node-version"]
|
|
370
|
+
: ecosystem === "python"
|
|
371
|
+
? ["pyproject.toml", "requirements.txt", "requirements-dev.txt", "poetry.lock", "Pipfile", "Pipfile.lock", "uv.lock", "pip.conf", "pyvenv.cfg"]
|
|
372
|
+
: ["global.json", "NuGet.Config", "nuget.config", "Directory.Build.props", "Directory.Build.targets", "Directory.Packages.props", "packages.lock.json"];
|
|
373
|
+
let current = cwd;
|
|
374
|
+
const root = path.resolve(worktreePath);
|
|
375
|
+
while (isSubPath(root, current)) {
|
|
376
|
+
for (const name of directNames) {
|
|
377
|
+
const candidate = path.join(current, name);
|
|
378
|
+
if (await pathExists(candidate))
|
|
379
|
+
result.add(candidate);
|
|
380
|
+
}
|
|
381
|
+
if (ecosystem === "dotnet" && current === cwd) {
|
|
382
|
+
const entries = await fs.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
383
|
+
for (const item of entries) {
|
|
384
|
+
if (item.isFile() && (/\.(?:csproj|fsproj|vbproj|sln|slnx)$/iu.test(item.name))) {
|
|
385
|
+
result.add(path.join(current, item.name));
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
if (current === root)
|
|
390
|
+
break;
|
|
391
|
+
current = path.dirname(current);
|
|
392
|
+
}
|
|
393
|
+
return [...result].sort((left, right) => left.localeCompare(right));
|
|
394
|
+
}
|
|
395
|
+
async function dependencyToolchainVersion(ecosystem, cwd, policy) {
|
|
396
|
+
if (ecosystem === "node") {
|
|
397
|
+
const manager = await pathExists(path.join(cwd, "pnpm-lock.yaml")) ? "pnpm" : "npm";
|
|
398
|
+
const override = policy?.toolchainVersions?.[manager];
|
|
399
|
+
if (override)
|
|
400
|
+
return `${manager}@${override};node@${process.version}`;
|
|
401
|
+
const launch = manager === "pnpm" ? resolvePnpmLaunch(["--version"]) : resolveNpmLaunch(["--version"]);
|
|
402
|
+
return `${manager}@${await readToolVersion(cwd, launch.command, launch.args)};node@${process.version}`;
|
|
403
|
+
}
|
|
404
|
+
const override = policy?.toolchainVersions?.[ecosystem];
|
|
405
|
+
if (override)
|
|
406
|
+
return `${ecosystem}@${override}`;
|
|
407
|
+
const launch = ecosystem === "dotnet"
|
|
408
|
+
? { command: "dotnet", args: ["--version"] }
|
|
409
|
+
: { command: "python", args: ["--version"] };
|
|
410
|
+
return `${ecosystem}@${await readToolVersion(cwd, launch.command, launch.args)}`;
|
|
411
|
+
}
|
|
412
|
+
async function readToolVersion(cwd, command, args) {
|
|
413
|
+
return await defaultProcessRunner.run({ command, cwd, args })
|
|
414
|
+
.then(result => result.exitCode === 0
|
|
415
|
+
? (result.stdout.trim() || result.stderr.trim() || "unknown").slice(0, 256)
|
|
416
|
+
: `unavailable:${result.exitCode}`)
|
|
417
|
+
.catch(() => "unavailable");
|
|
418
|
+
}
|
|
419
|
+
async function readDependencyPreparationReceipt(receiptPath) {
|
|
420
|
+
try {
|
|
421
|
+
const parsed = JSON.parse(await fs.readFile(receiptPath, "utf8"));
|
|
422
|
+
return parsed.schemaVersion === DEPENDENCY_RECEIPT_SCHEMA_VERSION
|
|
423
|
+
&& typeof parsed.fingerprint === "string"
|
|
424
|
+
&& typeof parsed.ecosystem === "string"
|
|
425
|
+
&& typeof parsed.packageRoot === "string"
|
|
426
|
+
&& typeof parsed.command === "string"
|
|
427
|
+
&& typeof parsed.outputMarker === "string"
|
|
428
|
+
? parsed
|
|
429
|
+
: null;
|
|
430
|
+
}
|
|
431
|
+
catch (error) {
|
|
432
|
+
if (error.code === "ENOENT" || error instanceof SyntaxError)
|
|
433
|
+
return null;
|
|
434
|
+
throw error;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
async function persistDependencyPreparationReceipt(receiptPath, receipt) {
|
|
438
|
+
await fs.mkdir(path.dirname(receiptPath), { recursive: true });
|
|
439
|
+
const temporaryPath = `${receiptPath}.${randomUUID()}.tmp`;
|
|
440
|
+
await fs.writeFile(temporaryPath, `${JSON.stringify(receipt, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
|
|
441
|
+
try {
|
|
442
|
+
await fs.rename(temporaryPath, receiptPath);
|
|
443
|
+
}
|
|
444
|
+
finally {
|
|
445
|
+
await fs.rm(temporaryPath, { force: true }).catch(() => undefined);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
function sha256Text(value) {
|
|
449
|
+
return createHash("sha256").update(value).digest("hex");
|
|
450
|
+
}
|
|
182
451
|
async function runInstallFallback(entry, worktreePath, reason, executor) {
|
|
452
|
+
assertHostDatabaseCapabilityBoundary();
|
|
183
453
|
const cwd = path.join(worktreePath, entry.packageRoot === "." ? "" : entry.packageRoot);
|
|
184
454
|
if (entry.ecosystem === "node") {
|
|
455
|
+
const pnpmLockPath = path.join(cwd, "pnpm-lock.yaml");
|
|
456
|
+
if (await pathExists(pnpmLockPath)) {
|
|
457
|
+
const args = ["install", "--frozen-lockfile"];
|
|
458
|
+
const launch = resolvePnpmLaunch(args);
|
|
459
|
+
await executor.run(cwd, launch.command, launch.args, {
|
|
460
|
+
env: unattendedPackageManagerEnvironment(process.env, {
|
|
461
|
+
packageRoot: cwd,
|
|
462
|
+
requiredPathDirectories: launch.requiredPathDirectories,
|
|
463
|
+
}),
|
|
464
|
+
windowsVerbatimArguments: launch.windowsVerbatimArguments,
|
|
465
|
+
});
|
|
466
|
+
return { ecosystem: entry.ecosystem, packageRoot: entry.packageRoot, reason, command: `pnpm ${args.join(" ")}` };
|
|
467
|
+
}
|
|
185
468
|
const lockPath = path.join(cwd, "package-lock.json");
|
|
186
469
|
const args = (await pathExists(lockPath)) ? ["ci"] : ["install"];
|
|
187
|
-
|
|
470
|
+
const launch = resolveNpmLaunch(args);
|
|
471
|
+
await executor.run(cwd, launch.command, launch.args, {
|
|
472
|
+
env: unattendedPackageManagerEnvironment(process.env, {
|
|
473
|
+
packageRoot: cwd,
|
|
474
|
+
requiredPathDirectories: launch.requiredPathDirectories,
|
|
475
|
+
}),
|
|
476
|
+
windowsVerbatimArguments: launch.windowsVerbatimArguments,
|
|
477
|
+
});
|
|
188
478
|
return { ecosystem: entry.ecosystem, packageRoot: entry.packageRoot, reason, command: `npm ${args.join(" ")}` };
|
|
189
479
|
}
|
|
190
480
|
if (entry.ecosystem === "python") {
|
|
191
|
-
|
|
481
|
+
const environment = unattendedDependencyEnvironment();
|
|
482
|
+
await executor.run(cwd, "python", ["-m", "venv", ".venv"], { env: environment });
|
|
192
483
|
const requirements = path.join(cwd, "requirements.txt");
|
|
193
484
|
if (await pathExists(requirements)) {
|
|
194
485
|
const pip = process.platform === "win32" ? path.join(".venv", "Scripts", "pip") : path.join(".venv", "bin", "pip");
|
|
195
|
-
await executor.run(cwd, pip, ["install", "-r", "requirements.txt"]);
|
|
486
|
+
await executor.run(cwd, pip, ["install", "-r", "requirements.txt"], { env: environment });
|
|
196
487
|
return {
|
|
197
488
|
ecosystem: entry.ecosystem,
|
|
198
489
|
packageRoot: entry.packageRoot,
|
|
@@ -207,7 +498,7 @@ async function runInstallFallback(entry, worktreePath, reason, executor) {
|
|
|
207
498
|
command: "python -m venv .venv"
|
|
208
499
|
};
|
|
209
500
|
}
|
|
210
|
-
await executor.run(cwd, "dotnet", ["restore"]);
|
|
501
|
+
await executor.run(cwd, "dotnet", ["restore"], { env: unattendedDependencyEnvironment() });
|
|
211
502
|
return {
|
|
212
503
|
ecosystem: entry.ecosystem,
|
|
213
504
|
packageRoot: entry.packageRoot,
|
|
@@ -215,6 +506,23 @@ async function runInstallFallback(entry, worktreePath, reason, executor) {
|
|
|
215
506
|
command: "dotnet restore"
|
|
216
507
|
};
|
|
217
508
|
}
|
|
509
|
+
async function resolveNodeInstallPackageRoot(worktreePath, packageRoot) {
|
|
510
|
+
const resolvedWorktree = path.resolve(worktreePath);
|
|
511
|
+
let current = path.resolve(resolvedWorktree, packageRoot === "." ? "" : packageRoot);
|
|
512
|
+
assertWithinRoot(resolvedWorktree, current, "Node package root");
|
|
513
|
+
while (isSubPath(resolvedWorktree, current)) {
|
|
514
|
+
if (await pathExists(path.join(current, "pnpm-lock.yaml"))
|
|
515
|
+
|| await pathExists(path.join(current, "pnpm-workspace.yaml"))
|
|
516
|
+
|| await pathExists(path.join(current, "package-lock.json"))) {
|
|
517
|
+
const relative = normalizeRelativePath(path.relative(resolvedWorktree, current));
|
|
518
|
+
return relative || ".";
|
|
519
|
+
}
|
|
520
|
+
if (current === resolvedWorktree)
|
|
521
|
+
break;
|
|
522
|
+
current = path.dirname(current);
|
|
523
|
+
}
|
|
524
|
+
return packageRoot;
|
|
525
|
+
}
|
|
218
526
|
export async function requiresInstallFallback(entry, baseCheckoutPath, worktreePath, sourceExists) {
|
|
219
527
|
return resolveFallbackReason(entry, baseCheckoutPath, worktreePath, sourceExists);
|
|
220
528
|
}
|
|
@@ -286,11 +594,311 @@ async function sha256File(filePath) {
|
|
|
286
594
|
const content = await fs.readFile(filePath);
|
|
287
595
|
return crypto.createHash("sha256").update(content).digest("hex");
|
|
288
596
|
}
|
|
597
|
+
export function resolveNpmLaunch(args, options = {}) {
|
|
598
|
+
const env = options.env ?? process.env;
|
|
599
|
+
const execPath = options.execPath ?? process.execPath;
|
|
600
|
+
const platform = options.platform ?? process.platform;
|
|
601
|
+
const pathExists = options.pathExists ?? existsSync;
|
|
602
|
+
const npmExecPath = env.npm_execpath;
|
|
603
|
+
const npmExecPathIsAbsolute = platform === "win32"
|
|
604
|
+
? path.win32.isAbsolute(npmExecPath ?? "")
|
|
605
|
+
: path.posix.isAbsolute(npmExecPath ?? "");
|
|
606
|
+
const npmExecPathBaseName = platform === "win32"
|
|
607
|
+
? path.win32.basename(npmExecPath ?? "")
|
|
608
|
+
: path.posix.basename(npmExecPath ?? "");
|
|
609
|
+
if (npmExecPath
|
|
610
|
+
&& npmExecPathIsAbsolute
|
|
611
|
+
&& npmExecPathBaseName.toLowerCase() === "npm-cli.js"
|
|
612
|
+
&& pathExists(npmExecPath)) {
|
|
613
|
+
return { command: execPath, args: [npmExecPath, ...args] };
|
|
614
|
+
}
|
|
615
|
+
if (platform === "win32") {
|
|
616
|
+
const command = resolveWindowsPackageManagerCommand("npm.cmd", env, pathExists, [path.win32.dirname(execPath)]);
|
|
617
|
+
if (!command) {
|
|
618
|
+
throw new Error("Windows npm launcher could not be resolved before PATH compaction.");
|
|
619
|
+
}
|
|
620
|
+
const cli = resolveWindowsPackageManagerCli("npm", command, execPath, pathExists);
|
|
621
|
+
if (cli)
|
|
622
|
+
return { command: execPath, args: [cli, ...args] };
|
|
623
|
+
const comSpec = options.env
|
|
624
|
+
? windowsEnvironmentValue(env, "ComSpec") ?? "cmd.exe"
|
|
625
|
+
: windowsEnvironmentValue(env, "ComSpec");
|
|
626
|
+
return {
|
|
627
|
+
...buildWindowsCmdLaunch(command, args, comSpec),
|
|
628
|
+
requiredPathDirectories: [path.win32.dirname(command)],
|
|
629
|
+
windowsVerbatimArguments: true,
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
for (const candidate of options.candidates ?? defaultNpmCliCandidates(execPath, options.homeDir ?? os.homedir())) {
|
|
633
|
+
const resolvedCandidate = tryResolveNpmCandidate(candidate);
|
|
634
|
+
if (resolvedCandidate) {
|
|
635
|
+
return { command: execPath, args: [resolvedCandidate, ...args] };
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
return { command: "npm", args };
|
|
639
|
+
}
|
|
640
|
+
export function resolvePnpmLaunch(args, options = {}) {
|
|
641
|
+
const platform = options.platform ?? process.platform;
|
|
642
|
+
if (platform === "win32") {
|
|
643
|
+
const env = options.env ?? process.env;
|
|
644
|
+
const execPath = options.execPath ?? process.execPath;
|
|
645
|
+
const command = resolveWindowsPackageManagerCommand("pnpm.cmd", env, options.pathExists ?? existsSync, [path.win32.dirname(execPath)]);
|
|
646
|
+
if (!command) {
|
|
647
|
+
throw new Error("Windows pnpm launcher could not be resolved before PATH compaction.");
|
|
648
|
+
}
|
|
649
|
+
const cli = resolveWindowsPackageManagerCli("pnpm", command, execPath, options.pathExists ?? existsSync);
|
|
650
|
+
if (cli)
|
|
651
|
+
return { command: execPath, args: [cli, ...args] };
|
|
652
|
+
const comSpec = options.env
|
|
653
|
+
? windowsEnvironmentValue(env, "ComSpec") ?? "cmd.exe"
|
|
654
|
+
: windowsEnvironmentValue(env, "ComSpec");
|
|
655
|
+
return {
|
|
656
|
+
...buildWindowsCmdLaunch(command, args, comSpec),
|
|
657
|
+
requiredPathDirectories: [path.win32.dirname(command)],
|
|
658
|
+
windowsVerbatimArguments: true,
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
return { command: "pnpm", args };
|
|
662
|
+
}
|
|
663
|
+
export function formatCommandFailureOutput(stdout, stderr) {
|
|
664
|
+
const sections = [
|
|
665
|
+
stderr.trim().length > 0 ? `stderr:\n${stderr.trim()}` : null,
|
|
666
|
+
stdout.trim().length > 0 ? `stdout:\n${stdout.trim()}` : null,
|
|
667
|
+
].filter((section) => section !== null);
|
|
668
|
+
const output = sections.join("\n");
|
|
669
|
+
if (output.length <= MAX_COMMAND_FAILURE_OUTPUT_LENGTH) {
|
|
670
|
+
return output;
|
|
671
|
+
}
|
|
672
|
+
return `[last ${MAX_COMMAND_FAILURE_OUTPUT_LENGTH} characters]\n${output.slice(-MAX_COMMAND_FAILURE_OUTPUT_LENGTH)}`;
|
|
673
|
+
}
|
|
674
|
+
export function unattendedPackageManagerEnvironment(env = process.env, options = {}) {
|
|
675
|
+
const result = { ...unattendedDependencyEnvironment(env), CI: "true" };
|
|
676
|
+
if ((options.platform ?? process.platform) !== "win32")
|
|
677
|
+
return result;
|
|
678
|
+
const pathValue = windowsEnvironmentValues(env, "Path").join(";");
|
|
679
|
+
for (const key of Object.keys(result)) {
|
|
680
|
+
if (key.toLowerCase() === "path")
|
|
681
|
+
delete result[key];
|
|
682
|
+
}
|
|
683
|
+
result.PATH = compactWindowsPackageManagerPath(pathValue, env, options);
|
|
684
|
+
return result;
|
|
685
|
+
}
|
|
686
|
+
const dependencyEnvironmentAllowlist = new Set([
|
|
687
|
+
"appdata",
|
|
688
|
+
"comspec",
|
|
689
|
+
"corepack_home",
|
|
690
|
+
"dotnet_cli_home",
|
|
691
|
+
"dotnet_root",
|
|
692
|
+
"home",
|
|
693
|
+
"homedrive",
|
|
694
|
+
"homepath",
|
|
695
|
+
"http_proxy",
|
|
696
|
+
"https_proxy",
|
|
697
|
+
"localappdata",
|
|
698
|
+
"no_proxy",
|
|
699
|
+
"npm_config_cache",
|
|
700
|
+
"nuget_http_cache_path",
|
|
701
|
+
"nuget_packages",
|
|
702
|
+
"path",
|
|
703
|
+
"pathext",
|
|
704
|
+
"programdata",
|
|
705
|
+
"programfiles",
|
|
706
|
+
"programfiles(x86)",
|
|
707
|
+
"programw6432",
|
|
708
|
+
"ssl_cert_dir",
|
|
709
|
+
"ssl_cert_file",
|
|
710
|
+
"systemdrive",
|
|
711
|
+
"systemroot",
|
|
712
|
+
"temp",
|
|
713
|
+
"tmp",
|
|
714
|
+
"tmpdir",
|
|
715
|
+
"userdomain",
|
|
716
|
+
"username",
|
|
717
|
+
"userprofile",
|
|
718
|
+
"windir",
|
|
719
|
+
]);
|
|
720
|
+
export function unattendedDependencyEnvironment(env = process.env) {
|
|
721
|
+
return Object.fromEntries(Object.entries(env).filter(([name, value]) => typeof value === "string"
|
|
722
|
+
&& (dependencyEnvironmentAllowlist.has(name.toLowerCase()) || name.toUpperCase().startsWith("LC_"))));
|
|
723
|
+
}
|
|
724
|
+
export function compactWindowsPackageManagerPath(pathValue, env = process.env, options = {}) {
|
|
725
|
+
const pathExists = options.pathExists ?? existsSync;
|
|
726
|
+
const maximumPathLength = options.maximumPathLength
|
|
727
|
+
?? windowsPackageManagerPathBudget(options.packageRoot ?? process.cwd());
|
|
728
|
+
const execPath = options.execPath ?? process.execPath;
|
|
729
|
+
if (!Number.isSafeInteger(maximumPathLength) || maximumPathLength < 1) {
|
|
730
|
+
throw new Error(`Windows package-manager PATH budget is exhausted before launch (${maximumPathLength}).`);
|
|
731
|
+
}
|
|
732
|
+
const normalize = windowsPathNormalizer(env);
|
|
733
|
+
const inherited = [];
|
|
734
|
+
const seen = new Set();
|
|
735
|
+
for (const candidate of pathValue.split(";").map(normalize)) {
|
|
736
|
+
if (candidate.length === 0 || !path.win32.isAbsolute(candidate) || !pathExists(candidate))
|
|
737
|
+
continue;
|
|
738
|
+
const key = candidate.toLowerCase();
|
|
739
|
+
if (seen.has(key))
|
|
740
|
+
continue;
|
|
741
|
+
inherited.push(candidate);
|
|
742
|
+
seen.add(key);
|
|
743
|
+
}
|
|
744
|
+
const nodeDirectory = normalize(path.win32.dirname(execPath));
|
|
745
|
+
if (!nodeDirectory || !path.win32.isAbsolute(nodeDirectory) || !pathExists(nodeDirectory)) {
|
|
746
|
+
throw new Error(`Windows Node runtime directory is unavailable: ${nodeDirectory || "<empty>"}.`);
|
|
747
|
+
}
|
|
748
|
+
const systemRoot = normalize(windowsEnvironmentValue(env, "SystemRoot") ?? "");
|
|
749
|
+
const requiredCandidates = [
|
|
750
|
+
nodeDirectory,
|
|
751
|
+
normalize(path.win32.dirname(windowsEnvironmentValue(env, "ComSpec") ?? "")),
|
|
752
|
+
systemRoot ? path.win32.join(systemRoot, "System32") : "",
|
|
753
|
+
systemRoot,
|
|
754
|
+
].filter(candidate => candidate.length > 0
|
|
755
|
+
&& path.win32.isAbsolute(candidate)
|
|
756
|
+
&& pathExists(candidate));
|
|
757
|
+
const explicitRequired = (options.requiredPathDirectories ?? []).map(normalize);
|
|
758
|
+
for (const candidate of explicitRequired) {
|
|
759
|
+
if (!candidate || !path.win32.isAbsolute(candidate) || !pathExists(candidate)) {
|
|
760
|
+
throw new Error(`Windows package-manager required PATH directory is unavailable: ${candidate || "<empty>"}.`);
|
|
761
|
+
}
|
|
762
|
+
requiredCandidates.push(candidate);
|
|
763
|
+
}
|
|
764
|
+
const required = new Map();
|
|
765
|
+
for (const candidate of requiredCandidates)
|
|
766
|
+
required.set(candidate.toLowerCase(), candidate);
|
|
767
|
+
const ordered = [...inherited];
|
|
768
|
+
for (const candidate of required.values()) {
|
|
769
|
+
if (!seen.has(candidate.toLowerCase()))
|
|
770
|
+
ordered.push(candidate);
|
|
771
|
+
}
|
|
772
|
+
const requiredLength = joinedWindowsPathLength([...required.values()]);
|
|
773
|
+
if (requiredLength > maximumPathLength) {
|
|
774
|
+
throw new Error(`Windows package-manager PATH cannot retain required runtime directories within ${maximumPathLength} characters.`);
|
|
775
|
+
}
|
|
776
|
+
const selected = new Set(required.keys());
|
|
777
|
+
let selectedLength = requiredLength;
|
|
778
|
+
for (const candidate of inherited) {
|
|
779
|
+
const key = candidate.toLowerCase();
|
|
780
|
+
if (selected.has(key))
|
|
781
|
+
continue;
|
|
782
|
+
const nextLength = selectedLength + (selected.size === 0 ? 0 : 1) + candidate.length;
|
|
783
|
+
if (nextLength > maximumPathLength)
|
|
784
|
+
continue;
|
|
785
|
+
selected.add(key);
|
|
786
|
+
selectedLength = nextLength;
|
|
787
|
+
}
|
|
788
|
+
return ordered.filter(candidate => selected.has(candidate.toLowerCase())).join(";");
|
|
789
|
+
}
|
|
790
|
+
export function windowsPackageManagerLifecyclePathPrefix(packageRoot) {
|
|
791
|
+
const entries = [];
|
|
792
|
+
let current = path.win32.resolve(packageRoot);
|
|
793
|
+
while (true) {
|
|
794
|
+
entries.push(path.win32.join(current, "node_modules", ".bin"));
|
|
795
|
+
const parent = path.win32.dirname(current);
|
|
796
|
+
if (parent === current)
|
|
797
|
+
break;
|
|
798
|
+
current = parent;
|
|
799
|
+
}
|
|
800
|
+
return entries.join(";");
|
|
801
|
+
}
|
|
802
|
+
export function windowsPackageManagerPathBudget(packageRoot) {
|
|
803
|
+
const lifecyclePrefix = windowsPackageManagerLifecyclePathPrefix(packageRoot);
|
|
804
|
+
return WINDOWS_COMMAND_ENVIRONMENT_PATH_LIMIT
|
|
805
|
+
- lifecyclePrefix.length
|
|
806
|
+
- 1
|
|
807
|
+
- WINDOWS_PACKAGE_MANAGER_INTERNAL_PATH_RESERVE;
|
|
808
|
+
}
|
|
809
|
+
function resolveWindowsPackageManagerCommand(commandName, env, pathExists, fallbackDirectories) {
|
|
810
|
+
const normalize = windowsPathNormalizer(env);
|
|
811
|
+
const directories = [
|
|
812
|
+
...windowsEnvironmentValues(env, "Path").flatMap(value => value.split(";")),
|
|
813
|
+
...fallbackDirectories,
|
|
814
|
+
].map(normalize);
|
|
815
|
+
const seen = new Set();
|
|
816
|
+
for (const directory of directories) {
|
|
817
|
+
if (!directory || !path.win32.isAbsolute(directory))
|
|
818
|
+
continue;
|
|
819
|
+
const key = directory.toLowerCase();
|
|
820
|
+
if (seen.has(key))
|
|
821
|
+
continue;
|
|
822
|
+
seen.add(key);
|
|
823
|
+
const candidate = path.win32.join(directory, commandName);
|
|
824
|
+
if (pathExists(candidate))
|
|
825
|
+
return candidate;
|
|
826
|
+
}
|
|
827
|
+
return null;
|
|
828
|
+
}
|
|
829
|
+
function resolveWindowsPackageManagerCli(manager, commandPath, execPath, pathExists) {
|
|
830
|
+
const commandDirectory = path.win32.dirname(commandPath);
|
|
831
|
+
const execDirectory = path.win32.dirname(execPath);
|
|
832
|
+
const candidates = manager === "npm"
|
|
833
|
+
? [
|
|
834
|
+
path.win32.join(commandDirectory, "node_modules", "npm", "bin", "npm-cli.js"),
|
|
835
|
+
path.win32.join(execDirectory, "node_modules", "npm", "bin", "npm-cli.js"),
|
|
836
|
+
path.win32.join(execDirectory, "node_modules", "corepack", "dist", "npm.js"),
|
|
837
|
+
]
|
|
838
|
+
: [
|
|
839
|
+
path.win32.join(commandDirectory, "node_modules", "pnpm", "bin", "pnpm.mjs"),
|
|
840
|
+
path.win32.join(execDirectory, "node_modules", "pnpm", "bin", "pnpm.mjs"),
|
|
841
|
+
path.win32.join(execDirectory, "node_modules", "corepack", "dist", "pnpm.js"),
|
|
842
|
+
];
|
|
843
|
+
return candidates.find(pathExists) ?? null;
|
|
844
|
+
}
|
|
845
|
+
function windowsPathNormalizer(env) {
|
|
846
|
+
const environment = new Map(Object.entries(env).map(([key, value]) => [key.toLowerCase(), value ?? ""]));
|
|
847
|
+
return (value) => {
|
|
848
|
+
const trimmed = value.trim();
|
|
849
|
+
const unquoted = trimmed.length >= 2 && trimmed.startsWith('"') && trimmed.endsWith('"')
|
|
850
|
+
? trimmed.slice(1, -1).trim()
|
|
851
|
+
: trimmed;
|
|
852
|
+
return unquoted.replace(/%([^%]+)%/gu, (match, name) => environment.get(name.toLowerCase()) || match);
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
function windowsEnvironmentValues(env, name) {
|
|
856
|
+
const normalized = name.toLowerCase();
|
|
857
|
+
return Object.entries(env)
|
|
858
|
+
.filter(([key, value]) => key.toLowerCase() === normalized && Boolean(value))
|
|
859
|
+
.map(([, value]) => value);
|
|
860
|
+
}
|
|
861
|
+
function windowsEnvironmentValue(env, name) {
|
|
862
|
+
return windowsEnvironmentValues(env, name)[0];
|
|
863
|
+
}
|
|
864
|
+
function joinedWindowsPathLength(entries) {
|
|
865
|
+
return entries.length === 0
|
|
866
|
+
? 0
|
|
867
|
+
: entries.reduce((total, entry) => total + entry.length, entries.length - 1);
|
|
868
|
+
}
|
|
869
|
+
function tryResolveNpmCandidate(candidate) {
|
|
870
|
+
try {
|
|
871
|
+
if (!existsSync(candidate)) {
|
|
872
|
+
return null;
|
|
873
|
+
}
|
|
874
|
+
return realpathSync(candidate);
|
|
875
|
+
}
|
|
876
|
+
catch {
|
|
877
|
+
return null;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
function defaultNpmCliCandidates(execPath, homeDir) {
|
|
881
|
+
const candidates = [];
|
|
882
|
+
const execDir = path.dirname(execPath);
|
|
883
|
+
if (path.isAbsolute(execDir)) {
|
|
884
|
+
candidates.push(path.join(execDir, "npm"), path.join(execDir, "npm-cli.js"), path.join(execDir, "..", "lib", "node_modules", "npm", "bin", "npm-cli.js"));
|
|
885
|
+
}
|
|
886
|
+
candidates.push("/usr/bin/npm", "/usr/local/bin/npm", path.join(homeDir, ".npm-global", "bin", "npm"), path.join(homeDir, ".npm-global", "lib", "node_modules", "npm", "bin", "npm-cli.js"));
|
|
887
|
+
return candidates;
|
|
888
|
+
}
|
|
289
889
|
const defaultCommandExecutor = {
|
|
290
|
-
async run(cwd, command, args) {
|
|
291
|
-
const result = await defaultProcessRunner.run({
|
|
890
|
+
async run(cwd, command, args, options) {
|
|
891
|
+
const result = await defaultProcessRunner.run({
|
|
892
|
+
command,
|
|
893
|
+
cwd,
|
|
894
|
+
args,
|
|
895
|
+
env: options?.env,
|
|
896
|
+
windowsVerbatimArguments: options?.windowsVerbatimArguments,
|
|
897
|
+
});
|
|
292
898
|
if (result.exitCode !== 0) {
|
|
293
|
-
|
|
899
|
+
const output = formatCommandFailureOutput(result.stdout, result.stderr);
|
|
900
|
+
const details = output.length > 0 ? `\n${output}` : " no output was captured";
|
|
901
|
+
throw new Error(`${command} ${args.join(" ")} failed (${result.exitCode}) in ${cwd}:${details}`);
|
|
294
902
|
}
|
|
295
903
|
}
|
|
296
904
|
};
|