@abelxiaoxing/cadence 1.7.0 → 1.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -3
- package/config/.env.example +6 -0
- package/package.json +1 -1
- package/prompts/abel-design.md +6 -0
- package/prompts/abel-diagnose.md +6 -0
- package/prompts/abel-implement.md +6 -0
- package/src/apply-transaction.ts +28 -8
- package/src/build-windows-job.mjs +95 -0
- package/src/candidate-workspace.ts +6 -0
- package/src/change-verification.ts +6 -2
- package/src/durable-contracts.ts +2 -0
- package/src/durable-workflow.ts +8 -2
- package/src/execution-profile.ts +28 -2
- package/src/execution-retention.ts +77 -0
- package/src/index.ts +200 -27
- package/src/isolation-backend.ts +2 -1
- package/src/operator-cli.mjs +652 -97
- package/src/operator-tools.ts +7 -1
- package/src/package-state.ts +11 -0
- package/src/package-verification.ts +49 -17
- package/src/package-workflow.ts +3 -0
- package/src/phase-execution.ts +11 -6
- package/src/remove-path.ts +32 -0
- package/src/verification-capability.ts +1 -1
- package/src/verification-environment-io.ts +123 -14
- package/src/verification-environment.ts +31 -2
- package/src/windows-job-backend.ts +419 -0
- package/src/windows-job.c +149 -0
- package/src/workspace-io-entry.mjs +2 -2
- package/src/workspace-io-worker.mjs +207 -110
package/src/operator-tools.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { resolveOpenSpecInvocation } from "./openspec-cli.ts";
|
|
|
7
7
|
import { resolveStateRoot } from "./state-root.ts";
|
|
8
8
|
import { requestBounds } from "./transport-budget.ts";
|
|
9
9
|
import { validateVerificationAdapterCapability } from "./verification-capability.ts";
|
|
10
|
+
import { probeWindowsJob } from "./windows-job-backend.ts";
|
|
10
11
|
|
|
11
12
|
export interface DoctorCheck {
|
|
12
13
|
name: string;
|
|
@@ -121,6 +122,11 @@ export function inspectConsumer(root: string) {
|
|
|
121
122
|
"execution",
|
|
122
123
|
() => {
|
|
123
124
|
const profile = executionProfile();
|
|
125
|
+
if (profile.mode === "host-trusted") {
|
|
126
|
+
if (!probeWindowsJob(profile.windowsHelperPath ?? ""))
|
|
127
|
+
throw new Error("windows-job-backend-unavailable");
|
|
128
|
+
return "host-trusted: Windows x64 Job probe passed; host files/network accessible";
|
|
129
|
+
}
|
|
124
130
|
if (process.platform !== "linux")
|
|
125
131
|
throw new Error("linux-pid-isolation-required");
|
|
126
132
|
|
|
@@ -139,7 +145,7 @@ export function inspectConsumer(root: string) {
|
|
|
139
145
|
if (result.status !== 0) throw new Error("bubblewrap-probe-failed");
|
|
140
146
|
return `${profile.mode}: Bubblewrap PID namespace probe passed`;
|
|
141
147
|
},
|
|
142
|
-
"On Linux install/enable Bubblewrap; set ABEL_BWRAP_PATH for a custom install. Trusted Linux projects may explicitly set ABEL_EXECUTION_MODE=local-trusted; Windows
|
|
148
|
+
"On Linux install/enable Bubblewrap; set ABEL_BWRAP_PATH for a custom install. Trusted Linux projects may explicitly set ABEL_EXECUTION_MODE=local-trusted; Windows x64 trusted projects may explicitly select ABEL_EXECUTION_MODE=host-trusted and install the matching native Job helper (ABEL_WINDOWS_JOB_HELPER).",
|
|
143
149
|
);
|
|
144
150
|
check(
|
|
145
151
|
"request-budgets",
|
package/src/package-state.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
import path from "node:path";
|
|
16
16
|
import { DatabaseSync } from "node:sqlite";
|
|
17
17
|
import { syncDirectory } from "./directory-sync.ts";
|
|
18
|
+
import { assertExecutionsSettled } from "./execution-retention.ts";
|
|
18
19
|
import { RunStore } from "./run-store.ts";
|
|
19
20
|
import { prepareStateRoot, type ResolvedStateRoot } from "./state-root.ts";
|
|
20
21
|
|
|
@@ -160,6 +161,16 @@ export function acquirePackageState(
|
|
|
160
161
|
): { notice?: PackageStateNotice; close(): void } {
|
|
161
162
|
versionParts(version);
|
|
162
163
|
const state = prepareStateRoot(unresolved);
|
|
164
|
+
try {
|
|
165
|
+
assertExecutionsSettled(state.rootDir);
|
|
166
|
+
} catch (error) {
|
|
167
|
+
if (
|
|
168
|
+
error instanceof Error &&
|
|
169
|
+
error.message === "isolation-termination-unconfirmed"
|
|
170
|
+
)
|
|
171
|
+
fail(error.message);
|
|
172
|
+
throw error;
|
|
173
|
+
}
|
|
163
174
|
for (const suffix of ["-wal", "-shm", "-journal"])
|
|
164
175
|
regular(state.databasePath + suffix);
|
|
165
176
|
const coordinator = path.join(
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
verificationSteps,
|
|
10
10
|
} from "./contracts.ts";
|
|
11
11
|
import { executionProfile } from "./execution-profile.ts";
|
|
12
|
+
import { retainExecution } from "./execution-retention.ts";
|
|
12
13
|
import { canonicalJson } from "./implement-graph.ts";
|
|
13
14
|
import { BubblewrapIsolationBackend } from "./isolation-backend.ts";
|
|
14
15
|
import { observeSafePath } from "./safe-path.ts";
|
|
@@ -19,6 +20,7 @@ import {
|
|
|
19
20
|
} from "./verification-capability.ts";
|
|
20
21
|
import { diagnosticText } from "./verification-diagnostics.ts";
|
|
21
22
|
import { prepareVerificationEnvironment } from "./verification-environment.ts";
|
|
23
|
+
import { WindowsJobBackend } from "./windows-job-backend.ts";
|
|
22
24
|
|
|
23
25
|
function sha256(value: Uint8Array | string): string {
|
|
24
26
|
return createHash("sha256").update(value).digest("hex");
|
|
@@ -311,6 +313,7 @@ function readReport(root: string, relative: string, maximum: number): string {
|
|
|
311
313
|
export async function executePackageVerification(input: {
|
|
312
314
|
root: string;
|
|
313
315
|
dependencyOwner: string;
|
|
316
|
+
executionOwnerRoot?: string;
|
|
314
317
|
verification: StructuredVerificationContract;
|
|
315
318
|
signal: AbortSignal;
|
|
316
319
|
maxReportBytes?: number;
|
|
@@ -376,6 +379,7 @@ export async function executePackageVerification(input: {
|
|
|
376
379
|
"workspace-dependency-missing",
|
|
377
380
|
"dependency-path-unsafe",
|
|
378
381
|
"execution-mode-invalid",
|
|
382
|
+
"windows-job-helper-path-invalid",
|
|
379
383
|
"verification-timeout-invalid",
|
|
380
384
|
"verification-environment-names-invalid",
|
|
381
385
|
"verification-environment-requires-trusted-mode",
|
|
@@ -384,13 +388,20 @@ export async function executePackageVerification(input: {
|
|
|
384
388
|
: "verification-environment-unavailable";
|
|
385
389
|
return unavailable(code, "environment");
|
|
386
390
|
}
|
|
391
|
+
let retained = false;
|
|
387
392
|
try {
|
|
388
393
|
const profile = executionProfile();
|
|
389
|
-
const isolation =
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
+
const isolation =
|
|
395
|
+
profile.mode === "host-trusted"
|
|
396
|
+
? new WindowsJobBackend({
|
|
397
|
+
helperPath: profile.windowsHelperPath ?? "",
|
|
398
|
+
timeoutMs: profile.timeoutMs,
|
|
399
|
+
})
|
|
400
|
+
: new BubblewrapIsolationBackend({
|
|
401
|
+
bwrapPath: profile.bwrapPath,
|
|
402
|
+
timeoutMs: profile.timeoutMs,
|
|
403
|
+
localTrusted: profile.mode === "local-trusted",
|
|
404
|
+
});
|
|
394
405
|
let final: VerificationObservation | undefined;
|
|
395
406
|
let sequence = 0;
|
|
396
407
|
for (const step of verificationSteps(input.verification)) {
|
|
@@ -401,7 +412,7 @@ export async function executePackageVerification(input: {
|
|
|
401
412
|
`${environment.reportRoot}/${relative}`,
|
|
402
413
|
);
|
|
403
414
|
if (!invocation) return unavailable("runner-missing");
|
|
404
|
-
if (profile.mode
|
|
415
|
+
if (profile.mode !== "isolated") {
|
|
405
416
|
const translate = (value: string) =>
|
|
406
417
|
value.startsWith("/workspace/")
|
|
407
418
|
? path.join(input.root, value.slice(11))
|
|
@@ -409,16 +420,37 @@ export async function executePackageVerification(input: {
|
|
|
409
420
|
invocation.executable = translate(invocation.executable);
|
|
410
421
|
invocation.args = invocation.args.map(translate);
|
|
411
422
|
}
|
|
412
|
-
const
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
423
|
+
const lease =
|
|
424
|
+
profile.mode === "host-trusted"
|
|
425
|
+
? retainExecution(
|
|
426
|
+
input.executionOwnerRoot ?? path.dirname(input.root),
|
|
427
|
+
[input.root, environment.privateRoot],
|
|
428
|
+
)
|
|
429
|
+
: undefined;
|
|
430
|
+
let executed: Awaited<ReturnType<typeof isolation.run>>;
|
|
431
|
+
try {
|
|
432
|
+
executed = await isolation.run({
|
|
433
|
+
root: input.root,
|
|
434
|
+
...invocation,
|
|
435
|
+
mounts: profile.mode === "host-trusted" ? [] : environment.mounts,
|
|
436
|
+
environment: environment.environment,
|
|
437
|
+
...(step.kind !== "vitest" && step.classification === "expected-red"
|
|
438
|
+
? { outputWitness: step.expectedFailure }
|
|
439
|
+
: {}),
|
|
440
|
+
signal: input.signal,
|
|
441
|
+
});
|
|
442
|
+
if (
|
|
443
|
+
!executed.ok &&
|
|
444
|
+
executed.code === "isolation-termination-unconfirmed"
|
|
445
|
+
) {
|
|
446
|
+
retained = true;
|
|
447
|
+
lease?.uncertain();
|
|
448
|
+
} else lease?.settled();
|
|
449
|
+
} catch (error) {
|
|
450
|
+
retained = !!lease;
|
|
451
|
+
lease?.uncertain();
|
|
452
|
+
throw error;
|
|
453
|
+
}
|
|
422
454
|
if (!executed.ok) {
|
|
423
455
|
if (executed.state === "cancelled") return { kind: "cancelled" };
|
|
424
456
|
return unavailable(
|
|
@@ -534,7 +566,7 @@ export async function executePackageVerification(input: {
|
|
|
534
566
|
}
|
|
535
567
|
return final ?? unavailable("verification-contract-unsupported");
|
|
536
568
|
} finally {
|
|
537
|
-
await environment.cleanup();
|
|
569
|
+
if (!retained) await environment.cleanup();
|
|
538
570
|
}
|
|
539
571
|
}
|
|
540
572
|
|
package/src/package-workflow.ts
CHANGED
|
@@ -160,6 +160,7 @@ function openPreparedPackageWorkflowService(
|
|
|
160
160
|
const workHardLimit = hostLimit("ABEL_WORK_MAX_UNITS", 512);
|
|
161
161
|
const engine = openDurableWorkflowEngine({
|
|
162
162
|
workHardLimit,
|
|
163
|
+
awaitPostApplySettlement: true,
|
|
163
164
|
verificationPolicy: `report-file-v5:${JSON.stringify(executionProfile())}`,
|
|
164
165
|
verificationEnvironment: coalesceVerificationScans(
|
|
165
166
|
(plan: import("./implement-plan.ts").ImplementPlan, signal) =>
|
|
@@ -210,6 +211,7 @@ function openPreparedPackageWorkflowService(
|
|
|
210
211
|
executionWritePaths: input.executionWritePaths,
|
|
211
212
|
root: input.root,
|
|
212
213
|
dependencyOwner: consumerRoot,
|
|
214
|
+
executionOwnerRoot: stateRoot.rootDir,
|
|
213
215
|
verification: input.verification,
|
|
214
216
|
signal: input.signal,
|
|
215
217
|
}),
|
|
@@ -230,6 +232,7 @@ function openPreparedPackageWorkflowService(
|
|
|
230
232
|
),
|
|
231
233
|
root: input.root,
|
|
232
234
|
dependencyOwner: consumerRoot,
|
|
235
|
+
executionOwnerRoot: stateRoot.rootDir,
|
|
233
236
|
verification,
|
|
234
237
|
signal: input.signal,
|
|
235
238
|
});
|
package/src/phase-execution.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { mkdtempSync
|
|
2
|
+
import { mkdtempSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import {
|
|
5
5
|
inspectCandidateArtifact,
|
|
6
6
|
sealCandidateArtifact,
|
|
7
7
|
} from "./candidate-artifact.ts";
|
|
8
8
|
import { revisionChanges, runGitApply } from "./candidate-workspace.ts";
|
|
9
|
-
|
|
10
9
|
import type { StructuredVerificationContract } from "./contracts.ts";
|
|
11
10
|
import type { PlanTaskDraft } from "./delivery-compiler.ts";
|
|
12
11
|
import type {
|
|
@@ -20,6 +19,8 @@ import type {
|
|
|
20
19
|
DurableWorkflowEngineOptions,
|
|
21
20
|
PhaseResources,
|
|
22
21
|
} from "./durable-contracts.ts";
|
|
22
|
+
import { isExecutionRetained } from "./execution-retention.ts";
|
|
23
|
+
import { removePathSync } from "./remove-path.ts";
|
|
23
24
|
import type {
|
|
24
25
|
BeginCandidateInput,
|
|
25
26
|
TaskLedger,
|
|
@@ -308,7 +309,8 @@ export class PhaseExecution {
|
|
|
308
309
|
policy: this.#options.verificationPolicy ?? "legacy",
|
|
309
310
|
});
|
|
310
311
|
} finally {
|
|
311
|
-
|
|
312
|
+
if (!isExecutionRetained(root))
|
|
313
|
+
removePathSync(root, { recursive: true, force: true });
|
|
312
314
|
}
|
|
313
315
|
}
|
|
314
316
|
return undefined;
|
|
@@ -980,7 +982,8 @@ export class PhaseExecution {
|
|
|
980
982
|
},
|
|
981
983
|
};
|
|
982
984
|
} finally {
|
|
983
|
-
|
|
985
|
+
if (!isExecutionRetained(proposalRoot))
|
|
986
|
+
removePathSync(proposalRoot, { recursive: true, force: true });
|
|
984
987
|
}
|
|
985
988
|
}
|
|
986
989
|
|
|
@@ -1165,7 +1168,8 @@ export class PhaseExecution {
|
|
|
1165
1168
|
signal: request.signal,
|
|
1166
1169
|
});
|
|
1167
1170
|
} finally {
|
|
1168
|
-
|
|
1171
|
+
if (!isExecutionRetained(root))
|
|
1172
|
+
removePathSync(root, { recursive: true, force: true });
|
|
1169
1173
|
}
|
|
1170
1174
|
};
|
|
1171
1175
|
emitWorkflowActivity(request.onActivity, { state: "verifying" });
|
|
@@ -2248,7 +2252,8 @@ export class PhaseExecution {
|
|
|
2248
2252
|
baselineRevisionId: resources.baselineRevisionId,
|
|
2249
2253
|
});
|
|
2250
2254
|
} finally {
|
|
2251
|
-
|
|
2255
|
+
if (!isExecutionRetained(proposalRoot))
|
|
2256
|
+
removePathSync(proposalRoot, { recursive: true, force: true });
|
|
2252
2257
|
}
|
|
2253
2258
|
}
|
|
2254
2259
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { lstatSync, readdirSync, rmdirSync, rmSync, unlinkSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
/** Remove owned paths without following directory links. Node 24.13's Windows
|
|
5
|
+
* rmSync uses narrow std::filesystem paths and can silently miss Unicode names.
|
|
6
|
+
* The libuv-backed unlink/rmdir APIs preserve the original UTF-16 path. */
|
|
7
|
+
export function removePathSync(
|
|
8
|
+
target: string,
|
|
9
|
+
options: { recursive?: boolean; force?: boolean } = {},
|
|
10
|
+
): void {
|
|
11
|
+
if (process.platform !== "win32") {
|
|
12
|
+
rmSync(target, options);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
let stat: import("node:fs").Stats;
|
|
16
|
+
try {
|
|
17
|
+
stat = lstatSync(target);
|
|
18
|
+
} catch (error) {
|
|
19
|
+
if (options.force && (error as NodeJS.ErrnoException).code === "ENOENT")
|
|
20
|
+
return;
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
if (stat.isDirectory() && !stat.isSymbolicLink()) {
|
|
24
|
+
if (!options.recursive)
|
|
25
|
+
throw Object.assign(new Error("Path is a directory"), {
|
|
26
|
+
code: "ERR_FS_EISDIR",
|
|
27
|
+
});
|
|
28
|
+
for (const name of readdirSync(target))
|
|
29
|
+
removePathSync(path.join(target, name), options);
|
|
30
|
+
rmdirSync(target);
|
|
31
|
+
} else unlinkSync(target);
|
|
32
|
+
}
|
|
@@ -838,7 +838,7 @@ function validateAtomicCapability(
|
|
|
838
838
|
continue;
|
|
839
839
|
if (
|
|
840
840
|
lstatSync(path.join(root, relative)).size > 64 * 1024 ||
|
|
841
|
-
(executionProfile().mode
|
|
841
|
+
(executionProfile().mode === "isolated" &&
|
|
842
842
|
unsupportedExecutionConfiguration(
|
|
843
843
|
readFileSync(path.join(root, relative), "utf8"),
|
|
844
844
|
))
|
|
@@ -4,14 +4,15 @@ import {
|
|
|
4
4
|
lstatSync,
|
|
5
5
|
mkdirSync,
|
|
6
6
|
readdirSync,
|
|
7
|
+
readlinkSync,
|
|
7
8
|
realpathSync,
|
|
8
|
-
rmSync,
|
|
9
9
|
symlinkSync,
|
|
10
10
|
writeFileSync,
|
|
11
11
|
} from "node:fs";
|
|
12
12
|
import path from "node:path";
|
|
13
13
|
import type { ExecutionProfile } from "./execution-profile.ts";
|
|
14
14
|
import type { IsolationMount } from "./isolation-backend.ts";
|
|
15
|
+
import { removePathSync } from "./remove-path.ts";
|
|
15
16
|
import type { VerificationRunnerBinding } from "./verification-capability.ts";
|
|
16
17
|
|
|
17
18
|
const CACHES = [".vite", ".vite-temp"];
|
|
@@ -34,9 +35,11 @@ export function prepareVerificationEnvironmentIo(
|
|
|
34
35
|
checkCancelled: () => void,
|
|
35
36
|
privateRoot: string,
|
|
36
37
|
) {
|
|
37
|
-
const cleanup = () =>
|
|
38
|
+
const cleanup = () =>
|
|
39
|
+
removePathSync(privateRoot, { recursive: true, force: true });
|
|
38
40
|
try {
|
|
39
|
-
const local = profile.mode
|
|
41
|
+
const local = profile.mode !== "isolated";
|
|
42
|
+
const native = profile.mode === "host-trusted";
|
|
40
43
|
if (local && realpathSync(root) === realpathSync(dependencyOwner))
|
|
41
44
|
throw new Error("trusted-candidate-required");
|
|
42
45
|
const mounts: IsolationMount[] = [
|
|
@@ -51,11 +54,19 @@ export function prepareVerificationEnvironmentIo(
|
|
|
51
54
|
]) {
|
|
52
55
|
mkdirSync(path.join(privateRoot, directory), { mode: 0o700 });
|
|
53
56
|
}
|
|
57
|
+
if (native) {
|
|
58
|
+
mkdirSync(path.join(privateRoot, "home/AppData/Roaming"), {
|
|
59
|
+
recursive: true,
|
|
60
|
+
});
|
|
61
|
+
mkdirSync(path.join(privateRoot, "home/AppData/Local"), {
|
|
62
|
+
recursive: true,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
54
65
|
writeFileSync(path.join(privateRoot, "home/user.npmrc"), "");
|
|
55
66
|
writeFileSync(path.join(privateRoot, "home/global.npmrc"), "");
|
|
56
67
|
const dependencies = path.join(dependencyOwner, "node_modules");
|
|
57
68
|
const stat = lstatSync(dependencies, { throwIfNoEntry: false });
|
|
58
|
-
if (stat) {
|
|
69
|
+
if (stat && !native) {
|
|
59
70
|
const target = path.join(root, "node_modules");
|
|
60
71
|
const targetStat = lstatSync(target, { throwIfNoEntry: false });
|
|
61
72
|
if (
|
|
@@ -133,7 +144,7 @@ export function prepareVerificationEnvironmentIo(
|
|
|
133
144
|
});
|
|
134
145
|
}
|
|
135
146
|
}
|
|
136
|
-
if (local && stat) {
|
|
147
|
+
if (local && stat && !native) {
|
|
137
148
|
// Private copies keep package caches/writes away from consumer dependencies.
|
|
138
149
|
const copyEntry = (name: string) => {
|
|
139
150
|
checkCancelled();
|
|
@@ -177,8 +188,77 @@ export function prepareVerificationEnvironmentIo(
|
|
|
177
188
|
copyEntry(name);
|
|
178
189
|
}
|
|
179
190
|
|
|
191
|
+
if (native && stat) {
|
|
192
|
+
const target = path.join(root, "node_modules");
|
|
193
|
+
const targetStat = lstatSync(target, { throwIfNoEntry: false });
|
|
194
|
+
if (
|
|
195
|
+
!stat.isDirectory() ||
|
|
196
|
+
stat.isSymbolicLink() ||
|
|
197
|
+
(targetStat &&
|
|
198
|
+
(!targetStat.isDirectory() || targetStat.isSymbolicLink()))
|
|
199
|
+
)
|
|
200
|
+
throw new Error("dependency-path-unsafe");
|
|
201
|
+
const owner = realpathSync(dependencyOwner);
|
|
202
|
+
let count = 0;
|
|
203
|
+
const copy = (source: string, destination: string) => {
|
|
204
|
+
checkCancelled();
|
|
205
|
+
if (++count > 500_000) throw new Error("dependency-view-too-large");
|
|
206
|
+
const entry = lstatSync(source);
|
|
207
|
+
if (entry.isSymbolicLink()) {
|
|
208
|
+
const resolved = realpathSync(source);
|
|
209
|
+
if (!within(owner, resolved))
|
|
210
|
+
throw new Error("dependency-path-unsafe");
|
|
211
|
+
const candidate = path.join(root, path.relative(owner, resolved));
|
|
212
|
+
if (
|
|
213
|
+
!within(realpathSync(dependencies), resolved) &&
|
|
214
|
+
!lstatSync(candidate, { throwIfNoEntry: false })
|
|
215
|
+
)
|
|
216
|
+
throw new Error("workspace-dependency-missing");
|
|
217
|
+
const linked = lstatSync(resolved);
|
|
218
|
+
if (linked.isDirectory()) {
|
|
219
|
+
// Junctions do not require Windows Developer Mode/admin privileges.
|
|
220
|
+
symlinkSync(
|
|
221
|
+
candidate,
|
|
222
|
+
destination,
|
|
223
|
+
process.platform === "win32" ? "junction" : "dir",
|
|
224
|
+
);
|
|
225
|
+
} else if (linked.isFile()) {
|
|
226
|
+
const file = within(realpathSync(dependencies), resolved)
|
|
227
|
+
? resolved
|
|
228
|
+
: candidate;
|
|
229
|
+
cpSync(file, destination, { mode: constants.COPYFILE_FICLONE });
|
|
230
|
+
} else throw new Error("dependency-path-unsafe");
|
|
231
|
+
} else if (entry.isDirectory()) {
|
|
232
|
+
mkdirSync(destination, { recursive: true });
|
|
233
|
+
for (const name of readdirSync(source)) {
|
|
234
|
+
if (source === dependencies && CACHES.includes(name)) {
|
|
235
|
+
mkdirSync(path.join(destination, name));
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
copy(path.join(source, name), path.join(destination, name));
|
|
239
|
+
}
|
|
240
|
+
} else if (entry.isFile())
|
|
241
|
+
cpSync(source, destination, { mode: constants.COPYFILE_FICLONE });
|
|
242
|
+
else throw new Error("dependency-path-unsafe");
|
|
243
|
+
};
|
|
244
|
+
removeEntry(target, checkCancelled);
|
|
245
|
+
copy(dependencies, target);
|
|
246
|
+
}
|
|
180
247
|
const sources = new Map<string, string>();
|
|
181
248
|
const bindings = runnerBindings.map((binding) => {
|
|
249
|
+
if (native) {
|
|
250
|
+
const translate = (value: string) =>
|
|
251
|
+
path.isAbsolute(value) && within(dependencyOwner, value)
|
|
252
|
+
? path.join(root, path.relative(dependencyOwner, value))
|
|
253
|
+
: value;
|
|
254
|
+
return {
|
|
255
|
+
...binding,
|
|
256
|
+
executablePath: translate(binding.executablePath),
|
|
257
|
+
...(binding.fixedArgs
|
|
258
|
+
? { fixedArgs: binding.fixedArgs.map(translate) }
|
|
259
|
+
: {}),
|
|
260
|
+
};
|
|
261
|
+
}
|
|
182
262
|
if (local || !binding.mountSource) return { ...binding };
|
|
183
263
|
const source = path.resolve(binding.mountSource);
|
|
184
264
|
const stat = lstatSync(source);
|
|
@@ -200,7 +280,15 @@ export function prepareVerificationEnvironmentIo(
|
|
|
200
280
|
path.posix.join(target, ...path.relative(source, file).split(path.sep));
|
|
201
281
|
return {
|
|
202
282
|
...binding,
|
|
203
|
-
|
|
283
|
+
// Preserve an installation's relative launcher link (e.g. bin/npm).
|
|
284
|
+
// Exposing lib/node_modules/npm/bin on PATH selects npm's Windows
|
|
285
|
+
// installer shell shim during a nested `npm run` on Linux.
|
|
286
|
+
executablePath: translate(
|
|
287
|
+
lstatSync(binding.executablePath).isSymbolicLink() &&
|
|
288
|
+
!path.isAbsolute(readlinkSync(binding.executablePath))
|
|
289
|
+
? binding.executablePath
|
|
290
|
+
: executable,
|
|
291
|
+
),
|
|
204
292
|
...(binding.fixedArgs
|
|
205
293
|
? {
|
|
206
294
|
fixedArgs: binding.fixedArgs.map((arg) =>
|
|
@@ -215,7 +303,7 @@ export function prepareVerificationEnvironmentIo(
|
|
|
215
303
|
checkCancelled();
|
|
216
304
|
return {
|
|
217
305
|
privateRoot,
|
|
218
|
-
mounts,
|
|
306
|
+
mounts: native ? [] : mounts,
|
|
219
307
|
bindings,
|
|
220
308
|
reportRoot: local ? privateRoot : "/cadence",
|
|
221
309
|
environment: {
|
|
@@ -228,6 +316,24 @@ export function prepareVerificationEnvironmentIo(
|
|
|
228
316
|
),
|
|
229
317
|
)
|
|
230
318
|
: {}),
|
|
319
|
+
...(native
|
|
320
|
+
? Object.fromEntries(
|
|
321
|
+
Object.entries(environment).filter(([key]) =>
|
|
322
|
+
["SYSTEMROOT", "WINDIR", "COMSPEC", "PATHEXT"].includes(
|
|
323
|
+
key.toUpperCase(),
|
|
324
|
+
),
|
|
325
|
+
),
|
|
326
|
+
)
|
|
327
|
+
: {}),
|
|
328
|
+
...(native
|
|
329
|
+
? {
|
|
330
|
+
USERPROFILE: path.join(privateRoot, "home"),
|
|
331
|
+
APPDATA: path.join(privateRoot, "home", "AppData", "Roaming"),
|
|
332
|
+
LOCALAPPDATA: path.join(privateRoot, "home", "AppData", "Local"),
|
|
333
|
+
TEMP: path.join(privateRoot, "tmp"),
|
|
334
|
+
TMP: path.join(privateRoot, "tmp"),
|
|
335
|
+
}
|
|
336
|
+
: {}),
|
|
231
337
|
CI: "1",
|
|
232
338
|
HOME: local ? path.join(privateRoot, "home") : "/cadence/home",
|
|
233
339
|
XDG_CACHE_HOME: local
|
|
@@ -249,13 +355,13 @@ export function prepareVerificationEnvironmentIo(
|
|
|
249
355
|
[
|
|
250
356
|
...bindings.filter((binding) => binding.mountSource),
|
|
251
357
|
...bindings.filter((binding) => !binding.mountSource),
|
|
252
|
-
].map((binding) =>
|
|
358
|
+
].map((binding) =>
|
|
359
|
+
(local ? path : path.posix).dirname(binding.executablePath),
|
|
360
|
+
),
|
|
253
361
|
),
|
|
254
362
|
...(local ? (environment.PATH ?? "").split(path.delimiter) : []),
|
|
255
|
-
"/usr/local/bin",
|
|
256
|
-
|
|
257
|
-
"/bin",
|
|
258
|
-
].join(":"),
|
|
363
|
+
...(native ? [] : ["/usr/local/bin", "/usr/bin", "/bin"]),
|
|
364
|
+
].join(local ? path.delimiter : ":"),
|
|
259
365
|
},
|
|
260
366
|
};
|
|
261
367
|
} catch (error) {
|
|
@@ -273,8 +379,11 @@ function removeEntry(file: string, checkCancelled: () => void): void {
|
|
|
273
379
|
checkCancelled();
|
|
274
380
|
const stat = lstatSync(file, { throwIfNoEntry: false });
|
|
275
381
|
if (!stat) return;
|
|
276
|
-
if (stat.isDirectory())
|
|
382
|
+
if (stat.isDirectory() && !stat.isSymbolicLink())
|
|
277
383
|
for (const name of readdirSync(file))
|
|
278
384
|
removeEntry(path.join(file, name), checkCancelled);
|
|
279
|
-
|
|
385
|
+
removePathSync(file, {
|
|
386
|
+
recursive: stat.isDirectory() && !stat.isSymbolicLink(),
|
|
387
|
+
force: true,
|
|
388
|
+
});
|
|
280
389
|
}
|
|
@@ -25,7 +25,18 @@ export async function captureVerificationEnvironmentIdentity(
|
|
|
25
25
|
const roots = [path.join(dependencyOwner, "node_modules")];
|
|
26
26
|
const unavailable: string[] = [];
|
|
27
27
|
const profile = executionProfile();
|
|
28
|
-
|
|
28
|
+
if (profile.mode === "host-trusted")
|
|
29
|
+
roots.push(
|
|
30
|
+
path.resolve(profile.windowsHelperPath ?? ""),
|
|
31
|
+
path.resolve(`${profile.windowsHelperPath}.json`),
|
|
32
|
+
);
|
|
33
|
+
else roots.push(path.resolve(profile.bwrapPath));
|
|
34
|
+
if (
|
|
35
|
+
profile.mode === "host-trusted" &&
|
|
36
|
+
process.env.ComSpec &&
|
|
37
|
+
path.isAbsolute(process.env.ComSpec)
|
|
38
|
+
)
|
|
39
|
+
roots.push(process.env.ComSpec);
|
|
29
40
|
const node = resolveVerificationRunner("node");
|
|
30
41
|
if (node) roots.push(realpathSync(node.executablePath));
|
|
31
42
|
for (const verification of verifications) {
|
|
@@ -72,6 +83,18 @@ export async function captureVerificationEnvironmentIdentity(
|
|
|
72
83
|
.update(installed)
|
|
73
84
|
.update(JSON.stringify(unavailable.sort()))
|
|
74
85
|
.update(JSON.stringify(profile))
|
|
86
|
+
.update(
|
|
87
|
+
profile.mode === "host-trusted"
|
|
88
|
+
? JSON.stringify([
|
|
89
|
+
process.platform,
|
|
90
|
+
process.arch,
|
|
91
|
+
process.versions.node,
|
|
92
|
+
["PATH", "SystemRoot", "WINDIR", "ComSpec", "PATHEXT"].map(
|
|
93
|
+
(name) => [name, process.env[name] ?? null],
|
|
94
|
+
),
|
|
95
|
+
])
|
|
96
|
+
: "",
|
|
97
|
+
)
|
|
75
98
|
.update(
|
|
76
99
|
JSON.stringify(
|
|
77
100
|
profile.inheritEnvironment.map((name) => [
|
|
@@ -116,7 +139,13 @@ export async function prepareVerificationEnvironment(
|
|
|
116
139
|
runnerBindings,
|
|
117
140
|
profile,
|
|
118
141
|
Object.fromEntries(
|
|
119
|
-
[
|
|
142
|
+
[
|
|
143
|
+
"PATH",
|
|
144
|
+
...(profile.mode === "host-trusted"
|
|
145
|
+
? ["SystemRoot", "WINDIR", "ComSpec", "PATHEXT"]
|
|
146
|
+
: []),
|
|
147
|
+
...profile.inheritEnvironment,
|
|
148
|
+
].flatMap((name) =>
|
|
120
149
|
process.env[name] === undefined ? [] : [[name, process.env[name]]],
|
|
121
150
|
),
|
|
122
151
|
),
|