@abelxiaoxing/cadence 1.7.0 → 1.8.0
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 +7 -3
- package/config/.env.example +6 -0
- package/package.json +1 -1
- 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/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-cli.mjs
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
// Generated by scripts/build-workspace-io.mjs; edit operator-cli-entry.mjs and its source modules.
|
|
2
2
|
// src/operator-cli-entry.mjs
|
|
3
|
-
import
|
|
3
|
+
import path9 from "node:path";
|
|
4
4
|
|
|
5
5
|
// src/operator-tools.ts
|
|
6
|
-
import { spawnSync } from "node:child_process";
|
|
7
|
-
import { lstatSync as
|
|
8
|
-
import
|
|
6
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
7
|
+
import { lstatSync as lstatSync8, readdirSync as readdirSync4, readFileSync as readFileSync5, realpathSync as realpathSync4 } from "node:fs";
|
|
8
|
+
import path8 from "node:path";
|
|
9
9
|
import { DatabaseSync } from "node:sqlite";
|
|
10
10
|
|
|
11
11
|
// src/execution-profile.ts
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
12
14
|
function executionProfile(env = process.env) {
|
|
13
15
|
const mode = env.ABEL_EXECUTION_MODE ?? "isolated";
|
|
14
|
-
if (mode !== "isolated" && mode !== "local-trusted")
|
|
16
|
+
if (mode !== "isolated" && mode !== "local-trusted" && mode !== "host-trusted")
|
|
15
17
|
throw new Error("execution-mode-invalid");
|
|
16
18
|
const timeoutMs = Number(env.ABEL_VERIFICATION_TIMEOUT_MS ?? 600000);
|
|
17
19
|
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 86400000)
|
|
@@ -21,17 +23,22 @@ function executionProfile(env = process.env) {
|
|
|
21
23
|
throw new Error("verification-environment-names-invalid");
|
|
22
24
|
if (mode === "isolated" && inheritEnvironment.length)
|
|
23
25
|
throw new Error("verification-environment-requires-trusted-mode");
|
|
26
|
+
if (mode === "host-trusted" && env.ABEL_WINDOWS_JOB_HELPER !== undefined && (!env.ABEL_WINDOWS_JOB_HELPER || env.ABEL_WINDOWS_JOB_HELPER.includes("\x00") || !(path.isAbsolute(env.ABEL_WINDOWS_JOB_HELPER) || path.win32.isAbsolute(env.ABEL_WINDOWS_JOB_HELPER))))
|
|
27
|
+
throw new Error("windows-job-helper-path-invalid");
|
|
24
28
|
return {
|
|
25
29
|
mode,
|
|
26
30
|
timeoutMs,
|
|
27
31
|
bwrapPath: env.ABEL_BWRAP_PATH ?? "/usr/bin/bwrap",
|
|
28
|
-
inheritEnvironment
|
|
32
|
+
inheritEnvironment,
|
|
33
|
+
...mode === "host-trusted" ? {
|
|
34
|
+
windowsHelperPath: env.ABEL_WINDOWS_JOB_HELPER ?? fileURLToPath(new URL("./windows-job.exe", import.meta.url))
|
|
35
|
+
} : {}
|
|
29
36
|
};
|
|
30
37
|
}
|
|
31
38
|
|
|
32
39
|
// src/openspec-cli.ts
|
|
33
40
|
import { lstatSync, readdirSync, readFileSync, realpathSync } from "node:fs";
|
|
34
|
-
import
|
|
41
|
+
import path2 from "node:path";
|
|
35
42
|
var PACKAGE_NAME = "@fission-ai/openspec";
|
|
36
43
|
var OPENSPEC_LIMITS = Object.freeze({
|
|
37
44
|
timeoutMs: 30000,
|
|
@@ -57,8 +64,8 @@ function record(value) {
|
|
|
57
64
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : undefined;
|
|
58
65
|
}
|
|
59
66
|
function inside(root, target) {
|
|
60
|
-
const relative =
|
|
61
|
-
return relative === "" || relative !== ".." && !relative.startsWith(`..${
|
|
67
|
+
const relative = path2.relative(root, target);
|
|
68
|
+
return relative === "" || relative !== ".." && !relative.startsWith(`..${path2.sep}`) && !path2.isAbsolute(relative);
|
|
62
69
|
}
|
|
63
70
|
function envValue(environment, name) {
|
|
64
71
|
const key = Object.keys(environment).sort().find((candidate) => process.platform === "win32" ? candidate.toUpperCase() === name.toUpperCase() : candidate === name);
|
|
@@ -66,15 +73,15 @@ function envValue(environment, name) {
|
|
|
66
73
|
}
|
|
67
74
|
function packageEntry(root) {
|
|
68
75
|
const canonicalRoot = realpathSync(root);
|
|
69
|
-
const manifestPath = realpathSync(
|
|
76
|
+
const manifestPath = realpathSync(path2.join(canonicalRoot, "package.json"));
|
|
70
77
|
const manifestStat = lstatSync(manifestPath);
|
|
71
78
|
if (!inside(canonicalRoot, manifestPath) || !manifestStat.isFile() || manifestStat.size > 1024 * 1024)
|
|
72
79
|
throw new Error("manifest-limit");
|
|
73
80
|
const manifest = record(JSON.parse(readFileSync(manifestPath, "utf8")));
|
|
74
81
|
const bin = typeof manifest?.bin === "string" ? manifest.bin : record(manifest?.bin)?.openspec;
|
|
75
|
-
if (manifest?.name !== PACKAGE_NAME || typeof bin !== "string" ||
|
|
82
|
+
if (manifest?.name !== PACKAGE_NAME || typeof bin !== "string" || path2.isAbsolute(bin))
|
|
76
83
|
throw new Error("manifest-invalid");
|
|
77
|
-
const entry = realpathSync(
|
|
84
|
+
const entry = realpathSync(path2.resolve(canonicalRoot, bin));
|
|
78
85
|
if (!inside(canonicalRoot, entry) || !/\.(?:cjs|mjs|js)$/iu.test(entry) || !lstatSync(entry).isFile())
|
|
79
86
|
throw new Error("entry-invalid");
|
|
80
87
|
return entry;
|
|
@@ -99,7 +106,7 @@ function commandIn(directory) {
|
|
|
99
106
|
if (matches.length > 1)
|
|
100
107
|
throw new Error("ambiguous-command");
|
|
101
108
|
if (matches.length === 1) {
|
|
102
|
-
const candidate =
|
|
109
|
+
const candidate = path2.join(directory, matches[0]);
|
|
103
110
|
if (lstatSync(candidate, { throwIfNoEntry: false }))
|
|
104
111
|
return candidate;
|
|
105
112
|
}
|
|
@@ -112,7 +119,7 @@ function resolveOpenSpecInvocation(consumerRoot, environment = process.env) {
|
|
|
112
119
|
const node = configuredNode ?? (process.release.name === "node" && !process.versions.bun ? process.execPath : undefined);
|
|
113
120
|
let executable;
|
|
114
121
|
try {
|
|
115
|
-
if (!node || !
|
|
122
|
+
if (!node || !path2.isAbsolute(node) || /\.(?:cmd|bat|ps1)$/iu.test(node)) {
|
|
116
123
|
throw new Error("runtime-invalid");
|
|
117
124
|
}
|
|
118
125
|
executable = realpathSync(node);
|
|
@@ -123,7 +130,7 @@ function resolveOpenSpecInvocation(consumerRoot, environment = process.env) {
|
|
|
123
130
|
}
|
|
124
131
|
if (configuredRoot !== undefined) {
|
|
125
132
|
try {
|
|
126
|
-
if (!
|
|
133
|
+
if (!path2.isAbsolute(configuredRoot))
|
|
127
134
|
throw new Error("root-invalid");
|
|
128
135
|
return { executable, entry: packageEntry(configuredRoot) };
|
|
129
136
|
} catch {
|
|
@@ -131,9 +138,9 @@ function resolveOpenSpecInvocation(consumerRoot, environment = process.env) {
|
|
|
131
138
|
}
|
|
132
139
|
}
|
|
133
140
|
const consumer = canonicalConsumerRoot(consumerRoot);
|
|
134
|
-
const directories = (envValue(environment, "PATH") ?? "").split(
|
|
141
|
+
const directories = (envValue(environment, "PATH") ?? "").split(path2.delimiter);
|
|
135
142
|
for (const directory of directories) {
|
|
136
|
-
if (!
|
|
143
|
+
if (!path2.isAbsolute(directory))
|
|
137
144
|
continue;
|
|
138
145
|
let command;
|
|
139
146
|
let canonicalDirectory;
|
|
@@ -153,14 +160,14 @@ function resolveOpenSpecInvocation(consumerRoot, environment = process.env) {
|
|
|
153
160
|
throw new OpenSpecCliError("resolve", "resolution", "unsafe-installation");
|
|
154
161
|
}
|
|
155
162
|
const layoutRoots = [
|
|
156
|
-
|
|
157
|
-
|
|
163
|
+
path2.join(canonicalDirectory, "node_modules", PACKAGE_NAME),
|
|
164
|
+
path2.resolve(canonicalDirectory, "../lib/node_modules", PACKAGE_NAME)
|
|
158
165
|
];
|
|
159
166
|
const roots = [];
|
|
160
|
-
let ancestor =
|
|
167
|
+
let ancestor = path2.dirname(target);
|
|
161
168
|
for (let depth = 0;depth < 8; depth++) {
|
|
162
169
|
roots.push(ancestor);
|
|
163
|
-
const parent =
|
|
170
|
+
const parent = path2.dirname(ancestor);
|
|
164
171
|
if (parent === ancestor)
|
|
165
172
|
break;
|
|
166
173
|
ancestor = parent;
|
|
@@ -194,7 +201,7 @@ function resolveOpenSpecInvocation(consumerRoot, environment = process.env) {
|
|
|
194
201
|
import { createHash } from "node:crypto";
|
|
195
202
|
import { chmodSync, lstatSync as lstatSync2, mkdirSync, realpathSync as realpathSync2 } from "node:fs";
|
|
196
203
|
import os from "node:os";
|
|
197
|
-
import
|
|
204
|
+
import path3 from "node:path";
|
|
198
205
|
class StateRootError extends Error {
|
|
199
206
|
code;
|
|
200
207
|
constructor(code) {
|
|
@@ -204,15 +211,15 @@ class StateRootError extends Error {
|
|
|
204
211
|
}
|
|
205
212
|
}
|
|
206
213
|
function isContainedBy(parent, candidate) {
|
|
207
|
-
const relative =
|
|
208
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
214
|
+
const relative = path3.relative(parent, candidate);
|
|
215
|
+
return relative === "" || !relative.startsWith("..") && !path3.isAbsolute(relative);
|
|
209
216
|
}
|
|
210
217
|
function inspectExistingComponents(absolutePath) {
|
|
211
|
-
const parsed =
|
|
212
|
-
const relativeParts = absolutePath.slice(parsed.root.length).split(
|
|
218
|
+
const parsed = path3.parse(absolutePath);
|
|
219
|
+
const relativeParts = absolutePath.slice(parsed.root.length).split(path3.sep).filter(Boolean);
|
|
213
220
|
let current = parsed.root;
|
|
214
221
|
for (const part of relativeParts) {
|
|
215
|
-
current =
|
|
222
|
+
current = path3.join(current, part);
|
|
216
223
|
let stat;
|
|
217
224
|
try {
|
|
218
225
|
stat = lstatSync2(current);
|
|
@@ -230,37 +237,37 @@ function inspectExistingComponents(absolutePath) {
|
|
|
230
237
|
}
|
|
231
238
|
}
|
|
232
239
|
function canonicalFuturePath(input) {
|
|
233
|
-
const absolute =
|
|
240
|
+
const absolute = path3.resolve(input);
|
|
234
241
|
inspectExistingComponents(absolute);
|
|
235
242
|
let cursor = absolute;
|
|
236
243
|
const missing = [];
|
|
237
244
|
for (;; ) {
|
|
238
245
|
try {
|
|
239
|
-
return
|
|
246
|
+
return path3.join(realpathSync2.native(cursor), ...missing.reverse());
|
|
240
247
|
} catch (error) {
|
|
241
248
|
if (error.code !== "ENOENT")
|
|
242
249
|
throw error;
|
|
243
|
-
const parent =
|
|
250
|
+
const parent = path3.dirname(cursor);
|
|
244
251
|
if (parent === cursor)
|
|
245
252
|
throw error;
|
|
246
|
-
missing.push(
|
|
253
|
+
missing.push(path3.basename(cursor));
|
|
247
254
|
cursor = parent;
|
|
248
255
|
}
|
|
249
256
|
}
|
|
250
257
|
}
|
|
251
258
|
function requireAbsoluteDirectoryBase(xdgStateHome, homeDir) {
|
|
252
|
-
if (xdgStateHome &&
|
|
259
|
+
if (xdgStateHome && path3.isAbsolute(xdgStateHome)) {
|
|
253
260
|
return canonicalFuturePath(xdgStateHome);
|
|
254
261
|
}
|
|
255
|
-
if (!
|
|
262
|
+
if (!path3.isAbsolute(homeDir)) {
|
|
256
263
|
throw new StateRootError("invalid-state-base");
|
|
257
264
|
}
|
|
258
|
-
return canonicalFuturePath(
|
|
265
|
+
return canonicalFuturePath(path3.join(homeDir, ".local", "state"));
|
|
259
266
|
}
|
|
260
267
|
function resolveStateRoot(options) {
|
|
261
268
|
let consumerRoot;
|
|
262
269
|
try {
|
|
263
|
-
consumerRoot = realpathSync2.native(
|
|
270
|
+
consumerRoot = realpathSync2.native(path3.resolve(options.consumerRoot));
|
|
264
271
|
if (!lstatSync2(consumerRoot).isDirectory()) {
|
|
265
272
|
throw new StateRootError("consumer-root-unavailable");
|
|
266
273
|
}
|
|
@@ -271,9 +278,9 @@ function resolveStateRoot(options) {
|
|
|
271
278
|
}
|
|
272
279
|
const stateBase = requireAbsoluteDirectoryBase(options.xdgStateHome, options.homeDir ?? os.homedir());
|
|
273
280
|
const consumerRootHash = createHash("sha256").update(consumerRoot).digest("hex");
|
|
274
|
-
const productRoot = canonicalFuturePath(
|
|
275
|
-
const rootsDir = canonicalFuturePath(
|
|
276
|
-
const rootDir = canonicalFuturePath(
|
|
281
|
+
const productRoot = canonicalFuturePath(path3.join(stateBase, "abel-cadence"));
|
|
282
|
+
const rootsDir = canonicalFuturePath(path3.join(productRoot, "roots"));
|
|
283
|
+
const rootDir = canonicalFuturePath(path3.join(rootsDir, consumerRootHash));
|
|
277
284
|
if (isContainedBy(consumerRoot, rootDir)) {
|
|
278
285
|
throw new StateRootError("state-root-inside-consumer");
|
|
279
286
|
}
|
|
@@ -284,10 +291,10 @@ function resolveStateRoot(options) {
|
|
|
284
291
|
productRoot,
|
|
285
292
|
rootsDir,
|
|
286
293
|
rootDir,
|
|
287
|
-
databasePath:
|
|
288
|
-
artifactsDir:
|
|
289
|
-
workspacesDir:
|
|
290
|
-
transactionsDir:
|
|
294
|
+
databasePath: path3.join(rootDir, "control.sqlite3"),
|
|
295
|
+
artifactsDir: path3.join(rootDir, "artifacts"),
|
|
296
|
+
workspacesDir: path3.join(rootDir, "workspaces"),
|
|
297
|
+
transactionsDir: path3.join(rootDir, "transactions")
|
|
291
298
|
};
|
|
292
299
|
}
|
|
293
300
|
|
|
@@ -318,7 +325,7 @@ import {
|
|
|
318
325
|
readFileSync as readFileSync3,
|
|
319
326
|
realpathSync as realpathSync3
|
|
320
327
|
} from "node:fs";
|
|
321
|
-
import
|
|
328
|
+
import path5 from "node:path";
|
|
322
329
|
|
|
323
330
|
// src/control-contracts.ts
|
|
324
331
|
var CONTROL_SCHEMA_PROPERTIES = {
|
|
@@ -609,13 +616,13 @@ import { join, resolve } from "node:path";
|
|
|
609
616
|
|
|
610
617
|
// src/safe-path.ts
|
|
611
618
|
import { lstatSync as lstatSync3 } from "node:fs";
|
|
612
|
-
import
|
|
619
|
+
import path4 from "node:path";
|
|
613
620
|
function observeSafePath(root, relative) {
|
|
614
621
|
if (!isValidRelativePath(relative)) {
|
|
615
622
|
return { kind: "unsafe", reason: "invalid-path" };
|
|
616
623
|
}
|
|
617
624
|
try {
|
|
618
|
-
const resolvedRoot =
|
|
625
|
+
const resolvedRoot = path4.resolve(root);
|
|
619
626
|
const rootStat = lstatSync3(resolvedRoot, { throwIfNoEntry: false });
|
|
620
627
|
if (!rootStat?.isDirectory() || rootStat.isSymbolicLink()) {
|
|
621
628
|
return { kind: "unsafe", reason: "root-unavailable" };
|
|
@@ -625,8 +632,8 @@ function observeSafePath(root, relative) {
|
|
|
625
632
|
let current = resolvedRoot;
|
|
626
633
|
const parts = relative.split("/");
|
|
627
634
|
for (const [index, part] of parts.entries()) {
|
|
628
|
-
current =
|
|
629
|
-
if (!current.startsWith(`${resolvedRoot}${
|
|
635
|
+
current = path4.join(current, part);
|
|
636
|
+
if (!current.startsWith(`${resolvedRoot}${path4.sep}`)) {
|
|
630
637
|
return { kind: "unsafe", reason: "invalid-path" };
|
|
631
638
|
}
|
|
632
639
|
const stat = lstatSync3(current, { throwIfNoEntry: false });
|
|
@@ -716,11 +723,11 @@ function regularFile(root, relative) {
|
|
|
716
723
|
return fileStatus(root, relative) === "regular";
|
|
717
724
|
}
|
|
718
725
|
function localExecutable(root, executable, dependencyOwner = root, environment = {}) {
|
|
719
|
-
const dependencyRoot =
|
|
726
|
+
const dependencyRoot = path5.resolve(dependencyOwner, "node_modules");
|
|
720
727
|
try {
|
|
721
728
|
const canonicalDependencyRoot = realpathSync3(dependencyRoot);
|
|
722
729
|
for (const name of localExecutableNames(executable, environment)) {
|
|
723
|
-
const unresolved = resolveCandidateName(
|
|
730
|
+
const unresolved = resolveCandidateName(path5.resolve(dependencyRoot, ".bin"), name, environment.platform ?? process.platform);
|
|
724
731
|
if (!unresolved)
|
|
725
732
|
continue;
|
|
726
733
|
const canonical = realpathSync3(unresolved);
|
|
@@ -736,11 +743,11 @@ function localExecutable(root, executable, dependencyOwner = root, environment =
|
|
|
736
743
|
}
|
|
737
744
|
function pathApi(platform, ...values) {
|
|
738
745
|
if (platform !== "win32")
|
|
739
|
-
return
|
|
746
|
+
return path5.posix;
|
|
740
747
|
if (process.platform === "win32" || values.some((value) => /^[a-z]:[\\/]/iu.test(value) || value.startsWith("\\\\"))) {
|
|
741
|
-
return
|
|
748
|
+
return path5.win32;
|
|
742
749
|
}
|
|
743
|
-
return
|
|
750
|
+
return path5.posix;
|
|
744
751
|
}
|
|
745
752
|
function pathWithin(root, candidate, platform) {
|
|
746
753
|
const platformPath = pathApi(platform, root, candidate);
|
|
@@ -770,7 +777,7 @@ function runnerNames(command, environment) {
|
|
|
770
777
|
if ((environment.platform ?? process.platform) !== "win32") {
|
|
771
778
|
return [command];
|
|
772
779
|
}
|
|
773
|
-
const extension =
|
|
780
|
+
const extension = path5.extname(command).toLowerCase();
|
|
774
781
|
if (extension) {
|
|
775
782
|
return WINDOWS_EXECUTABLE_EXTENSIONS.has(extension) ? [command] : [];
|
|
776
783
|
}
|
|
@@ -782,7 +789,7 @@ function localExecutableNames(executable, environment) {
|
|
|
782
789
|
if ((environment.platform ?? process.platform) !== "win32") {
|
|
783
790
|
return [executable];
|
|
784
791
|
}
|
|
785
|
-
const extension =
|
|
792
|
+
const extension = path5.extname(executable).toLowerCase();
|
|
786
793
|
if (extension) {
|
|
787
794
|
return WINDOWS_EXECUTABLE_EXTENSIONS.has(extension) ? [executable] : [];
|
|
788
795
|
}
|
|
@@ -792,18 +799,18 @@ function localExecutableNames(executable, environment) {
|
|
|
792
799
|
];
|
|
793
800
|
}
|
|
794
801
|
function resolveCandidateName(directory, name, platform) {
|
|
795
|
-
const exact =
|
|
802
|
+
const exact = path5.join(directory, name);
|
|
796
803
|
if (platform !== "win32")
|
|
797
804
|
return exact;
|
|
798
805
|
try {
|
|
799
806
|
const matches = readdirSync3(directory).filter((entry) => entry.toLowerCase() === name.toLowerCase());
|
|
800
|
-
return matches.length === 1 ?
|
|
807
|
+
return matches.length === 1 ? path5.join(directory, matches[0]) : null;
|
|
801
808
|
} catch {
|
|
802
809
|
return null;
|
|
803
810
|
}
|
|
804
811
|
}
|
|
805
812
|
function within(root, candidate) {
|
|
806
|
-
return candidate === root || candidate.startsWith(`${root}${
|
|
813
|
+
return candidate === root || candidate.startsWith(`${root}${path5.sep}`);
|
|
807
814
|
}
|
|
808
815
|
function commonDirectory(left, right, platform = process.platform) {
|
|
809
816
|
const platformPath = pathApi(platform, left, right);
|
|
@@ -830,15 +837,15 @@ function commonDirectory(left, right, platform = process.platform) {
|
|
|
830
837
|
function unsafeMountSource(source) {
|
|
831
838
|
if (BROAD_MOUNT_ROOTS.has(source))
|
|
832
839
|
return true;
|
|
833
|
-
if (
|
|
840
|
+
if (path5.resolve(source) === path5.parse(path5.resolve(source)).root)
|
|
834
841
|
return true;
|
|
835
|
-
const parts =
|
|
842
|
+
const parts = path5.relative(path5.parse(path5.resolve(source)).root, path5.resolve(source)).split(path5.sep).filter(Boolean).map((part) => part.toLowerCase());
|
|
836
843
|
return parts[0] === "home" && parts.length <= 2 || parts[0] === "users" && parts.length <= 2 || parts[0] === "root" && parts.length <= 1;
|
|
837
844
|
}
|
|
838
845
|
function safeRunnerSource(paths, platform) {
|
|
839
|
-
let source =
|
|
846
|
+
let source = path5.dirname(paths[0]);
|
|
840
847
|
for (const candidate of paths.slice(1)) {
|
|
841
|
-
source = commonDirectory(source,
|
|
848
|
+
source = commonDirectory(source, path5.dirname(candidate), platform);
|
|
842
849
|
}
|
|
843
850
|
if (unsafeMountSource(source))
|
|
844
851
|
return null;
|
|
@@ -856,18 +863,18 @@ function resolvedRunnerEnvironment(environment) {
|
|
|
856
863
|
platform: environment.platform ?? process.platform,
|
|
857
864
|
path: environment.path ?? process.env.PATH ?? "",
|
|
858
865
|
pathExt: environment.pathExt ?? process.env.PATHEXT ?? DEFAULT_WINDOWS_PATH_EXT,
|
|
859
|
-
pathDelimiter: environment.pathDelimiter ??
|
|
866
|
+
pathDelimiter: environment.pathDelimiter ?? path5.delimiter
|
|
860
867
|
};
|
|
861
868
|
}
|
|
862
869
|
function windowsShimBinding(command, launcherPath, environment) {
|
|
863
|
-
const name =
|
|
870
|
+
const name = path5.basename(command, path5.extname(command)).toLowerCase();
|
|
864
871
|
if (name !== "npm" && name !== "npx")
|
|
865
872
|
return null;
|
|
866
873
|
const node = resolveVerificationRunner("node", environment);
|
|
867
|
-
if (!node || node.fixedArgs ||
|
|
874
|
+
if (!node || node.fixedArgs || path5.extname(node.executablePath).toLowerCase() !== ".exe") {
|
|
868
875
|
return null;
|
|
869
876
|
}
|
|
870
|
-
const cli =
|
|
877
|
+
const cli = path5.join(path5.dirname(launcherPath), "node_modules", "npm", "bin", `${name}-cli.js`);
|
|
871
878
|
try {
|
|
872
879
|
const canonicalLauncher = realpathSync3(launcherPath);
|
|
873
880
|
const canonicalCli = realpathSync3(cli);
|
|
@@ -892,14 +899,14 @@ function localPackageDirectories(dependencyRoot) {
|
|
|
892
899
|
for (const entry of readdirSync3(dependencyRoot, { withFileTypes: true })) {
|
|
893
900
|
if (!entry.isDirectory() || entry.name.startsWith("."))
|
|
894
901
|
continue;
|
|
895
|
-
const candidate =
|
|
902
|
+
const candidate = path5.join(dependencyRoot, entry.name);
|
|
896
903
|
if (!entry.name.startsWith("@")) {
|
|
897
904
|
packages.push(candidate);
|
|
898
905
|
continue;
|
|
899
906
|
}
|
|
900
907
|
for (const scoped of readdirSync3(candidate, { withFileTypes: true })) {
|
|
901
908
|
if (scoped.isDirectory() && !scoped.name.startsWith(".")) {
|
|
902
|
-
packages.push(
|
|
909
|
+
packages.push(path5.join(candidate, scoped.name));
|
|
903
910
|
}
|
|
904
911
|
}
|
|
905
912
|
}
|
|
@@ -909,7 +916,7 @@ function localPackageDirectories(dependencyRoot) {
|
|
|
909
916
|
return packages;
|
|
910
917
|
}
|
|
911
918
|
function localPackageBinTargets(dependencyOwner, executable, platform) {
|
|
912
|
-
const dependencyRoot =
|
|
919
|
+
const dependencyRoot = path5.resolve(dependencyOwner, "node_modules");
|
|
913
920
|
let canonicalDependencyRoot;
|
|
914
921
|
try {
|
|
915
922
|
canonicalDependencyRoot = realpathSync3(dependencyRoot);
|
|
@@ -927,7 +934,7 @@ function localPackageBinTargets(dependencyOwner, executable, platform) {
|
|
|
927
934
|
if (!pathWithin(canonicalDependencyRoot, canonicalPackage, platform) || canonicalPackage === canonicalDependencyRoot) {
|
|
928
935
|
continue;
|
|
929
936
|
}
|
|
930
|
-
const manifestPath =
|
|
937
|
+
const manifestPath = path5.join(canonicalPackage, "package.json");
|
|
931
938
|
const manifestStat = lstatSync5(manifestPath);
|
|
932
939
|
if (!manifestStat.isFile() || manifestStat.isSymbolicLink())
|
|
933
940
|
continue;
|
|
@@ -949,7 +956,7 @@ function localPackageBinTargets(dependencyOwner, executable, platform) {
|
|
|
949
956
|
if (typeof relative !== "string" || relative.length === 0 || relative.includes("\x00")) {
|
|
950
957
|
continue;
|
|
951
958
|
}
|
|
952
|
-
const unresolved =
|
|
959
|
+
const unresolved = path5.resolve(canonicalPackage, relative);
|
|
953
960
|
if (!pathWithin(canonicalPackage, unresolved, platform))
|
|
954
961
|
continue;
|
|
955
962
|
const canonical = realpathSync3(unresolved);
|
|
@@ -969,7 +976,7 @@ function resolveLocalVerificationRunner(dependencyOwner, executable, runnerEnvir
|
|
|
969
976
|
if (targets.length !== 1)
|
|
970
977
|
return null;
|
|
971
978
|
const target = targets[0];
|
|
972
|
-
const extension =
|
|
979
|
+
const extension = path5.extname(target).toLowerCase();
|
|
973
980
|
if (extension === ".exe") {
|
|
974
981
|
return { command: executable, executablePath: target };
|
|
975
982
|
}
|
|
@@ -991,7 +998,7 @@ function resolveVerificationRunner(command, runnerEnvironment = {}) {
|
|
|
991
998
|
const candidates = [
|
|
992
999
|
...command === "bun" && process.versions.bun && environment.platform === process.platform ? [process.execPath] : [],
|
|
993
1000
|
...directories.flatMap((directory) => {
|
|
994
|
-
const resolvedDirectory =
|
|
1001
|
+
const resolvedDirectory = path5.resolve(directory);
|
|
995
1002
|
const key = environment.platform === "win32" ? resolvedDirectory.toLowerCase() : resolvedDirectory;
|
|
996
1003
|
if (seenDirectories.has(key))
|
|
997
1004
|
return [];
|
|
@@ -1005,7 +1012,7 @@ function resolveVerificationRunner(command, runnerEnvironment = {}) {
|
|
|
1005
1012
|
const seenCandidates = new Set;
|
|
1006
1013
|
for (const candidate of candidates) {
|
|
1007
1014
|
try {
|
|
1008
|
-
const executablePath =
|
|
1015
|
+
const executablePath = path5.resolve(candidate);
|
|
1009
1016
|
const candidateKey = environment.platform === "win32" ? executablePath.toLowerCase() : executablePath;
|
|
1010
1017
|
if (seenCandidates.has(candidateKey))
|
|
1011
1018
|
continue;
|
|
@@ -1015,7 +1022,7 @@ function resolveVerificationRunner(command, runnerEnvironment = {}) {
|
|
|
1015
1022
|
if (!regularExecutableFile(stat, environment.platform))
|
|
1016
1023
|
continue;
|
|
1017
1024
|
if (environment.platform === "win32") {
|
|
1018
|
-
const extension =
|
|
1025
|
+
const extension = path5.extname(executablePath).toLowerCase();
|
|
1019
1026
|
if (extension === ".cmd") {
|
|
1020
1027
|
const shim = windowsShimBinding(command, executablePath, environment);
|
|
1021
1028
|
if (shim)
|
|
@@ -1032,7 +1039,7 @@ function resolveVerificationRunner(command, runnerEnvironment = {}) {
|
|
|
1032
1039
|
const systemVisible = SYSTEM_MOUNT_ROOTS.some((root) => within(root, executablePath) && within(root, canonical));
|
|
1033
1040
|
if (systemVisible)
|
|
1034
1041
|
return { command, executablePath };
|
|
1035
|
-
const mountSource = commonDirectory(
|
|
1042
|
+
const mountSource = commonDirectory(path5.dirname(executablePath), path5.dirname(canonical), environment.platform);
|
|
1036
1043
|
if (unsafeMountSource(mountSource))
|
|
1037
1044
|
continue;
|
|
1038
1045
|
const sourceStat = lstatSync5(mountSource);
|
|
@@ -1047,7 +1054,7 @@ function packageManifest(root) {
|
|
|
1047
1054
|
if (!regularFile(root, "package.json"))
|
|
1048
1055
|
return null;
|
|
1049
1056
|
try {
|
|
1050
|
-
const value = JSON.parse(readFileSync3(
|
|
1057
|
+
const value = JSON.parse(readFileSync3(path5.join(root, "package.json"), "utf8"));
|
|
1051
1058
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
1052
1059
|
} catch {
|
|
1053
1060
|
return null;
|
|
@@ -1169,7 +1176,7 @@ function validateAtomicCapability(root, step, dependencyOwner, runnerEnvironment
|
|
|
1169
1176
|
"pnpm-workspace.yaml"
|
|
1170
1177
|
].includes(relative))
|
|
1171
1178
|
continue;
|
|
1172
|
-
if (lstatSync5(
|
|
1179
|
+
if (lstatSync5(path5.join(root, relative)).size > 64 * 1024 || executionProfile().mode === "isolated" && unsupportedExecutionConfiguration(readFileSync3(path5.join(root, relative), "utf8"))) {
|
|
1173
1180
|
return adapterFailure(step.id, "verification-config-unsafe", "Execution configuration requires unsupported credentials or host configuration");
|
|
1174
1181
|
}
|
|
1175
1182
|
}
|
|
@@ -1243,6 +1250,549 @@ function validateAcceptedVerificationCapability(root, verification, dependencyOw
|
|
|
1243
1250
|
return capabilitySuccess(verification.id, runnerBindings);
|
|
1244
1251
|
}
|
|
1245
1252
|
|
|
1253
|
+
// src/windows-job-backend.ts
|
|
1254
|
+
import { spawn as spawn2, spawnSync } from "node:child_process";
|
|
1255
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
1256
|
+
import { lstatSync as lstatSync7, readFileSync as readFileSync4 } from "node:fs";
|
|
1257
|
+
import path7 from "node:path";
|
|
1258
|
+
|
|
1259
|
+
// src/isolation-backend.ts
|
|
1260
|
+
import { spawn } from "node:child_process";
|
|
1261
|
+
import { accessSync, constants, lstatSync as lstatSync6 } from "node:fs";
|
|
1262
|
+
import path6 from "node:path";
|
|
1263
|
+
var ISOLATION_RUN_LIMITS = Object.freeze({
|
|
1264
|
+
timeoutMs: 10 * 60000,
|
|
1265
|
+
maxOutputBytes: 256 * 1024,
|
|
1266
|
+
terminateGraceMs: 1000
|
|
1267
|
+
});
|
|
1268
|
+
function defaultProbe(executable) {
|
|
1269
|
+
try {
|
|
1270
|
+
accessSync(executable, constants.X_OK);
|
|
1271
|
+
return true;
|
|
1272
|
+
} catch {
|
|
1273
|
+
return false;
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
function validEnvironmentName(name) {
|
|
1277
|
+
return /^[A-Za-z_][A-Za-z0-9_]*$/u.test(name);
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
class BoundedLog {
|
|
1281
|
+
bytes = 0;
|
|
1282
|
+
#head = Buffer.alloc(0);
|
|
1283
|
+
#tail = Buffer.alloc(0);
|
|
1284
|
+
limit;
|
|
1285
|
+
#witness;
|
|
1286
|
+
#witnessTail = Buffer.alloc(0);
|
|
1287
|
+
witnessMatched = false;
|
|
1288
|
+
constructor(limit, witness) {
|
|
1289
|
+
this.limit = limit;
|
|
1290
|
+
if (witness)
|
|
1291
|
+
this.#witness = Buffer.from(witness);
|
|
1292
|
+
}
|
|
1293
|
+
append(chunk) {
|
|
1294
|
+
const input = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
1295
|
+
this.bytes += input.byteLength;
|
|
1296
|
+
if (this.#witness && !this.witnessMatched) {
|
|
1297
|
+
const searchable = Buffer.concat([this.#witnessTail, input]);
|
|
1298
|
+
this.witnessMatched = searchable.includes(this.#witness);
|
|
1299
|
+
this.#witnessTail = this.witnessMatched ? Buffer.alloc(0) : Buffer.from(searchable.subarray(Math.max(0, searchable.length - this.#witness.length + 1)));
|
|
1300
|
+
}
|
|
1301
|
+
const headLimit = Math.ceil(this.limit / 2);
|
|
1302
|
+
const take = Math.min(input.length, headLimit - this.#head.length);
|
|
1303
|
+
if (take > 0)
|
|
1304
|
+
this.#head = Buffer.concat([this.#head, input.subarray(0, take)]);
|
|
1305
|
+
const rest = input.subarray(take);
|
|
1306
|
+
const tailLimit = this.limit - headLimit;
|
|
1307
|
+
if (rest.length && tailLimit) {
|
|
1308
|
+
this.#tail = rest.length >= tailLimit ? Buffer.from(rest.subarray(-tailLimit)) : Buffer.concat([
|
|
1309
|
+
this.#tail.subarray(Math.max(0, this.#tail.length + rest.length - tailLimit)),
|
|
1310
|
+
rest
|
|
1311
|
+
]);
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
text() {
|
|
1315
|
+
return Buffer.concat([this.#head, this.#tail]).toString("utf8");
|
|
1316
|
+
}
|
|
1317
|
+
metadata() {
|
|
1318
|
+
return { bytes: this.bytes, truncated: this.bytes > this.limit };
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
class BubblewrapIsolationBackend {
|
|
1323
|
+
#localTrusted;
|
|
1324
|
+
#bwrapPath;
|
|
1325
|
+
#probe;
|
|
1326
|
+
#spawn;
|
|
1327
|
+
#timeoutMs;
|
|
1328
|
+
#maxOutputBytes;
|
|
1329
|
+
#terminateGraceMs;
|
|
1330
|
+
constructor(options = {}) {
|
|
1331
|
+
this.#localTrusted = options.localTrusted === true;
|
|
1332
|
+
this.#bwrapPath = options.bwrapPath ?? "/usr/bin/bwrap";
|
|
1333
|
+
this.#probe = options.probe ?? defaultProbe;
|
|
1334
|
+
this.#spawn = options.spawnProcess ?? spawn;
|
|
1335
|
+
this.#timeoutMs = options.timeoutMs ?? ISOLATION_RUN_LIMITS.timeoutMs;
|
|
1336
|
+
this.#maxOutputBytes = options.maxOutputBytes ?? ISOLATION_RUN_LIMITS.maxOutputBytes;
|
|
1337
|
+
this.#terminateGraceMs = options.terminateGraceMs ?? ISOLATION_RUN_LIMITS.terminateGraceMs;
|
|
1338
|
+
if (!Number.isSafeInteger(this.#timeoutMs) || this.#timeoutMs < 1 || !Number.isSafeInteger(this.#maxOutputBytes) || this.#maxOutputBytes < 1 || !Number.isSafeInteger(this.#terminateGraceMs) || this.#terminateGraceMs < 1) {
|
|
1339
|
+
throw new Error("isolation-run-limits-invalid");
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
async available() {
|
|
1343
|
+
try {
|
|
1344
|
+
if (this.#localTrusted && process.platform !== "linux")
|
|
1345
|
+
return false;
|
|
1346
|
+
return Boolean(await this.#probe(this.#bwrapPath));
|
|
1347
|
+
} catch {
|
|
1348
|
+
return false;
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
async run(input) {
|
|
1352
|
+
if (input.outputWitness !== undefined && (typeof input.outputWitness !== "string" || input.outputWitness.length < 1 || input.outputWitness.length > 512))
|
|
1353
|
+
throw new Error("isolation-output-witness-invalid");
|
|
1354
|
+
if (!await this.available()) {
|
|
1355
|
+
return {
|
|
1356
|
+
ok: false,
|
|
1357
|
+
state: "paused",
|
|
1358
|
+
code: "isolation-backend-unavailable"
|
|
1359
|
+
};
|
|
1360
|
+
}
|
|
1361
|
+
if (input.signal?.aborted) {
|
|
1362
|
+
return { ok: false, state: "cancelled", code: "cancelled" };
|
|
1363
|
+
}
|
|
1364
|
+
const root = path6.resolve(input.root);
|
|
1365
|
+
const rootStat = lstatSync6(root, { throwIfNoEntry: false });
|
|
1366
|
+
if (!rootStat?.isDirectory() || rootStat.isSymbolicLink()) {
|
|
1367
|
+
throw new Error("isolation-root-invalid");
|
|
1368
|
+
}
|
|
1369
|
+
const argv = this.#localTrusted ? [
|
|
1370
|
+
"--die-with-parent",
|
|
1371
|
+
"--new-session",
|
|
1372
|
+
"--unshare-pid",
|
|
1373
|
+
"--bind",
|
|
1374
|
+
"/",
|
|
1375
|
+
"/",
|
|
1376
|
+
"--proc",
|
|
1377
|
+
"/proc",
|
|
1378
|
+
"--dev",
|
|
1379
|
+
"/dev",
|
|
1380
|
+
"--chdir",
|
|
1381
|
+
root,
|
|
1382
|
+
"--clearenv"
|
|
1383
|
+
] : [
|
|
1384
|
+
"--die-with-parent",
|
|
1385
|
+
"--new-session",
|
|
1386
|
+
"--unshare-all",
|
|
1387
|
+
"--proc",
|
|
1388
|
+
"/proc",
|
|
1389
|
+
"--dev",
|
|
1390
|
+
"/dev",
|
|
1391
|
+
"--tmpfs",
|
|
1392
|
+
"/tmp",
|
|
1393
|
+
"--bind",
|
|
1394
|
+
root,
|
|
1395
|
+
"/workspace",
|
|
1396
|
+
"--chdir",
|
|
1397
|
+
"/workspace",
|
|
1398
|
+
"--clearenv",
|
|
1399
|
+
"--setenv",
|
|
1400
|
+
"PATH",
|
|
1401
|
+
"/usr/local/bin:/usr/bin:/bin"
|
|
1402
|
+
];
|
|
1403
|
+
if (!this.#localTrusted) {
|
|
1404
|
+
for (const systemRoot of ["/usr", "/bin", "/lib", "/lib64"]) {
|
|
1405
|
+
if (lstatSync6(systemRoot, { throwIfNoEntry: false }))
|
|
1406
|
+
argv.push("--ro-bind", systemRoot, systemRoot);
|
|
1407
|
+
}
|
|
1408
|
+
for (const mount of input.mounts ?? []) {
|
|
1409
|
+
if (!path6.isAbsolute(mount.source) || !path6.isAbsolute(mount.target))
|
|
1410
|
+
throw new Error("isolation-mount-invalid");
|
|
1411
|
+
argv.push(mount.writable ? "--bind" : "--ro-bind", mount.source, mount.target);
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
for (const [name, value] of Object.entries(input.environment ?? {}).sort()) {
|
|
1415
|
+
if (!validEnvironmentName(name) || value.includes("\x00"))
|
|
1416
|
+
throw new Error("isolation-environment-invalid");
|
|
1417
|
+
argv.push("--setenv", name, value);
|
|
1418
|
+
}
|
|
1419
|
+
argv.push("--", input.executable, ...input.args ?? []);
|
|
1420
|
+
return new Promise((resolve2) => {
|
|
1421
|
+
let settled = false;
|
|
1422
|
+
const stdout = new BoundedLog(this.#maxOutputBytes, input.outputWitness);
|
|
1423
|
+
const stderr = new BoundedLog(this.#maxOutputBytes, input.outputWitness);
|
|
1424
|
+
let child;
|
|
1425
|
+
let executionTimer;
|
|
1426
|
+
let killTimer;
|
|
1427
|
+
let terminationResult;
|
|
1428
|
+
const settle = (result) => {
|
|
1429
|
+
if (settled)
|
|
1430
|
+
return;
|
|
1431
|
+
settled = true;
|
|
1432
|
+
if (executionTimer)
|
|
1433
|
+
clearTimeout(executionTimer);
|
|
1434
|
+
input.signal?.removeEventListener("abort", abort);
|
|
1435
|
+
resolve2(result);
|
|
1436
|
+
};
|
|
1437
|
+
const kill = (signal) => {
|
|
1438
|
+
child?.kill(signal);
|
|
1439
|
+
};
|
|
1440
|
+
const terminate = (result) => {
|
|
1441
|
+
if (settled || terminationResult)
|
|
1442
|
+
return;
|
|
1443
|
+
terminationResult = result;
|
|
1444
|
+
if (executionTimer)
|
|
1445
|
+
clearTimeout(executionTimer);
|
|
1446
|
+
killTimer = setTimeout(() => kill("SIGKILL"), this.#terminateGraceMs);
|
|
1447
|
+
killTimer.unref?.();
|
|
1448
|
+
kill("SIGTERM");
|
|
1449
|
+
};
|
|
1450
|
+
const abort = () => {
|
|
1451
|
+
terminate({ ok: false, state: "cancelled", code: "cancelled" });
|
|
1452
|
+
};
|
|
1453
|
+
try {
|
|
1454
|
+
child = this.#spawn(this.#bwrapPath, argv, {
|
|
1455
|
+
cwd: root,
|
|
1456
|
+
shell: false,
|
|
1457
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1458
|
+
});
|
|
1459
|
+
} catch {
|
|
1460
|
+
settle({
|
|
1461
|
+
ok: false,
|
|
1462
|
+
state: "paused",
|
|
1463
|
+
code: "isolation-backend-launch-failed"
|
|
1464
|
+
});
|
|
1465
|
+
return;
|
|
1466
|
+
}
|
|
1467
|
+
const capture = (destination, chunk) => {
|
|
1468
|
+
if (!settled && !terminationResult)
|
|
1469
|
+
destination.append(chunk);
|
|
1470
|
+
};
|
|
1471
|
+
child.stdout?.on("data", (chunk) => {
|
|
1472
|
+
capture(stdout, chunk);
|
|
1473
|
+
});
|
|
1474
|
+
child.stderr?.on("data", (chunk) => {
|
|
1475
|
+
capture(stderr, chunk);
|
|
1476
|
+
});
|
|
1477
|
+
child.once("error", () => {
|
|
1478
|
+
if (terminationResult)
|
|
1479
|
+
return;
|
|
1480
|
+
settle({
|
|
1481
|
+
ok: false,
|
|
1482
|
+
state: "paused",
|
|
1483
|
+
code: "isolation-backend-launch-failed"
|
|
1484
|
+
});
|
|
1485
|
+
});
|
|
1486
|
+
child.once("close", (code) => {
|
|
1487
|
+
if (killTimer)
|
|
1488
|
+
clearTimeout(killTimer);
|
|
1489
|
+
settle(terminationResult ?? {
|
|
1490
|
+
ok: true,
|
|
1491
|
+
state: "completed",
|
|
1492
|
+
exitCode: code ?? 1,
|
|
1493
|
+
stdout: stdout.text(),
|
|
1494
|
+
stderr: stderr.text(),
|
|
1495
|
+
logs: { stdout: stdout.metadata(), stderr: stderr.metadata() },
|
|
1496
|
+
...input.outputWitness ? {
|
|
1497
|
+
outputWitnessMatched: stdout.witnessMatched || stderr.witnessMatched
|
|
1498
|
+
} : {}
|
|
1499
|
+
});
|
|
1500
|
+
});
|
|
1501
|
+
input.signal?.addEventListener("abort", abort, { once: true });
|
|
1502
|
+
if (input.signal?.aborted) {
|
|
1503
|
+
abort();
|
|
1504
|
+
return;
|
|
1505
|
+
}
|
|
1506
|
+
executionTimer = setTimeout(() => terminate({
|
|
1507
|
+
ok: false,
|
|
1508
|
+
state: "paused",
|
|
1509
|
+
code: "isolation-execution-timeout"
|
|
1510
|
+
}), this.#timeoutMs);
|
|
1511
|
+
executionTimer.unref?.();
|
|
1512
|
+
});
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
// src/windows-job-backend.ts
|
|
1517
|
+
var SHUTDOWN_MS = 5000;
|
|
1518
|
+
var invalid = () => {
|
|
1519
|
+
throw new Error("windows-job-request-invalid");
|
|
1520
|
+
};
|
|
1521
|
+
var text = (value) => typeof value === "string" && value.length <= 16384 && !value.includes("\x00") && !/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/u.test(value);
|
|
1522
|
+
var absolute = (value) => text(value) && /^[A-Za-z]:\\/u.test(value) && path7.win32.isAbsolute(value);
|
|
1523
|
+
function encodeWindowsJobRequest(input, timeoutMs) {
|
|
1524
|
+
if (!absolute(input.root) || !absolute(input.executable) || !input.executable.toLowerCase().endsWith(".exe") || input.mounts?.length || !Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 86400000)
|
|
1525
|
+
invalid();
|
|
1526
|
+
const args = input.args ?? [];
|
|
1527
|
+
if (args.length > 128 || !args.every(text))
|
|
1528
|
+
invalid();
|
|
1529
|
+
const seen = new Set;
|
|
1530
|
+
const env = Object.entries(input.environment ?? {});
|
|
1531
|
+
for (const [name, value] of env) {
|
|
1532
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/u.test(name) || !text(value) || seen.has(name.toUpperCase()))
|
|
1533
|
+
invalid();
|
|
1534
|
+
seen.add(name.toUpperCase());
|
|
1535
|
+
}
|
|
1536
|
+
const quote = (value) => `"${value.replace(/(\\*)"/gu, "$1$1\\\"").replace(/(\\+)$/gu, "$1$1")}"`;
|
|
1537
|
+
const command = [input.executable, ...args].map(quote).join(" ");
|
|
1538
|
+
if (command.length >= 32767)
|
|
1539
|
+
invalid();
|
|
1540
|
+
const environment = `${env.sort(([a], [b]) => a.toUpperCase() < b.toUpperCase() ? -1 : 1).map(([k, v]) => `${k}=${v}`).join("\x00")}\x00\x00`;
|
|
1541
|
+
const parts = [
|
|
1542
|
+
`${input.executable}\x00`,
|
|
1543
|
+
`${command}\x00`,
|
|
1544
|
+
`${input.root}\x00`,
|
|
1545
|
+
environment
|
|
1546
|
+
].map((s) => Buffer.from(s, "utf16le"));
|
|
1547
|
+
const header = Buffer.alloc(36);
|
|
1548
|
+
[2, timeoutMs, SHUTDOWN_MS, 0, ...parts.map((p) => p.length), 0].forEach((n, i) => {
|
|
1549
|
+
header.writeUInt32LE(n, i * 4);
|
|
1550
|
+
});
|
|
1551
|
+
const wire = Buffer.concat([header, ...parts]);
|
|
1552
|
+
if (wire.length > 65536 || parts.some((p) => p.length > 60000))
|
|
1553
|
+
invalid();
|
|
1554
|
+
return wire;
|
|
1555
|
+
}
|
|
1556
|
+
function verifyWindowsJobHelper(helper) {
|
|
1557
|
+
try {
|
|
1558
|
+
if (!path7.isAbsolute(helper))
|
|
1559
|
+
return false;
|
|
1560
|
+
const files = [helper, `${helper}.json`];
|
|
1561
|
+
if (files.some((file) => {
|
|
1562
|
+
const s = lstatSync7(file);
|
|
1563
|
+
return !s.isFile() || s.isSymbolicLink() || s.size > 8 * 1024 * 1024;
|
|
1564
|
+
}))
|
|
1565
|
+
return false;
|
|
1566
|
+
const bytes = readFileSync4(helper);
|
|
1567
|
+
const pe = bytes.length >= 64 ? bytes.readUInt32LE(60) : -1;
|
|
1568
|
+
if (bytes.toString("ascii", 0, 2) !== "MZ" || pe < 64 || pe + 6 > bytes.length || bytes.readUInt32LE(pe) !== 17744 || bytes.readUInt16LE(pe + 4) !== 34404)
|
|
1569
|
+
return false;
|
|
1570
|
+
const hash = (data) => createHash3("sha256").update(data).digest("hex");
|
|
1571
|
+
const manifest = JSON.parse(readFileSync4(`${helper}.json`, "utf8"));
|
|
1572
|
+
return manifest.version === 2 && manifest.arch === "x64" && manifest.helperSha256 === hash(bytes) && manifest.sourceSha256 === hash(readFileSync4(new URL("./windows-job.c", import.meta.url)));
|
|
1573
|
+
} catch {
|
|
1574
|
+
return false;
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
function observation(value) {
|
|
1578
|
+
if (!value || typeof value !== "object")
|
|
1579
|
+
return;
|
|
1580
|
+
const v = value;
|
|
1581
|
+
if (Object.keys(v).sort().join(",") !== "exitCode,managedSettled,outcome,reason,rootExited,version" || v.version !== 2 || !["complete", "failed", "uncertain"].includes(v.outcome) || ![
|
|
1582
|
+
"exit",
|
|
1583
|
+
"timeout",
|
|
1584
|
+
"cancelled",
|
|
1585
|
+
"launch-failed",
|
|
1586
|
+
"termination-unconfirmed"
|
|
1587
|
+
].includes(v.reason) || typeof v.rootExited !== "boolean" || typeof v.managedSettled !== "boolean" || !(v.exitCode === null || Number.isInteger(v.exitCode) && v.exitCode >= 0 && v.exitCode <= 4294967295))
|
|
1588
|
+
return;
|
|
1589
|
+
if (v.outcome === "complete" && (v.reason !== "exit" || !v.rootExited || !v.managedSettled || v.exitCode === null))
|
|
1590
|
+
return;
|
|
1591
|
+
if (v.outcome === "failed" && (!v.managedSettled || !["timeout", "cancelled", "launch-failed"].includes(v.reason)))
|
|
1592
|
+
return;
|
|
1593
|
+
return v;
|
|
1594
|
+
}
|
|
1595
|
+
var uncertain = () => ({
|
|
1596
|
+
ok: false,
|
|
1597
|
+
state: "paused",
|
|
1598
|
+
code: "isolation-termination-unconfirmed"
|
|
1599
|
+
});
|
|
1600
|
+
function classify(v, helperExit, output, cancelled) {
|
|
1601
|
+
if (!v?.managedSettled || v.outcome === "uncertain" || helperExit !== 0)
|
|
1602
|
+
return uncertain();
|
|
1603
|
+
if (cancelled || v.reason === "cancelled")
|
|
1604
|
+
return { ok: false, state: "cancelled", code: "cancelled" };
|
|
1605
|
+
if (v.reason === "timeout")
|
|
1606
|
+
return { ok: false, state: "paused", code: "isolation-execution-timeout" };
|
|
1607
|
+
if (v.reason === "launch-failed")
|
|
1608
|
+
return {
|
|
1609
|
+
ok: false,
|
|
1610
|
+
state: "paused",
|
|
1611
|
+
code: "isolation-backend-launch-failed"
|
|
1612
|
+
};
|
|
1613
|
+
if (v.outcome !== "complete" || v.exitCode === null)
|
|
1614
|
+
return uncertain();
|
|
1615
|
+
return {
|
|
1616
|
+
ok: true,
|
|
1617
|
+
state: "completed",
|
|
1618
|
+
exitCode: v.exitCode,
|
|
1619
|
+
stdout: output.text(),
|
|
1620
|
+
stderr: "",
|
|
1621
|
+
outputWitnessMatched: output.witnessMatched,
|
|
1622
|
+
logs: { stdout: output.metadata(), stderr: { bytes: 0, truncated: false } }
|
|
1623
|
+
};
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
class WindowsJobBackend {
|
|
1627
|
+
#options;
|
|
1628
|
+
constructor(options) {
|
|
1629
|
+
for (const [value, maximum] of [
|
|
1630
|
+
[options.timeoutMs ?? ISOLATION_RUN_LIMITS.timeoutMs, 86400000],
|
|
1631
|
+
[
|
|
1632
|
+
options.maxOutputBytes ?? ISOLATION_RUN_LIMITS.maxOutputBytes,
|
|
1633
|
+
1024 * 1024
|
|
1634
|
+
]
|
|
1635
|
+
])
|
|
1636
|
+
if (!Number.isSafeInteger(value) || value < 1 || value > maximum)
|
|
1637
|
+
throw new Error("isolation-run-limits-invalid");
|
|
1638
|
+
this.#options = options;
|
|
1639
|
+
}
|
|
1640
|
+
available() {
|
|
1641
|
+
return (this.#options.platform ?? process.platform) === "win32" && (this.#options.arch ?? process.arch) === "x64" && (this.#options.probe ?? verifyWindowsJobHelper)(this.#options.helperPath);
|
|
1642
|
+
}
|
|
1643
|
+
async run(input) {
|
|
1644
|
+
if (!this.available())
|
|
1645
|
+
return {
|
|
1646
|
+
ok: false,
|
|
1647
|
+
state: "paused",
|
|
1648
|
+
code: "isolation-backend-unavailable"
|
|
1649
|
+
};
|
|
1650
|
+
if (input.signal?.aborted)
|
|
1651
|
+
return { ok: false, state: "cancelled", code: "cancelled" };
|
|
1652
|
+
if (input.outputWitness !== undefined && (!text(input.outputWitness) || input.outputWitness.length < 1 || input.outputWitness.length > 512))
|
|
1653
|
+
return {
|
|
1654
|
+
ok: false,
|
|
1655
|
+
state: "paused",
|
|
1656
|
+
code: "isolation-backend-launch-failed"
|
|
1657
|
+
};
|
|
1658
|
+
const timeoutMs = this.#options.timeoutMs ?? ISOLATION_RUN_LIMITS.timeoutMs;
|
|
1659
|
+
let wire;
|
|
1660
|
+
try {
|
|
1661
|
+
wire = encodeWindowsJobRequest(input, timeoutMs);
|
|
1662
|
+
} catch {
|
|
1663
|
+
return {
|
|
1664
|
+
ok: false,
|
|
1665
|
+
state: "paused",
|
|
1666
|
+
code: "isolation-backend-launch-failed"
|
|
1667
|
+
};
|
|
1668
|
+
}
|
|
1669
|
+
const output = new BoundedLog(this.#options.maxOutputBytes ?? ISOLATION_RUN_LIMITS.maxOutputBytes, input.outputWitness);
|
|
1670
|
+
return new Promise((resolve2) => {
|
|
1671
|
+
let child;
|
|
1672
|
+
let line = "", final, bad = false, closed = false, launchFailed = false;
|
|
1673
|
+
let timer;
|
|
1674
|
+
let stopping = false;
|
|
1675
|
+
const armDeadline = (duration) => {
|
|
1676
|
+
clearTimeout(timer);
|
|
1677
|
+
timer = setTimeout(() => {
|
|
1678
|
+
bad = true;
|
|
1679
|
+
child.kill();
|
|
1680
|
+
finish(uncertain());
|
|
1681
|
+
}, duration);
|
|
1682
|
+
timer.unref?.();
|
|
1683
|
+
};
|
|
1684
|
+
const cancel = () => {
|
|
1685
|
+
if (stopping || closed)
|
|
1686
|
+
return;
|
|
1687
|
+
stopping = true;
|
|
1688
|
+
child.stdin?.end();
|
|
1689
|
+
armDeadline(SHUTDOWN_MS + 5000);
|
|
1690
|
+
};
|
|
1691
|
+
const finish = (result) => {
|
|
1692
|
+
if (closed)
|
|
1693
|
+
return;
|
|
1694
|
+
closed = true;
|
|
1695
|
+
clearTimeout(timer);
|
|
1696
|
+
input.signal?.removeEventListener("abort", cancel);
|
|
1697
|
+
resolve2(result);
|
|
1698
|
+
};
|
|
1699
|
+
try {
|
|
1700
|
+
child = (this.#options.spawnProcess ?? spawn2)(this.#options.helperPath, [], {
|
|
1701
|
+
cwd: input.root,
|
|
1702
|
+
shell: false,
|
|
1703
|
+
windowsHide: true,
|
|
1704
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1705
|
+
});
|
|
1706
|
+
} catch {
|
|
1707
|
+
finish({
|
|
1708
|
+
ok: false,
|
|
1709
|
+
state: "paused",
|
|
1710
|
+
code: "isolation-backend-launch-failed"
|
|
1711
|
+
});
|
|
1712
|
+
return;
|
|
1713
|
+
}
|
|
1714
|
+
const malformed = () => {
|
|
1715
|
+
bad = true;
|
|
1716
|
+
cancel();
|
|
1717
|
+
};
|
|
1718
|
+
child.stdout?.on("data", (chunk) => {
|
|
1719
|
+
if (closed || bad)
|
|
1720
|
+
return;
|
|
1721
|
+
line += chunk.toString("utf8");
|
|
1722
|
+
while (line.includes(`
|
|
1723
|
+
`)) {
|
|
1724
|
+
const end = line.indexOf(`
|
|
1725
|
+
`);
|
|
1726
|
+
const row = line.slice(0, end);
|
|
1727
|
+
line = line.slice(end + 1);
|
|
1728
|
+
if (row.length > 4096 || final) {
|
|
1729
|
+
malformed();
|
|
1730
|
+
return;
|
|
1731
|
+
}
|
|
1732
|
+
try {
|
|
1733
|
+
const value = JSON.parse(row);
|
|
1734
|
+
if (Object.keys(value).length === 1 && typeof value.outputHex === "string" && /^(?:[a-f0-9]{2}){1,1024}$/u.test(value.outputHex))
|
|
1735
|
+
output.append(Buffer.from(value.outputHex, "hex"));
|
|
1736
|
+
else {
|
|
1737
|
+
final = observation(value);
|
|
1738
|
+
if (!final) {
|
|
1739
|
+
malformed();
|
|
1740
|
+
return;
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
} catch {
|
|
1744
|
+
malformed();
|
|
1745
|
+
return;
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
if (line.length > 4096)
|
|
1749
|
+
malformed();
|
|
1750
|
+
});
|
|
1751
|
+
child.stderr?.on("data", () => {});
|
|
1752
|
+
child.stdin?.on("error", () => {
|
|
1753
|
+
bad = true;
|
|
1754
|
+
});
|
|
1755
|
+
child.once("error", () => {
|
|
1756
|
+
bad = true;
|
|
1757
|
+
launchFailed = child.pid === undefined;
|
|
1758
|
+
});
|
|
1759
|
+
child.once("close", (code) => finish(launchFailed ? {
|
|
1760
|
+
ok: false,
|
|
1761
|
+
state: "paused",
|
|
1762
|
+
code: "isolation-backend-launch-failed"
|
|
1763
|
+
} : bad || line.length ? uncertain() : classify(final, code, output, input.signal?.aborted === true)));
|
|
1764
|
+
armDeadline(timeoutMs + SHUTDOWN_MS + 1e4);
|
|
1765
|
+
input.signal?.addEventListener("abort", cancel, { once: true });
|
|
1766
|
+
child.stdin?.write(wire);
|
|
1767
|
+
if (input.signal?.aborted)
|
|
1768
|
+
cancel();
|
|
1769
|
+
});
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
function probeWindowsJob(helper) {
|
|
1773
|
+
if (process.platform !== "win32" || process.arch !== "x64" || !verifyWindowsJobHelper(helper))
|
|
1774
|
+
return false;
|
|
1775
|
+
const wire = encodeWindowsJobRequest({
|
|
1776
|
+
root: process.cwd(),
|
|
1777
|
+
executable: process.execPath,
|
|
1778
|
+
args: ["-e", "process.exit(0)"],
|
|
1779
|
+
environment: { SystemRoot: process.env.SystemRoot ?? "C:\\Windows" }
|
|
1780
|
+
}, 5000);
|
|
1781
|
+
const result = spawnSync(helper, ["--probe"], {
|
|
1782
|
+
input: wire,
|
|
1783
|
+
shell: false,
|
|
1784
|
+
encoding: "utf8",
|
|
1785
|
+
timeout: 15000,
|
|
1786
|
+
maxBuffer: 65536
|
|
1787
|
+
});
|
|
1788
|
+
try {
|
|
1789
|
+
const v = observation(JSON.parse(result.stdout.trim()));
|
|
1790
|
+
return !result.error && result.status === 0 && v?.outcome === "complete" && v.exitCode === 0;
|
|
1791
|
+
} catch {
|
|
1792
|
+
return false;
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1246
1796
|
// src/operator-tools.ts
|
|
1247
1797
|
var PACKAGE_LOCKS = {
|
|
1248
1798
|
bun: ["bun.lock", "bun.lockb"],
|
|
@@ -1252,7 +1802,7 @@ var PACKAGE_LOCKS = {
|
|
|
1252
1802
|
};
|
|
1253
1803
|
function packageManagerSelection(root, declaration) {
|
|
1254
1804
|
const managers = Object.keys(PACKAGE_LOCKS);
|
|
1255
|
-
const lockfiles = managers.flatMap((manager) => PACKAGE_LOCKS[manager].filter((file) =>
|
|
1805
|
+
const lockfiles = managers.flatMap((manager) => PACKAGE_LOCKS[manager].filter((file) => lstatSync8(path8.join(root, file), { throwIfNoEntry: false })?.isFile()));
|
|
1256
1806
|
const inferred = managers.filter((manager) => PACKAGE_LOCKS[manager].some((file) => lockfiles.includes(file)));
|
|
1257
1807
|
const declared = typeof declaration === "string" ? managers.find((manager) => new RegExp(`^${manager}@[0-9]+\\.[0-9]+\\.[0-9]+(?:[-+][a-z0-9._+-]+)?$`, "iu").test(declaration)) : undefined;
|
|
1258
1808
|
const selectedPackageManager = declaration !== undefined ? declared ?? null : inferred.length === 1 ? inferred[0] ?? null : inferred.length > 1 ? null : "npm";
|
|
@@ -1285,7 +1835,7 @@ function inspectConsumer(root) {
|
|
|
1285
1835
|
return process.versions.node;
|
|
1286
1836
|
}, "Install Node >=22.13.");
|
|
1287
1837
|
check("git", () => {
|
|
1288
|
-
const result =
|
|
1838
|
+
const result = spawnSync2("git", ["rev-parse", "--show-toplevel"], {
|
|
1289
1839
|
cwd: root,
|
|
1290
1840
|
encoding: "utf8",
|
|
1291
1841
|
timeout: 5000,
|
|
@@ -1301,9 +1851,14 @@ function inspectConsumer(root) {
|
|
|
1301
1851
|
}, "Install @fission-ai/openspec@1.5.0, or configure ABEL_OPENSPEC_PACKAGE_ROOT / ABEL_OPENSPEC_NODE before launching the host.");
|
|
1302
1852
|
check("execution", () => {
|
|
1303
1853
|
const profile = executionProfile();
|
|
1854
|
+
if (profile.mode === "host-trusted") {
|
|
1855
|
+
if (!probeWindowsJob(profile.windowsHelperPath ?? ""))
|
|
1856
|
+
throw new Error("windows-job-backend-unavailable");
|
|
1857
|
+
return "host-trusted: Windows x64 Job probe passed; host files/network accessible";
|
|
1858
|
+
}
|
|
1304
1859
|
if (process.platform !== "linux")
|
|
1305
1860
|
throw new Error("linux-pid-isolation-required");
|
|
1306
|
-
const result =
|
|
1861
|
+
const result = spawnSync2(profile.bwrapPath, [
|
|
1307
1862
|
"--ro-bind",
|
|
1308
1863
|
"/",
|
|
1309
1864
|
"/",
|
|
@@ -1314,45 +1869,45 @@ function inspectConsumer(root) {
|
|
|
1314
1869
|
if (result.status !== 0)
|
|
1315
1870
|
throw new Error("bubblewrap-probe-failed");
|
|
1316
1871
|
return `${profile.mode}: Bubblewrap PID namespace probe passed`;
|
|
1317
|
-
}, "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
|
|
1872
|
+
}, "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).");
|
|
1318
1873
|
check("request-budgets", () => JSON.stringify(requestBounds()), "Set ABEL_FIRST_PROGRESS_MS and ABEL_STREAM_IDLE_MS to positive milliseconds <=1200000.");
|
|
1319
1874
|
check("state-root", () => resolveStateRoot({
|
|
1320
1875
|
consumerRoot: root,
|
|
1321
1876
|
xdgStateHome: process.env.XDG_STATE_HOME
|
|
1322
1877
|
}).rootDir, "Use an absolute repository-external XDG_STATE_HOME.");
|
|
1323
1878
|
check("workspace-dependencies", () => {
|
|
1324
|
-
const dependencies =
|
|
1325
|
-
if (!
|
|
1879
|
+
const dependencies = path8.join(root, "node_modules");
|
|
1880
|
+
if (!lstatSync8(dependencies, { throwIfNoEntry: false }))
|
|
1326
1881
|
return "no node_modules; prepare dependencies before verification";
|
|
1327
|
-
const names = readdirSync4(dependencies).flatMap((name) => name.startsWith("@") &&
|
|
1882
|
+
const names = readdirSync4(dependencies).flatMap((name) => name.startsWith("@") && lstatSync8(path8.join(dependencies, name)).isDirectory() ? readdirSync4(path8.join(dependencies, name)).map((child) => `${name}/${child}`) : [name]);
|
|
1328
1883
|
if (names.length > 20000)
|
|
1329
1884
|
throw new Error("dependency-view-too-large");
|
|
1330
1885
|
let links = 0;
|
|
1331
1886
|
for (const name of names) {
|
|
1332
|
-
const file =
|
|
1333
|
-
if (!
|
|
1887
|
+
const file = path8.join(dependencies, name);
|
|
1888
|
+
if (!lstatSync8(file).isSymbolicLink())
|
|
1334
1889
|
continue;
|
|
1335
1890
|
const resolved = realpathSync4(file);
|
|
1336
|
-
const relative =
|
|
1337
|
-
if (relative === ".." || relative.startsWith(`..${
|
|
1891
|
+
const relative = path8.relative(root, resolved);
|
|
1892
|
+
if (relative === ".." || relative.startsWith(`..${path8.sep}`) || path8.isAbsolute(relative))
|
|
1338
1893
|
throw new Error("dependency-link-outside-consumer");
|
|
1339
|
-
if (!resolved.startsWith(`${dependencies}${
|
|
1894
|
+
if (!resolved.startsWith(`${dependencies}${path8.sep}`))
|
|
1340
1895
|
links++;
|
|
1341
1896
|
}
|
|
1342
1897
|
return `${links} repository workspace link(s); their targets must be included in the candidate baseline`;
|
|
1343
1898
|
}, "Repair dangling/external workspace links and ensure local package sources are tracked or admitted in the candidate.");
|
|
1344
|
-
const manifest =
|
|
1899
|
+
const manifest = path8.join(root, "package.json");
|
|
1345
1900
|
let selection = packageManagerSelection(root, undefined);
|
|
1346
|
-
if (
|
|
1901
|
+
if (lstatSync8(manifest, { throwIfNoEntry: false })?.isFile()) {
|
|
1347
1902
|
check("manifest", () => {
|
|
1348
|
-
const parsed2 = JSON.parse(
|
|
1903
|
+
const parsed2 = JSON.parse(readFileSync5(manifest, "utf8"));
|
|
1349
1904
|
if (!parsed2 || typeof parsed2 !== "object")
|
|
1350
1905
|
throw new Error("manifest-invalid");
|
|
1351
1906
|
return Object.keys(parsed2.scripts ?? {}).join(", ") || "no package scripts";
|
|
1352
1907
|
}, "Repair package.json.");
|
|
1353
1908
|
const parsed = (() => {
|
|
1354
1909
|
try {
|
|
1355
|
-
const value = JSON.parse(
|
|
1910
|
+
const value = JSON.parse(readFileSync5(manifest, "utf8"));
|
|
1356
1911
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
1357
1912
|
} catch {
|
|
1358
1913
|
return {};
|
|
@@ -1395,7 +1950,7 @@ function storageUsage(root, maximum = 1e5) {
|
|
|
1395
1950
|
truncated = true;
|
|
1396
1951
|
return;
|
|
1397
1952
|
}
|
|
1398
|
-
const stat =
|
|
1953
|
+
const stat = lstatSync8(file, { throwIfNoEntry: false });
|
|
1399
1954
|
if (!stat)
|
|
1400
1955
|
return;
|
|
1401
1956
|
entries++;
|
|
@@ -1403,7 +1958,7 @@ function storageUsage(root, maximum = 1e5) {
|
|
|
1403
1958
|
bytes += stat.size;
|
|
1404
1959
|
else if (stat.isDirectory())
|
|
1405
1960
|
for (const name of readdirSync4(file)) {
|
|
1406
|
-
visit(
|
|
1961
|
+
visit(path8.join(file, name));
|
|
1407
1962
|
if (truncated)
|
|
1408
1963
|
break;
|
|
1409
1964
|
}
|
|
@@ -1417,7 +1972,7 @@ function inspectRuns(root) {
|
|
|
1417
1972
|
xdgStateHome: process.env.XDG_STATE_HOME
|
|
1418
1973
|
});
|
|
1419
1974
|
const usage = storageUsage(state.rootDir);
|
|
1420
|
-
if (!
|
|
1975
|
+
if (!lstatSync8(state.databasePath, { throwIfNoEntry: false }))
|
|
1421
1976
|
return { stateRoot: state.rootDir, usage, runs: [], truncated: false };
|
|
1422
1977
|
const db = new DatabaseSync(state.databasePath, { readOnly: true });
|
|
1423
1978
|
try {
|
|
@@ -1438,7 +1993,7 @@ var [command = "doctor", root = process.cwd()] = process.argv.slice(2);
|
|
|
1438
1993
|
try {
|
|
1439
1994
|
if (!["doctor", "runs"].includes(command))
|
|
1440
1995
|
throw new Error("Usage: node --experimental-strip-types <cadence>/src/operator-cli.mjs doctor|runs [consumer-root]");
|
|
1441
|
-
const result = command === "doctor" ? inspectConsumer(
|
|
1996
|
+
const result = command === "doctor" ? inspectConsumer(path9.resolve(root)) : inspectRuns(path9.resolve(root));
|
|
1442
1997
|
console.log(JSON.stringify(result, null, 2));
|
|
1443
1998
|
if (command === "doctor" && !result.ok)
|
|
1444
1999
|
process.exitCode = 1;
|