@loucompanion/forge-bridge 0.1.3-dev.f66395c3ac21 → 0.1.4-dev.1ff37038366e

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.
Files changed (221) hide show
  1. package/README.md +165 -1
  2. package/dist/bridge/bridge-chat.types.d.ts +111 -0
  3. package/dist/bridge/bridge-chat.types.js +401 -0
  4. package/dist/bridge/bridge.base.d.ts +9 -1
  5. package/dist/bridge/bridge.service.d.ts +129 -3
  6. package/dist/bridge/bridge.service.js +2427 -70
  7. package/dist/bridge/bridge.types.d.ts +218 -2
  8. package/dist/bridge/dispatch-result-outbox.d.ts +25 -0
  9. package/dist/bridge/dispatch-result-outbox.js +62 -0
  10. package/dist/bridge/dispatch-result-worker.d.ts +2 -0
  11. package/dist/bridge/dispatch-result-worker.js +122 -0
  12. package/dist/bridge/internals.d.ts +2 -0
  13. package/dist/bridge/internals.js +1 -0
  14. package/dist/bridge/readiness-result-outbox.d.ts +21 -0
  15. package/dist/bridge/readiness-result-outbox.js +54 -0
  16. package/dist/bridge/readiness-result-worker.d.ts +2 -0
  17. package/dist/bridge/readiness-result-worker.js +121 -0
  18. package/dist/bridge/runner-host-capabilities.d.ts +15 -0
  19. package/dist/bridge/runner-host-capabilities.js +93 -0
  20. package/dist/cleanup/cleanup.service.d.ts +14 -2
  21. package/dist/cleanup/cleanup.service.js +496 -3
  22. package/dist/cleanup/cleanup.types.d.ts +10 -0
  23. package/dist/cleanup/index.d.ts +1 -1
  24. package/dist/cleanup/index.js +1 -1
  25. package/dist/cleanup/internals.d.ts +2 -2
  26. package/dist/cleanup/internals.js +1 -1
  27. package/dist/cli/cli.help.d.ts +2 -0
  28. package/dist/cli/cli.help.js +217 -0
  29. package/dist/cli/cli.service.d.ts +55 -7
  30. package/dist/cli/cli.service.js +429 -219
  31. package/dist/cli/local-config.d.ts +7 -0
  32. package/dist/cli/local-config.js +51 -3
  33. package/dist/runtime/aspnet-runtime-prebuild.d.ts +36 -0
  34. package/dist/runtime/aspnet-runtime-prebuild.js +341 -0
  35. package/dist/runtime/attempt-database-broker.d.ts +89 -0
  36. package/dist/runtime/attempt-database-broker.js +390 -0
  37. package/dist/runtime/attempt-node-runtime-launcher.d.ts +18 -0
  38. package/dist/runtime/attempt-node-runtime-launcher.js +140 -0
  39. package/dist/runtime/attempt-path-operation.d.ts +21 -0
  40. package/dist/runtime/attempt-path-operation.js +94 -0
  41. package/dist/runtime/attempt-private-directory.d.ts +6 -0
  42. package/dist/runtime/attempt-private-directory.js +54 -0
  43. package/dist/runtime/attempt-process-supervisor-registry.d.ts +20 -0
  44. package/dist/runtime/attempt-process-supervisor-registry.js +64 -0
  45. package/dist/runtime/attempt-process-supervisor.d.ts +97 -0
  46. package/dist/runtime/attempt-process-supervisor.js +397 -0
  47. package/dist/runtime/canonical-source-file.d.ts +3 -0
  48. package/dist/runtime/canonical-source-file.js +54 -0
  49. package/dist/runtime/docker-engine-probe.d.ts +90 -0
  50. package/dist/runtime/docker-engine-probe.js +333 -0
  51. package/dist/runtime/generated-output-reset.d.ts +20 -0
  52. package/dist/runtime/generated-output-reset.js +157 -0
  53. package/dist/runtime/host-database-capability-boundary.d.ts +16 -0
  54. package/dist/runtime/host-database-capability-boundary.js +62 -0
  55. package/dist/runtime/host-diagnostics.d.ts +11 -0
  56. package/dist/runtime/host-diagnostics.js +191 -0
  57. package/dist/runtime/host-execution-root.d.ts +4 -0
  58. package/dist/runtime/host-execution-root.js +39 -0
  59. package/dist/runtime/host-listener-inspector.d.ts +51 -0
  60. package/dist/runtime/host-listener-inspector.js +385 -0
  61. package/dist/runtime/host-port-broker.d.ts +76 -0
  62. package/dist/runtime/host-port-broker.js +333 -0
  63. package/dist/runtime/host-runtime-contract.d.ts +58 -0
  64. package/dist/runtime/host-runtime-contract.js +813 -0
  65. package/dist/runtime/host-runtime-lease.d.ts +217 -0
  66. package/dist/runtime/host-runtime-lease.js +2487 -0
  67. package/dist/runtime/host-runtime-promotion.d.ts +32 -0
  68. package/dist/runtime/host-runtime-promotion.js +206 -0
  69. package/dist/runtime/memory-capacity.d.ts +70 -0
  70. package/dist/runtime/memory-capacity.js +123 -0
  71. package/dist/runtime/native-test-postgres-fallback.d.ts +25 -0
  72. package/dist/runtime/native-test-postgres-fallback.js +91 -0
  73. package/dist/runtime/node-host-runtime-adapter.d.ts +54 -0
  74. package/dist/runtime/node-host-runtime-adapter.js +285 -0
  75. package/dist/runtime/pinned-directory-process.d.ts +9 -0
  76. package/dist/runtime/pinned-directory-process.js +61 -0
  77. package/dist/runtime/pinned-source-directory.d.ts +17 -0
  78. package/dist/runtime/pinned-source-directory.js +94 -0
  79. package/dist/runtime/playwright-host-runtime-adapter.d.ts +21 -0
  80. package/dist/runtime/playwright-host-runtime-adapter.js +268 -0
  81. package/dist/runtime/prepared-runtime-identity.d.ts +6 -0
  82. package/dist/runtime/prepared-runtime-identity.js +218 -0
  83. package/dist/runtime/prepared-runtime-readiness.d.ts +30 -0
  84. package/dist/runtime/prepared-runtime-readiness.js +285 -0
  85. package/dist/runtime/runtime-isolation-lease.d.ts +67 -0
  86. package/dist/runtime/runtime-isolation-lease.js +161 -0
  87. package/dist/runtime/workspace-resource-estimator.d.ts +17 -0
  88. package/dist/runtime/workspace-resource-estimator.js +174 -0
  89. package/dist/sandbox/command-runner.d.ts +4 -0
  90. package/dist/sandbox/command-runner.js +47 -0
  91. package/dist/sandbox/contract.d.ts +5 -0
  92. package/dist/sandbox/contract.js +276 -0
  93. package/dist/sandbox/event-budget.d.ts +11 -0
  94. package/dist/sandbox/event-budget.js +27 -0
  95. package/dist/sandbox/image-policy.d.ts +35 -0
  96. package/dist/sandbox/image-policy.js +247 -0
  97. package/dist/sandbox/index.d.ts +5 -0
  98. package/dist/sandbox/index.js +5 -0
  99. package/dist/sandbox/lease-watchdog.d.ts +18 -0
  100. package/dist/sandbox/lease-watchdog.js +67 -0
  101. package/dist/sandbox/preflight.d.ts +84 -0
  102. package/dist/sandbox/preflight.js +521 -0
  103. package/dist/sandbox/process.d.ts +20 -0
  104. package/dist/sandbox/process.js +209 -0
  105. package/dist/sandbox/provider-auth/codex.d.ts +26 -0
  106. package/dist/sandbox/provider-auth/codex.js +212 -0
  107. package/dist/sandbox/repository-lock.d.ts +6 -0
  108. package/dist/sandbox/repository-lock.js +128 -0
  109. package/dist/sandbox/runtime.d.ts +302 -0
  110. package/dist/sandbox/runtime.js +2367 -0
  111. package/dist/sandbox/sandbox.types.d.ts +93 -0
  112. package/dist/sandbox/sandbox.types.js +1 -0
  113. package/dist/sandbox/smoke.d.ts +76 -0
  114. package/dist/sandbox/smoke.js +380 -0
  115. package/dist/sandbox/worker-supervisor.d.ts +13 -0
  116. package/dist/sandbox/worker-supervisor.js +36 -0
  117. package/dist/sandbox/worker.d.ts +1 -0
  118. package/dist/sandbox/worker.js +225 -0
  119. package/dist/sandbox/workspace.service.d.ts +13 -0
  120. package/dist/sandbox/workspace.service.js +74 -0
  121. package/dist/session/managed-attempt-tools.d.ts +28 -0
  122. package/dist/session/managed-attempt-tools.js +1015 -0
  123. package/dist/session/managed-build-coordinator.d.ts +5 -0
  124. package/dist/session/managed-build-coordinator.js +105 -0
  125. package/dist/session/managed-build-receipt.d.ts +21 -0
  126. package/dist/session/managed-build-receipt.js +337 -0
  127. package/dist/session/managed-verification-timeouts.d.ts +8 -0
  128. package/dist/session/managed-verification-timeouts.js +17 -0
  129. package/dist/session/phase-completion.artifacts.d.ts +27 -0
  130. package/dist/session/phase-completion.artifacts.js +829 -0
  131. package/dist/session/phase-completion.d.ts +53 -42
  132. package/dist/session/phase-completion.helpers.d.ts +117 -0
  133. package/dist/session/phase-completion.helpers.js +707 -0
  134. package/dist/session/phase-completion.js +125 -3007
  135. package/dist/session/phase-completion.prompts.d.ts +14 -0
  136. package/dist/session/phase-completion.prompts.js +233 -0
  137. package/dist/session/phase-completion.runtime.d.ts +49 -0
  138. package/dist/session/phase-completion.runtime.js +840 -0
  139. package/dist/session/phase-completion.schemas.d.ts +3 -0
  140. package/dist/session/phase-completion.schemas.js +859 -0
  141. package/dist/session/phase-completion.validation.d.ts +38 -0
  142. package/dist/session/phase-completion.validation.js +918 -0
  143. package/dist/session/pre-publication-validation.d.ts +75 -0
  144. package/dist/session/pre-publication-validation.js +661 -0
  145. package/dist/session/provider-cli/bridge-cli.conformance-fixtures.d.ts +72 -0
  146. package/dist/session/provider-cli/bridge-cli.conformance-fixtures.js +462 -0
  147. package/dist/session/provider-cli/ci-repair-readiness.d.ts +16 -0
  148. package/dist/session/provider-cli/ci-repair-readiness.js +110 -0
  149. package/dist/session/provider-cli/provider-cli.artifacts.d.ts +95 -0
  150. package/dist/session/provider-cli/provider-cli.artifacts.js +812 -0
  151. package/dist/session/provider-cli/provider-cli.base.d.ts +1 -0
  152. package/dist/session/provider-cli/provider-cli.deterministic-payloads.d.ts +12 -0
  153. package/dist/session/provider-cli/provider-cli.deterministic-payloads.js +279 -0
  154. package/dist/session/provider-cli/provider-cli.deterministic.d.ts +14 -0
  155. package/dist/session/provider-cli/provider-cli.deterministic.js +407 -0
  156. package/dist/session/provider-cli/provider-cli.process.d.ts +99 -0
  157. package/dist/session/provider-cli/provider-cli.process.js +874 -0
  158. package/dist/session/provider-cli/provider-cli.prompts.d.ts +52 -0
  159. package/dist/session/provider-cli/provider-cli.prompts.js +715 -0
  160. package/dist/session/provider-cli/provider-cli.qa-sidecars.d.ts +11 -0
  161. package/dist/session/provider-cli/provider-cli.qa-sidecars.js +66 -0
  162. package/dist/session/provider-cli/provider-cli.repair-epoch.d.ts +6 -0
  163. package/dist/session/provider-cli/provider-cli.repair-epoch.js +11 -0
  164. package/dist/session/provider-cli/provider-cli.runtime.d.ts +16 -0
  165. package/dist/session/provider-cli/provider-cli.runtime.js +81 -0
  166. package/dist/session/provider-cli/provider-cli.service.d.ts +47 -53
  167. package/dist/session/provider-cli/provider-cli.service.js +1257 -2497
  168. package/dist/session/provider-cli/provider-cli.types.d.ts +8 -0
  169. package/dist/session/provider-cli/provider-output-state.d.ts +32 -0
  170. package/dist/session/provider-cli/provider-output-state.js +817 -0
  171. package/dist/session/provider-cli/provider-usage.d.ts +7 -0
  172. package/dist/session/provider-cli/provider-usage.js +28 -5
  173. package/dist/session/session-redaction.d.ts +3 -0
  174. package/dist/session/session-redaction.js +448 -0
  175. package/dist/session/session.base.d.ts +6 -1
  176. package/dist/session/session.service.d.ts +8 -2
  177. package/dist/session/session.service.js +586 -59
  178. package/dist/session/session.types.d.ts +324 -3
  179. package/dist/session/session.types.js +939 -10
  180. package/dist/session/unreferenced-exports.d.ts +12 -0
  181. package/dist/session/unreferenced-exports.js +59 -0
  182. package/dist/shared/format/format-bytes.d.ts +1 -0
  183. package/dist/shared/format/format-bytes.js +9 -0
  184. package/dist/shared/git/git.js +2 -1
  185. package/dist/shared/path/path.d.ts +1 -0
  186. package/dist/shared/path/path.js +21 -1
  187. package/dist/shared/process/dotnet-toolchain.d.ts +17 -0
  188. package/dist/shared/process/dotnet-toolchain.js +307 -0
  189. package/dist/shared/process/host-process-recovery.d.ts +12 -0
  190. package/dist/shared/process/host-process-recovery.js +78 -0
  191. package/dist/shared/process/host-process-supervisor.d.ts +59 -0
  192. package/dist/shared/process/host-process-supervisor.js +330 -0
  193. package/dist/shared/process/process.d.ts +3 -0
  194. package/dist/shared/process/process.js +13 -7
  195. package/dist/shared/process/windows-launch.d.ts +8 -0
  196. package/dist/shared/process/windows-launch.js +41 -0
  197. package/dist/shared/provider-capabilities.d.ts +4 -0
  198. package/dist/shared/provider-capabilities.js +88 -0
  199. package/dist/shared/redaction/redaction.d.ts +1 -0
  200. package/dist/shared/redaction/redaction.js +47 -4
  201. package/dist/version.d.ts +1 -1
  202. package/dist/version.js +1 -1
  203. package/dist/worktree/artifact-injector.d.ts +3 -2
  204. package/dist/worktree/artifact-injector.js +82 -10
  205. package/dist/worktree/dependency-warmer.d.ts +53 -1
  206. package/dist/worktree/dependency-warmer.js +619 -11
  207. package/dist/worktree/index.d.ts +2 -1
  208. package/dist/worktree/index.js +1 -0
  209. package/dist/worktree/prior-phase-artifact-hydrator.d.ts +2 -0
  210. package/dist/worktree/prior-phase-artifact-hydrator.js +136 -0
  211. package/dist/worktree/story-qa-reference-hydrator.d.ts +4 -0
  212. package/dist/worktree/story-qa-reference-hydrator.js +253 -0
  213. package/dist/worktree/worktree-paths.d.ts +4 -0
  214. package/dist/worktree/worktree-paths.js +30 -0
  215. package/dist/worktree/worktree.base.d.ts +3 -0
  216. package/dist/worktree/worktree.service.d.ts +16 -3
  217. package/dist/worktree/worktree.service.js +334 -54
  218. package/dist/worktree/worktree.types.d.ts +9 -1
  219. package/package.json +12 -7
  220. package/runtime/windows-job-supervisor.ps1 +722 -0
  221. 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 install = await runInstallFallback(entry, worktreePath, fallbackReason, executor);
58
- fallbacks.push(install);
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.rm(resolvedTarget, { recursive: true, force: true });
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
- await executor.run(cwd, "npm", args);
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
- await executor.run(cwd, "python", ["-m", "venv", ".venv"]);
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({ command, cwd, args });
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
- throw new Error(`${command} ${args.join(" ")} failed (${result.exitCode}): ${result.stderr.trim()}`);
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
  };