@deeeed/metamask-harness 0.79.0 → 0.80.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/CHANGELOG.md +47 -0
- package/README.md +1 -1
- package/adapters/extension/ensure-browser.sh +2 -2
- package/adapters/extension/inject.mjs +0 -1
- package/adapters/extension/launch-browser.cjs +39 -613
- package/adapters/extension/readiness.mjs +2 -5
- package/adapters/extension/reattach.sh +2 -2
- package/adapters/extension/stamp-runtime-title.cjs +1 -1
- package/adapters/manifest.json +2 -34
- package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +57 -1358
- package/adapters/mobile/bridge-runtime/teaching.cjs +20 -0
- package/adapters/mobile/inject.sh +16 -0
- package/adapters/mobile/launch-console-forwarder.cjs +1 -2
- package/adapters/mobile/open-device.sh +2 -2
- package/adapters/mobile/reload-app.mjs +4 -4
- package/adapters/mobile/reset-app-data.sh +1 -1
- package/adapters/mobile/start-console-forwarder.sh +3 -2
- package/adapters/mobile/start-metro.sh +17 -12
- package/adapters/mobile/wait-for-bridge.cjs +2 -2
- package/adapters/shared/harness-source-fingerprint.mjs +13 -8
- package/adapters/shared/open-debug.mjs +2 -2
- package/adapters/shared/sync-wallet-fixture.sh +8 -5
- package/adapters/terminal/cleanup.mjs +2 -1
- package/adapters/terminal/inject.mjs +2 -1
- package/bin/mm-harness +9 -28
- package/dist/adapters/checkout-files.js +16 -0
- package/dist/adapters/console-capture.js +14 -1
- package/dist/adapters/core/actions.js +4 -3
- package/dist/adapters/core/overlay.js +1 -1
- package/dist/adapters/core/readiness.js +28 -1
- package/dist/adapters/core/run.js +2 -2
- package/dist/adapters/core/surface.js +16 -1
- package/dist/adapters/extension/browser-pid.js +56 -0
- package/dist/adapters/extension/console-capture.js +1 -1
- package/dist/adapters/extension/doctor-checks.js +1 -1
- package/dist/adapters/extension/ensure-ready.js +5 -4
- package/dist/adapters/extension/extension-id.js +1 -1
- package/dist/adapters/extension/feature-flags.js +1 -1
- package/dist/adapters/extension/fixtures.js +1 -2
- package/dist/adapters/extension/launch.js +4 -5
- package/dist/adapters/extension/launched-browser.js +1 -2
- package/dist/adapters/extension/network-observer.js +1 -1
- package/dist/adapters/extension/overlay.js +1 -1
- package/dist/adapters/extension/product-config.js +1 -1
- package/dist/adapters/extension/readiness.js +1 -1
- package/dist/adapters/extension/reload.js +1 -1
- package/dist/adapters/extension/run.js +24 -10
- package/dist/adapters/extension/runtime-decision.js +2 -1
- package/dist/adapters/extension/runtime.js +9 -5
- package/dist/adapters/extension/surface.js +15 -2
- package/dist/adapters/extension/verify.js +2 -2
- package/dist/adapters/harness-freshness.js +2 -1
- package/dist/adapters/mobile/android-gfxinfo-performance.js +1 -3
- package/dist/adapters/mobile/app-identity.js +10 -0
- package/dist/adapters/mobile/doctor-checks.js +1 -1
- package/dist/adapters/mobile/fixtures.js +1 -1
- package/dist/adapters/mobile/launch.js +27 -28
- package/dist/adapters/mobile/lifecycle.js +1 -1
- package/dist/adapters/mobile/metro-env.js +9 -59
- package/dist/adapters/mobile/performance-observer.js +1 -1
- package/dist/adapters/mobile/prepare-target.js +1 -1
- package/dist/adapters/mobile/prepare.js +3 -4
- package/dist/adapters/mobile/provision.js +1 -1
- package/dist/adapters/mobile/readiness.js +1 -1
- package/dist/adapters/mobile/recording-target.js +15 -0
- package/dist/adapters/mobile/release-artifact-state.js +1 -1
- package/dist/adapters/mobile/reload.js +1 -1
- package/dist/adapters/mobile/run.js +8 -7
- package/dist/adapters/mobile/runtime-decision.js +2 -4
- package/dist/adapters/mobile/source-freshness.js +22 -126
- package/dist/adapters/mobile/surface.js +26 -3
- package/dist/adapters/mobile/ui.js +16 -5
- package/dist/adapters/mobile/verify.js +2 -3
- package/dist/adapters/performance/cdp-trace.js +1 -1
- package/dist/adapters/performance/device-platform.js +1 -1
- package/dist/adapters/slot-ports.js +1 -1
- package/dist/adapters/surface.js +1 -0
- package/dist/adapters/terminal/console-capture.js +1 -1
- package/dist/adapters/terminal/launch.js +1 -1
- package/dist/adapters/terminal/run.js +25 -2
- package/dist/adapters/terminal/surface.js +7 -1
- package/dist/adapters.js +8 -4
- package/dist/advisor-policy.js +1 -1
- package/dist/checkout-view.js +1 -2
- package/dist/cli.js +28 -15
- package/dist/command-contract.js +1 -26
- package/dist/commands/call.js +10 -24
- package/dist/commands/check.js +3 -4
- package/dist/commands/checklist.js +1 -1
- package/dist/commands/completion-candidates.js +2 -10
- package/dist/commands/config.js +2 -2
- package/dist/commands/core-readiness.js +12 -7
- package/dist/commands/debug.js +3 -3
- package/dist/commands/device-target.js +5 -5
- package/dist/commands/doctor.js +2 -14
- package/dist/commands/domain.js +2 -2
- package/dist/commands/ensure-ready.js +1 -7
- package/dist/commands/fixtures.js +5 -6
- package/dist/commands/help.js +3 -4
- package/dist/commands/launch/device.js +10 -17
- package/dist/commands/launch/extension.js +2 -8
- package/dist/commands/launch/mobile.js +1 -1
- package/dist/commands/list-executables.js +1 -3
- package/dist/commands/logs.js +3 -3
- package/dist/commands/manifest.js +1 -12
- package/dist/commands/mobile-device-view.js +4 -2
- package/dist/commands/overlay.js +110 -0
- package/dist/commands/pin.js +1 -1
- package/dist/commands/pr-body.js +2 -2
- package/dist/commands/prepare.js +4 -9
- package/dist/commands/provision.js +3 -12
- package/dist/commands/recipe-advice.js +1 -2
- package/dist/commands/recipe-quality.js +2 -3
- package/dist/commands/resolve-extension.js +1 -7
- package/dist/commands/review.js +4 -4
- package/dist/commands/run-engine.js +12 -51
- package/dist/commands/run.js +4 -25
- package/dist/commands/runtime-decision.js +1 -6
- package/dist/commands/runtime-health.js +1 -7
- package/dist/commands/runtime-launch.js +1 -13
- package/dist/commands/self-test.js +2 -6
- package/dist/commands/status-watch.js +1 -2
- package/dist/commands/status.js +2 -5
- package/dist/commands/task-init.js +2 -3
- package/dist/commands/tutorial.js +2 -2
- package/dist/commands/update.js +1 -1
- package/dist/completions-cache.js +1 -1
- package/dist/console-classifier.js +19 -0
- package/dist/doctor.js +2 -3
- package/dist/funding-execution-context.js +15 -1
- package/dist/host.js +32 -0
- package/dist/index.js +1 -6
- package/dist/live-tsx.js +49 -0
- package/dist/mm-harness-cli.js +2 -4
- package/dist/network-observation.js +4 -4
- package/dist/paths.js +34 -64
- package/dist/performance-observation.js +2 -2
- package/dist/progress.js +1 -1
- package/dist/recipe-assistance-context.js +1 -3
- package/dist/recipe-security.js +2 -59
- package/dist/review/knowledge.js +2 -2
- package/dist/runner.js +5 -3
- package/dist/runtime-context.js +1 -1
- package/dist/task-view.js +2 -3
- package/docs/CONTRIBUTING.md +1 -1
- package/docs/cli-contract.md +2 -2
- package/docs/feature-guard.md +90 -0
- package/library/README.md +3 -1
- package/library/actions/core/perps/_controller.mjs +153 -130
- package/library/actions/core/perps/quote_fees.mjs +58 -0
- package/library/actions/core/wallet/_fixture-accounts.mjs +69 -0
- package/library/actions/core/wallet/list_accounts.mjs +14 -57
- package/library/actions/extension/feature_flags/state.mjs +1 -1
- package/library/actions/extension/platform/cdp.mjs +2 -8
- package/library/actions/harness-exports.mjs +1 -6
- package/library/actions/mobile/app/network-control.mjs +6 -8
- package/library/actions/mobile/perps/prepare_local_snapshot_endpoint.mjs +1 -1
- package/library/actions/mobile/platform/bridge.mjs +8 -8
- package/library/actions/mobile/platform/native-session-name.mjs +21 -1
- package/library/actions/mobile/platform/native-session.mjs +3 -4
- package/library/actions/mobile/platform/observe-ui.mjs +12 -6
- package/library/actions/mobile/ui/native-navigation.mjs +4 -3
- package/library/actions/mobile/wallet/native-ui.mjs +3 -2
- package/library/actions/shared/analytics/_adapter.mjs +1 -1
- package/library/actions/shared/console-findings.mjs +1 -1
- package/library/actions/terminal/platform/runtime.mjs +2 -1
- package/library/manifests/core.action-manifest.json +83 -13
- package/package.json +8 -9
- package/adapters/extension/lib/slot-title.cjs +0 -292
- package/adapters/mobile/bridge-runtime/console-forwarder.cjs +0 -472
- package/adapters/mobile/bridge-runtime/lib/bridge-errors.cjs +0 -125
- package/adapters/mobile/bridge-runtime/lib/cdp-broker.cjs +0 -1055
- package/adapters/mobile/bridge-runtime/lib/cdp-eval.cjs +0 -110
- package/adapters/mobile/bridge-runtime/lib/config.cjs +0 -61
- package/adapters/mobile/bridge-runtime/lib/console-format.cjs +0 -53
- package/adapters/mobile/bridge-runtime/lib/devtools-proxy.cjs +0 -177
- package/adapters/mobile/bridge-runtime/lib/issue-capture.cjs +0 -446
- package/adapters/mobile/bridge-runtime/lib/match-bridge-target.cjs +0 -84
- package/adapters/mobile/bridge-runtime/lib/target-discovery.cjs +0 -403
- package/adapters/mobile/bridge-runtime/lib/ws-client.cjs +0 -116
- package/adapters/mobile/coalesce-metro-log.cjs +0 -24
- package/adapters/mobile/launch-metro.cjs +0 -77
- package/adapters/mobile/metro-config.cjs +0 -98
- package/adapters/mobile/metro-log-generation.cjs +0 -106
- package/dist/adapters/mobile/frame-metrics.js +0 -118
- package/dist/adapters/mobile/video-recorder.js +0 -289
- package/dist/artifact-files.js +0 -92
- package/dist/checkout-lock.js +0 -149
- package/dist/cli-color.js +0 -98
- package/dist/command-journal.js +0 -308
- package/dist/commands/last.js +0 -60
- package/dist/commands/launch/index.js +0 -109
- package/dist/commands/parse-args.js +0 -279
- package/dist/commands/reload.js +0 -41
- package/dist/commands/run-report.js +0 -241
- package/dist/commands/shared.js +0 -323
- package/dist/commands/stop.js +0 -60
- package/dist/devices.js +0 -100
- package/dist/execution-provenance.js +0 -332
- package/dist/harness.js +0 -451
- package/dist/heal-bounds.js +0 -137
- package/dist/json-stream.js +0 -60
- package/dist/leaf-invoke.js +0 -19
- package/dist/library-provenance.js +0 -33
- package/dist/live-adapter-contract.js +0 -380
- package/dist/recording-target.js +0 -120
- package/dist/run-diagnostics.js +0 -364
- package/dist/run-recording.js +0 -481
- package/dist/runtime-proof.js +0 -90
- package/library/actions/mobile/platform/tool-paths.mjs +0 -123
package/dist/artifact-files.js
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import { constants as fsConstants } from "node:fs";
|
|
2
|
-
import {
|
|
3
|
-
lstat,
|
|
4
|
-
mkdir,
|
|
5
|
-
open,
|
|
6
|
-
readFile,
|
|
7
|
-
realpath,
|
|
8
|
-
writeFile
|
|
9
|
-
} from "node:fs/promises";
|
|
10
|
-
import path from "node:path";
|
|
11
|
-
async function writeContainedArtifact(artifactsDir, relativePath, value, label) {
|
|
12
|
-
const file = resolveContainedArtifact(artifactsDir, relativePath, label);
|
|
13
|
-
await mkdir(path.dirname(file), { recursive: true });
|
|
14
|
-
await assertContainedParent(artifactsDir, file, label);
|
|
15
|
-
await assertNotSymlink(file, label);
|
|
16
|
-
const handle = await open(
|
|
17
|
-
file,
|
|
18
|
-
fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_TRUNC | fsConstants.O_NOFOLLOW,
|
|
19
|
-
384
|
|
20
|
-
);
|
|
21
|
-
try {
|
|
22
|
-
await handle.writeFile(value);
|
|
23
|
-
} finally {
|
|
24
|
-
await handle.close();
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
async function readContainedJsonArtifact(artifactsDir, relativePath, maxBytes, label) {
|
|
28
|
-
const file = resolveContainedArtifact(artifactsDir, relativePath, label);
|
|
29
|
-
await assertContainedParent(artifactsDir, file, label);
|
|
30
|
-
await assertNotSymlink(file, label);
|
|
31
|
-
const handle = await open(file, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW);
|
|
32
|
-
try {
|
|
33
|
-
const info = await handle.stat();
|
|
34
|
-
if (!info.isFile() || info.size > maxBytes) {
|
|
35
|
-
throw new Error(`${label} is not a bounded regular file.`);
|
|
36
|
-
}
|
|
37
|
-
return JSON.parse(await handle.readFile("utf8"));
|
|
38
|
-
} finally {
|
|
39
|
-
await handle.close();
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
async function indexArtifactManifest(manifestPath, entries) {
|
|
43
|
-
const parsed = JSON.parse(await readFile(manifestPath, "utf8"));
|
|
44
|
-
const manifest = asRecord(parsed);
|
|
45
|
-
const replacementPaths = new Set(entries.map((entry) => entry.path));
|
|
46
|
-
const current = Array.isArray(manifest.artifacts) ? manifest.artifacts.filter(
|
|
47
|
-
(artifact) => !replacementPaths.has(String(asRecord(artifact).path ?? ""))
|
|
48
|
-
) : [];
|
|
49
|
-
await writeFile(
|
|
50
|
-
manifestPath,
|
|
51
|
-
`${JSON.stringify(
|
|
52
|
-
{ ...manifest, artifacts: [...current, ...entries] },
|
|
53
|
-
null,
|
|
54
|
-
2
|
|
55
|
-
)}
|
|
56
|
-
`
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
function resolveContainedArtifact(artifactsDir, relativePath, label) {
|
|
60
|
-
const root = path.resolve(artifactsDir);
|
|
61
|
-
const file = path.resolve(root, relativePath);
|
|
62
|
-
const relative = path.relative(root, file);
|
|
63
|
-
if (relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
64
|
-
throw new Error(`${label} path must stay inside artifactsDir.`);
|
|
65
|
-
}
|
|
66
|
-
return file;
|
|
67
|
-
}
|
|
68
|
-
async function assertContainedParent(artifactsDir, file, label) {
|
|
69
|
-
const root = await realpath(artifactsDir);
|
|
70
|
-
const parent = await realpath(path.dirname(file));
|
|
71
|
-
const relative = path.relative(root, parent);
|
|
72
|
-
if (relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
73
|
-
throw new Error(`${label} parent must stay inside artifactsDir.`);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
async function assertNotSymlink(file, label) {
|
|
77
|
-
try {
|
|
78
|
-
if ((await lstat(file)).isSymbolicLink()) {
|
|
79
|
-
throw new Error(`${label} must not be a symbolic link.`);
|
|
80
|
-
}
|
|
81
|
-
} catch (error) {
|
|
82
|
-
if (error.code !== "ENOENT") throw error;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
function asRecord(value) {
|
|
86
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
87
|
-
}
|
|
88
|
-
export {
|
|
89
|
-
indexArtifactManifest,
|
|
90
|
-
readContainedJsonArtifact,
|
|
91
|
-
writeContainedArtifact
|
|
92
|
-
};
|
package/dist/checkout-lock.js
DELETED
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
import { ensureRuntimeDirectory, processIdentity } from "@farmslot/recipe-runner/runtime/operation";
|
|
2
|
-
import { randomUUID } from "node:crypto";
|
|
3
|
-
import fs from "node:fs";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
import { recipeRuntimeDir } from "./paths.js";
|
|
6
|
-
function acquireCheckoutLock(target, operation) {
|
|
7
|
-
const repoRoot = fs.realpathSync(target);
|
|
8
|
-
const lockPath = path.join(repoRoot, recipeRuntimeDir(), "sandbox.lock");
|
|
9
|
-
ensureRuntimeDirectory(path.dirname(lockPath));
|
|
10
|
-
const inheritedToken = process.env.MM_HARNESS_CHECKOUT_LOCK_TOKEN;
|
|
11
|
-
for (let attempt = 0; attempt < 4; attempt += 1) {
|
|
12
|
-
try {
|
|
13
|
-
const identity = processIdentity(process.pid);
|
|
14
|
-
if (!identity) throw new Error("Cannot identify checkout lock owner process");
|
|
15
|
-
const fd = fs.openSync(lockPath, "wx", 384);
|
|
16
|
-
fs.fchmodSync(fd, 384);
|
|
17
|
-
const token = randomUUID();
|
|
18
|
-
const owner = {
|
|
19
|
-
schemaVersion: 1,
|
|
20
|
-
pid: process.pid,
|
|
21
|
-
processStartedAt: identity,
|
|
22
|
-
operation,
|
|
23
|
-
repoRoot,
|
|
24
|
-
token,
|
|
25
|
-
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
26
|
-
};
|
|
27
|
-
fs.writeFileSync(fd, `${JSON.stringify(owner, null, 2)}
|
|
28
|
-
`);
|
|
29
|
-
fs.closeSync(fd);
|
|
30
|
-
process.env.MM_HARNESS_CHECKOUT_LOCK_TOKEN = token;
|
|
31
|
-
return {
|
|
32
|
-
path: lockPath,
|
|
33
|
-
release: () => {
|
|
34
|
-
const current = readOwner(lockPath);
|
|
35
|
-
if (current?.pid === process.pid && current.token === token && !childrenAlive(current)) {
|
|
36
|
-
fs.rmSync(lockPath, { force: true });
|
|
37
|
-
fs.rmSync(path.join(path.dirname(lockPath), "lock-members", token), { recursive: true, force: true });
|
|
38
|
-
}
|
|
39
|
-
if (process.env.MM_HARNESS_CHECKOUT_LOCK_TOKEN === token) {
|
|
40
|
-
if (inheritedToken) process.env.MM_HARNESS_CHECKOUT_LOCK_TOKEN = inheritedToken;
|
|
41
|
-
else delete process.env.MM_HARNESS_CHECKOUT_LOCK_TOKEN;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
} catch (error) {
|
|
46
|
-
if (error.code !== "EEXIST") throw error;
|
|
47
|
-
const owner = readOwner(lockPath);
|
|
48
|
-
if (inheritedToken && owner?.token === inheritedToken && owner.repoRoot === repoRoot && typeof owner.pid === "number" && ownerAlive(owner)) {
|
|
49
|
-
trackCheckoutChild(repoRoot, process.pid);
|
|
50
|
-
return { path: lockPath, release: () => void 0 };
|
|
51
|
-
}
|
|
52
|
-
if (owner && typeof owner.pid === "number" && ownerAlive(owner)) {
|
|
53
|
-
return {
|
|
54
|
-
path: lockPath,
|
|
55
|
-
owner,
|
|
56
|
-
message: `checkout sandbox is busy with ${String(owner.operation ?? "another operation")} (pid ${owner.pid}).`
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
if (!owner || childrenAlive(owner)) {
|
|
60
|
-
return { path: lockPath, owner, message: `checkout sandbox owner is unreadable or has surviving child processes. Inspect ${lockPath} and its lock-members directory; remove the lock only after verifying all recorded processes have exited.` };
|
|
61
|
-
}
|
|
62
|
-
claimStaleLock(lockPath, owner);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
return { path: lockPath, owner: readOwner(lockPath), message: "checkout sandbox lock could not be acquired." };
|
|
66
|
-
}
|
|
67
|
-
function claimStaleLock(lockPath, expectedOwner) {
|
|
68
|
-
const claimedPath = `${lockPath}.stale-${process.pid}-${randomUUID()}`;
|
|
69
|
-
try {
|
|
70
|
-
fs.renameSync(lockPath, claimedPath);
|
|
71
|
-
} catch (error) {
|
|
72
|
-
if (error.code === "ENOENT") return;
|
|
73
|
-
throw error;
|
|
74
|
-
}
|
|
75
|
-
try {
|
|
76
|
-
const claimedOwner = readOwner(claimedPath);
|
|
77
|
-
if (!sameOwner(claimedOwner, expectedOwner) || claimedOwner && typeof claimedOwner.pid === "number" && (ownerAlive(claimedOwner) || childrenAlive(claimedOwner))) {
|
|
78
|
-
try {
|
|
79
|
-
fs.linkSync(claimedPath, lockPath);
|
|
80
|
-
} catch (error) {
|
|
81
|
-
if (error.code !== "EEXIST") throw error;
|
|
82
|
-
}
|
|
83
|
-
} else if (typeof claimedOwner?.token === "string" && /^[a-f0-9-]{36}$/.test(claimedOwner.token)) {
|
|
84
|
-
fs.rmSync(path.join(path.dirname(lockPath), "lock-members", claimedOwner.token), { recursive: true, force: true });
|
|
85
|
-
}
|
|
86
|
-
} finally {
|
|
87
|
-
fs.rmSync(claimedPath, { force: true });
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
function sameOwner(left, right) {
|
|
91
|
-
if (!left || !right) return left === right;
|
|
92
|
-
return left.pid === right.pid && left.token === right.token && left.startedAt === right.startedAt && left.repoRoot === right.repoRoot;
|
|
93
|
-
}
|
|
94
|
-
function readOwner(lockPath) {
|
|
95
|
-
try {
|
|
96
|
-
const value = JSON.parse(fs.readFileSync(lockPath, "utf8"));
|
|
97
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
98
|
-
} catch {
|
|
99
|
-
return null;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
function ownerAlive(owner) {
|
|
103
|
-
if (typeof owner.pid !== "number") return true;
|
|
104
|
-
if (typeof owner.processStartedAt === "string") return processIdentity(owner.pid) === owner.processStartedAt;
|
|
105
|
-
return processAlive(owner.pid);
|
|
106
|
-
}
|
|
107
|
-
function childrenAlive(owner) {
|
|
108
|
-
if (typeof owner.repoRoot !== "string" || typeof owner.token !== "string" || !/^[a-f0-9-]{36}$/.test(owner.token)) return false;
|
|
109
|
-
const directory = path.join(owner.repoRoot, recipeRuntimeDir(), "lock-members", owner.token);
|
|
110
|
-
if (!fs.existsSync(directory)) return false;
|
|
111
|
-
return fs.readdirSync(directory).filter((name) => name.endsWith(".json")).some((name) => {
|
|
112
|
-
const child = readOwner(path.join(directory, name));
|
|
113
|
-
if (child && typeof child.processGroupId === "number" && child.processGroupId > 0) return processAlive(-child.processGroupId);
|
|
114
|
-
return !child || typeof child.pid !== "number" || typeof child.processStartedAt !== "string" || child.pid !== owner.pid && processIdentity(child.pid) === child.processStartedAt;
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
function trackCheckoutChild(target, pid, ownsProcessGroup = false) {
|
|
118
|
-
const repoRoot = fs.realpathSync(target);
|
|
119
|
-
const lockPath = path.join(repoRoot, recipeRuntimeDir(), "sandbox.lock");
|
|
120
|
-
const owner = readOwner(lockPath);
|
|
121
|
-
if (!owner || owner.token !== process.env.MM_HARNESS_CHECKOUT_LOCK_TOKEN || typeof owner.token !== "string" || !/^[a-f0-9-]{36}$/.test(owner.token)) return () => void 0;
|
|
122
|
-
const identity = processIdentity(pid);
|
|
123
|
-
if (!identity && !ownsProcessGroup) return () => void 0;
|
|
124
|
-
const directory = path.join(repoRoot, recipeRuntimeDir(), "lock-members", owner.token);
|
|
125
|
-
ensureRuntimeDirectory(directory);
|
|
126
|
-
const file = path.join(directory, `${pid}.json`);
|
|
127
|
-
const previous = readOwner(file);
|
|
128
|
-
const group = ownsProcessGroup || previous?.processStartedAt === identity && previous?.processGroupId === pid;
|
|
129
|
-
const temporary = `${file}.${process.pid}.${randomUUID()}.tmp`;
|
|
130
|
-
fs.writeFileSync(temporary, JSON.stringify({ pid, processStartedAt: identity, ...group ? { processGroupId: pid } : {} }), { mode: 384 });
|
|
131
|
-
fs.chmodSync(temporary, 384);
|
|
132
|
-
fs.renameSync(temporary, file);
|
|
133
|
-
return () => {
|
|
134
|
-
const current = readOwner(file);
|
|
135
|
-
if (current?.pid === pid && current.processStartedAt === identity) fs.rmSync(file, { force: true });
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
function processAlive(pid) {
|
|
139
|
-
try {
|
|
140
|
-
process.kill(pid, 0);
|
|
141
|
-
return true;
|
|
142
|
-
} catch (error) {
|
|
143
|
-
return error.code === "EPERM";
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
export {
|
|
147
|
-
acquireCheckoutLock,
|
|
148
|
-
trackCheckoutChild
|
|
149
|
-
};
|
package/dist/cli-color.js
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
const STYLES = {
|
|
2
|
-
reset: "\x1B[0m",
|
|
3
|
-
bold: "\x1B[1m",
|
|
4
|
-
dim: "\x1B[2m",
|
|
5
|
-
label: "\x1B[1;36m",
|
|
6
|
-
cmd: "\x1B[1m",
|
|
7
|
-
ok: "\x1B[1;32m",
|
|
8
|
-
warn: "\x1B[1;33m",
|
|
9
|
-
err: "\x1B[1;31m",
|
|
10
|
-
info: "\x1B[0;37m",
|
|
11
|
-
accent: "\x1B[1;35m",
|
|
12
|
-
path: "\x1B[2m",
|
|
13
|
-
comment: "\x1B[2m",
|
|
14
|
-
active: "\x1B[1m"
|
|
15
|
-
};
|
|
16
|
-
function colorEnabled(stream = process.stderr) {
|
|
17
|
-
if (process.env.NO_COLOR != null && process.env.NO_COLOR !== "" && process.env.NO_COLOR !== "0") {
|
|
18
|
-
return false;
|
|
19
|
-
}
|
|
20
|
-
if (process.env.RECIPE_NO_COLOR === "1") return false;
|
|
21
|
-
if (process.env.RECIPE_COLOR === "1" || process.env.FORCE_COLOR === "1") return true;
|
|
22
|
-
return Boolean(stream?.isTTY);
|
|
23
|
-
}
|
|
24
|
-
function stripAnsi(text) {
|
|
25
|
-
return String(text).replace(/\x1b\[[0-9;]*m/gu, "");
|
|
26
|
-
}
|
|
27
|
-
function color(style, text, { stream = process.stderr } = {}) {
|
|
28
|
-
const value = String(text);
|
|
29
|
-
if (!colorEnabled(stream)) return value;
|
|
30
|
-
const code = STYLES[style];
|
|
31
|
-
if (!code) return value;
|
|
32
|
-
return `${code}${value}${STYLES.reset}`;
|
|
33
|
-
}
|
|
34
|
-
function colorKv(label, value, valueStyle = "info", { stream = process.stderr } = {}) {
|
|
35
|
-
return `${color("label", `${label}:`, { stream })} ${color(valueStyle, value, { stream })}`;
|
|
36
|
-
}
|
|
37
|
-
function colorStatusWord(word, { stream = process.stderr } = {}) {
|
|
38
|
-
const normalized = String(word || "").toLowerCase();
|
|
39
|
-
if (["up", "ready", "yes", "pass", "running", "present", "healthy"].includes(normalized)) {
|
|
40
|
-
return color("ok", word, { stream });
|
|
41
|
-
}
|
|
42
|
-
if (["down", "stopped", "no", "fail", "failed", "missing", "blocked", "unknown"].includes(normalized)) {
|
|
43
|
-
return color("err", word, { stream });
|
|
44
|
-
}
|
|
45
|
-
if (["compiling", "bundling", "starting", "waiting", "warn"].includes(normalized)) {
|
|
46
|
-
return color("warn", word, { stream });
|
|
47
|
-
}
|
|
48
|
-
return color("info", word, { stream });
|
|
49
|
-
}
|
|
50
|
-
function colorHumanMessage(message, { stream = process.stderr } = {}) {
|
|
51
|
-
return String(message).split("\n").map((line) => {
|
|
52
|
-
const marker = /^(\s*)(→|✓|✗)\s?(.*)$/u.exec(line);
|
|
53
|
-
if (marker) {
|
|
54
|
-
const style = marker[2] === "\u2713" ? "ok" : marker[2] === "\u2717" ? "err" : "accent";
|
|
55
|
-
return `${marker[1]}${color(style, marker[2], { stream })}${marker[3] ? ` ${marker[3]}` : ""}`;
|
|
56
|
-
}
|
|
57
|
-
const next = /^(\s*)Next:\s*(.*)$/u.exec(line);
|
|
58
|
-
if (next) {
|
|
59
|
-
return `${next[1]}${color("label", "Next:", { stream })}${next[2] ? ` ${color("cmd", next[2], { stream })}` : ""}`;
|
|
60
|
-
}
|
|
61
|
-
const warning = /^(\s*)(WARN(?:ING)?)(:?)[ \t]*(.*)$/iu.exec(line);
|
|
62
|
-
if (warning) {
|
|
63
|
-
return `${warning[1]}${color("warn", `${warning[2]}${warning[3]}`, { stream })}${warning[4] ? ` ${warning[4]}` : ""}`;
|
|
64
|
-
}
|
|
65
|
-
return line;
|
|
66
|
-
}).join("\n");
|
|
67
|
-
}
|
|
68
|
-
function classifyLogEvent(event) {
|
|
69
|
-
const text = String(event || "");
|
|
70
|
-
if (/Module build failed|^ERROR in |BUILD FAILED|compiled with [1-9][0-9]* error/iu.test(text)) {
|
|
71
|
-
return "err";
|
|
72
|
-
}
|
|
73
|
-
if (/compiled successfully|compiled with [0-9]+ warning|PASS runtime-launch|launch pass|verify pass|build complete/iu.test(text)) {
|
|
74
|
-
return "ok";
|
|
75
|
-
}
|
|
76
|
-
if (/phase [0-9]\/[0-9]|prepare pipeline|snapshot|fixture:|dist-freshness|build-health/iu.test(text)) {
|
|
77
|
-
return "label";
|
|
78
|
-
}
|
|
79
|
-
if (/webpack [0-9]+%|Bundl(ed|ing)|(iOS|Android).*%/iu.test(text)) {
|
|
80
|
-
return "warn";
|
|
81
|
-
}
|
|
82
|
-
return "info";
|
|
83
|
-
}
|
|
84
|
-
function colorLogEvent(event, { latest = false, stream = process.stderr } = {}) {
|
|
85
|
-
const style = classifyLogEvent(event);
|
|
86
|
-
const painted = color(style === "label" ? "label" : style, event, { stream });
|
|
87
|
-
return latest ? color("active", painted, { stream }) : painted;
|
|
88
|
-
}
|
|
89
|
-
export {
|
|
90
|
-
classifyLogEvent,
|
|
91
|
-
color,
|
|
92
|
-
colorEnabled,
|
|
93
|
-
colorHumanMessage,
|
|
94
|
-
colorKv,
|
|
95
|
-
colorLogEvent,
|
|
96
|
-
colorStatusWord,
|
|
97
|
-
stripAnsi
|
|
98
|
-
};
|
package/dist/command-journal.js
DELETED
|
@@ -1,308 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { OperationRecord, readOperations, processIdentity, ensureRuntimeDirectory } from "@farmslot/recipe-runner/runtime/operation";
|
|
5
|
-
import { latestTaskDir } from "@farmslot/agent-runtime";
|
|
6
|
-
import { recipeRuntimeDir } from "./paths.js";
|
|
7
|
-
const COMMAND_JOURNAL_FILE = "last-command.json";
|
|
8
|
-
const JOURNALED_COMMANDS = /* @__PURE__ */ new Set([
|
|
9
|
-
"prepare",
|
|
10
|
-
"call",
|
|
11
|
-
"check",
|
|
12
|
-
"checklist",
|
|
13
|
-
"cleanup",
|
|
14
|
-
"doctor",
|
|
15
|
-
"fixtures",
|
|
16
|
-
"install",
|
|
17
|
-
"launch",
|
|
18
|
-
"runtime-launch",
|
|
19
|
-
"provision",
|
|
20
|
-
"recipe-quality",
|
|
21
|
-
"run",
|
|
22
|
-
"stop",
|
|
23
|
-
"verify"
|
|
24
|
-
]);
|
|
25
|
-
const SENSITIVE_KEY = /(?:api[-_]?key|auth|credential|mnemonic|pass(?:word)?|private[-_]?key|secret|seed|srp|token|vault)/iu;
|
|
26
|
-
const EVIDENCE_OPTIONS = /* @__PURE__ */ new Set(["--artifacts-dir", "--out", "--output"]);
|
|
27
|
-
let activeCommandJournal;
|
|
28
|
-
async function withCommandJournal(command, argv, execute) {
|
|
29
|
-
if (!JOURNALED_COMMANDS.has(command)) return execute();
|
|
30
|
-
const handle = tryBeginCommandJournal(command, argv);
|
|
31
|
-
const previous = activeCommandJournal;
|
|
32
|
-
const parentId = process.env.MM_HARNESS_OPERATION_ID;
|
|
33
|
-
if (handle) process.env.MM_HARNESS_OPERATION_ID = handle.operation.value.id;
|
|
34
|
-
activeCommandJournal = handle;
|
|
35
|
-
try {
|
|
36
|
-
const exitCode = await execute();
|
|
37
|
-
tryFinishCommandJournal(handle, exitCode);
|
|
38
|
-
return exitCode;
|
|
39
|
-
} catch (error) {
|
|
40
|
-
tryFinishCommandJournal(handle, 1);
|
|
41
|
-
throw error;
|
|
42
|
-
} finally {
|
|
43
|
-
activeCommandJournal = previous;
|
|
44
|
-
if (parentId) process.env.MM_HARNESS_OPERATION_ID = parentId;
|
|
45
|
-
else delete process.env.MM_HARNESS_OPERATION_ID;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
function recordCommandStage(stage) {
|
|
49
|
-
activeCommandJournal?.operation.stage(stage);
|
|
50
|
-
}
|
|
51
|
-
function recordCommandOutput(chunk) {
|
|
52
|
-
activeCommandJournal?.operation.output(chunk);
|
|
53
|
-
}
|
|
54
|
-
function recordCommandEvidence(...evidence) {
|
|
55
|
-
const handle = activeCommandJournal;
|
|
56
|
-
if (!handle || evidence.length === 0) return;
|
|
57
|
-
try {
|
|
58
|
-
const paths = new Set(handle.record.evidencePaths);
|
|
59
|
-
for (const entry of evidence) paths.add(path.resolve(entry));
|
|
60
|
-
handle.record = { ...handle.record, evidencePaths: [...paths] };
|
|
61
|
-
writeAtomic(handle.file, handle.record);
|
|
62
|
-
} catch (error) {
|
|
63
|
-
process.stderr.write(
|
|
64
|
-
`mm-harness: resumability journal could not record evidence: ${errorMessage(error)}
|
|
65
|
-
`
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
function commandJournalPath(target, runtimeDir) {
|
|
70
|
-
const relative = runtimeDir ?? recipeRuntimeDir();
|
|
71
|
-
assertRelativeRuntimeDir(relative);
|
|
72
|
-
return path.join(path.resolve(target), relative, COMMAND_JOURNAL_FILE);
|
|
73
|
-
}
|
|
74
|
-
function readCommandJournal(target, runtimeDir) {
|
|
75
|
-
const legacyFile = commandJournalPath(target, runtimeDir);
|
|
76
|
-
const operations = readOperations(path.join(path.dirname(legacyFile), "operations"));
|
|
77
|
-
const live = operations.filter((operation) => operation.status === "running" && processIdentity(operation.pid) === operation.processStartedAt);
|
|
78
|
-
const roots = operations.filter((operation) => !operation.parentId).sort((a, b) => (a.finishedAt ?? a.startedAt).localeCompare(b.finishedAt ?? b.startedAt));
|
|
79
|
-
const selected = live.find((operation) => !operation.parentId) ?? live.at(-1) ?? roots.at(-1) ?? operations.at(-1);
|
|
80
|
-
const file = selected ? path.join(path.dirname(legacyFile), "operations", `${selected.id}.journal.json`) : legacyFile;
|
|
81
|
-
let descriptor;
|
|
82
|
-
try {
|
|
83
|
-
const noFollow = typeof fs.constants.O_NOFOLLOW === "number" ? fs.constants.O_NOFOLLOW : 0;
|
|
84
|
-
descriptor = fs.openSync(file, fs.constants.O_RDONLY | noFollow);
|
|
85
|
-
const descriptorStat = fs.fstatSync(descriptor, { bigint: true });
|
|
86
|
-
const pathStat = fs.lstatSync(file, { bigint: true });
|
|
87
|
-
if (!descriptorStat.isFile() || !pathStat.isFile() || pathStat.isSymbolicLink() || pathStat.dev === 0n && pathStat.ino === 0n || descriptorStat.dev !== pathStat.dev || descriptorStat.ino !== pathStat.ino) {
|
|
88
|
-
return { file, record: null };
|
|
89
|
-
}
|
|
90
|
-
const parsed = JSON.parse(
|
|
91
|
-
fs.readFileSync(descriptor, "utf8")
|
|
92
|
-
);
|
|
93
|
-
return isCommandJournalRecord(parsed) ? { file, record: parsed } : { file, record: null };
|
|
94
|
-
} catch {
|
|
95
|
-
return { file, record: null };
|
|
96
|
-
} finally {
|
|
97
|
-
if (descriptor !== void 0) fs.closeSync(descriptor);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
function redactCommandArgs(argv) {
|
|
101
|
-
const redacted = [];
|
|
102
|
-
for (let index = 0; index < argv.length; index += 1) {
|
|
103
|
-
const token = argv[index] ?? "";
|
|
104
|
-
const equals = token.indexOf("=");
|
|
105
|
-
if (token.startsWith("--") && equals !== -1) {
|
|
106
|
-
const option = token.slice(0, equals);
|
|
107
|
-
const value = token.slice(equals + 1);
|
|
108
|
-
redacted.push(
|
|
109
|
-
`${option}=${option === "--arg" ? redactAssignment(value) : SENSITIVE_KEY.test(option) ? "<redacted>" : redactUrl(value)}`
|
|
110
|
-
);
|
|
111
|
-
continue;
|
|
112
|
-
}
|
|
113
|
-
if (token.startsWith("--") && SENSITIVE_KEY.test(token)) {
|
|
114
|
-
redacted.push(token);
|
|
115
|
-
const value = argv[index + 1];
|
|
116
|
-
if (value !== void 0 && !value.startsWith("--")) {
|
|
117
|
-
redacted.push("<redacted>");
|
|
118
|
-
index += 1;
|
|
119
|
-
}
|
|
120
|
-
continue;
|
|
121
|
-
}
|
|
122
|
-
if (token === "--arg") {
|
|
123
|
-
redacted.push(token);
|
|
124
|
-
const value = argv[index + 1];
|
|
125
|
-
if (value !== void 0) {
|
|
126
|
-
redacted.push(redactAssignment(value));
|
|
127
|
-
index += 1;
|
|
128
|
-
}
|
|
129
|
-
continue;
|
|
130
|
-
}
|
|
131
|
-
redacted.push(redactAssignment(redactUrl(token)));
|
|
132
|
-
}
|
|
133
|
-
return redacted;
|
|
134
|
-
}
|
|
135
|
-
function pruneCompletedJournals(directory, operations) {
|
|
136
|
-
const completed = operations.filter((operation) => operation.status !== "running");
|
|
137
|
-
for (const operation of completed.slice(0, -100)) {
|
|
138
|
-
for (const suffix of [".json", ".journal.json", ".log"]) {
|
|
139
|
-
fs.rmSync(path.join(directory, `${operation.id}${suffix}`), { force: true });
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
function tryBeginCommandJournal(command, argv) {
|
|
144
|
-
try {
|
|
145
|
-
const target = invocationTarget(argv);
|
|
146
|
-
if (!fs.existsSync(target)) return void 0;
|
|
147
|
-
const runtimeDir = optionValue(argv, "--runtime-dir");
|
|
148
|
-
const legacyFile = commandJournalPath(target, runtimeDir);
|
|
149
|
-
const taskDir = latestTaskDir(path.join(target, "temp", "tasks"));
|
|
150
|
-
const signalFile = taskDir && path.join(taskDir, "SIGNAL.json");
|
|
151
|
-
const liveTask = signalFile && fs.existsSync(signalFile) && JSON.parse(fs.readFileSync(signalFile, "utf8")).status === "running";
|
|
152
|
-
const directory = path.join(path.dirname(legacyFile), "operations");
|
|
153
|
-
const operations = readOperations(directory, (error) => process.stderr.write(`mm-harness: ignoring invalid prior operation: ${error.message}
|
|
154
|
-
`));
|
|
155
|
-
pruneCompletedJournals(directory, operations);
|
|
156
|
-
const inherited = process.env.MM_HARNESS_OPERATION_ID;
|
|
157
|
-
const parent = inherited && operations.find((operation2) => operation2.id === inherited && operation2.target === target && operation2.status === "running" && processIdentity(operation2.pid) === operation2.processStartedAt);
|
|
158
|
-
const operation = new OperationRecord(directory, command, target, {
|
|
159
|
-
...parent ? { parentId: parent.id } : {},
|
|
160
|
-
...liveTask ? { mirrorDirectory: path.join(taskDir, "artifacts", "operations") } : {}
|
|
161
|
-
});
|
|
162
|
-
const file = path.join(directory, `${operation.value.id}.journal.json`);
|
|
163
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
164
|
-
const record = {
|
|
165
|
-
schemaVersion: 1,
|
|
166
|
-
command,
|
|
167
|
-
args: redactCommandArgs(argv.slice(1)),
|
|
168
|
-
target,
|
|
169
|
-
verdict: "running",
|
|
170
|
-
exitCode: null,
|
|
171
|
-
evidencePaths: evidencePaths(argv),
|
|
172
|
-
startedAt: now,
|
|
173
|
-
finishedAt: null
|
|
174
|
-
};
|
|
175
|
-
writeAtomic(file, record);
|
|
176
|
-
return { file, record, operation };
|
|
177
|
-
} catch (error) {
|
|
178
|
-
process.stderr.write(
|
|
179
|
-
`mm-harness: resumability journal unavailable: ${errorMessage(error)}
|
|
180
|
-
`
|
|
181
|
-
);
|
|
182
|
-
return void 0;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
function tryFinishCommandJournal(handle, exitCode) {
|
|
186
|
-
if (!handle) return;
|
|
187
|
-
try {
|
|
188
|
-
writeAtomic(handle.file, {
|
|
189
|
-
...handle.record,
|
|
190
|
-
verdict: exitCode === 0 ? "pass" : "fail",
|
|
191
|
-
exitCode,
|
|
192
|
-
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
193
|
-
});
|
|
194
|
-
} catch (error) {
|
|
195
|
-
process.stderr.write(
|
|
196
|
-
`mm-harness: resumability journal could not finalize: ${errorMessage(error)}
|
|
197
|
-
`
|
|
198
|
-
);
|
|
199
|
-
} finally {
|
|
200
|
-
handle.operation.finish(exitCode);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
function writeAtomic(file, record) {
|
|
204
|
-
ensureRuntimeDirectory(path.dirname(file));
|
|
205
|
-
const temporary = `${file}.${process.pid}.${randomUUID()}.tmp`;
|
|
206
|
-
let descriptor;
|
|
207
|
-
try {
|
|
208
|
-
descriptor = fs.openSync(temporary, "wx", 384);
|
|
209
|
-
fs.fchmodSync(descriptor, 384);
|
|
210
|
-
fs.writeFileSync(descriptor, `${JSON.stringify(record, null, 2)}
|
|
211
|
-
`);
|
|
212
|
-
fs.fsyncSync(descriptor);
|
|
213
|
-
fs.closeSync(descriptor);
|
|
214
|
-
descriptor = void 0;
|
|
215
|
-
fs.renameSync(temporary, file);
|
|
216
|
-
} finally {
|
|
217
|
-
if (descriptor !== void 0) fs.closeSync(descriptor);
|
|
218
|
-
fs.rmSync(temporary, { force: true });
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
function invocationTarget(argv) {
|
|
222
|
-
const target = path.resolve(optionValue(argv, "--target") ?? process.cwd());
|
|
223
|
-
return fs.existsSync(target) ? fs.realpathSync(target) : target;
|
|
224
|
-
}
|
|
225
|
-
function evidencePaths(argv) {
|
|
226
|
-
const values = /* @__PURE__ */ new Set();
|
|
227
|
-
for (const option of EVIDENCE_OPTIONS) {
|
|
228
|
-
const value = optionValue(argv, option);
|
|
229
|
-
if (value) values.add(path.resolve(value));
|
|
230
|
-
}
|
|
231
|
-
return [...values];
|
|
232
|
-
}
|
|
233
|
-
function optionValue(argv, option) {
|
|
234
|
-
const divider = argv.indexOf("--");
|
|
235
|
-
const end = divider === -1 ? argv.length : divider;
|
|
236
|
-
for (let index = 1; index < end; index += 1) {
|
|
237
|
-
const token = argv[index] ?? "";
|
|
238
|
-
if (token === option) {
|
|
239
|
-
const value = argv[index + 1];
|
|
240
|
-
return value && !value.startsWith("--") ? value : void 0;
|
|
241
|
-
}
|
|
242
|
-
if (token.startsWith(`${option}=`)) return token.slice(option.length + 1);
|
|
243
|
-
}
|
|
244
|
-
return void 0;
|
|
245
|
-
}
|
|
246
|
-
function redactAssignment(token) {
|
|
247
|
-
const equals = token.indexOf("=");
|
|
248
|
-
if (equals <= 0) return redactValue(token);
|
|
249
|
-
const key = token.slice(0, equals);
|
|
250
|
-
const value = token.slice(equals + 1);
|
|
251
|
-
return `${key}=${SENSITIVE_KEY.test(key) ? "<redacted>" : redactValue(value)}`;
|
|
252
|
-
}
|
|
253
|
-
function redactUrl(token) {
|
|
254
|
-
return token.replace(/(\w+:\/\/)[^/@\s:]+:[^/@\s]+@/gu, "$1<redacted>@");
|
|
255
|
-
}
|
|
256
|
-
function redactValue(value) {
|
|
257
|
-
try {
|
|
258
|
-
return JSON.stringify(redactStructuredValue(JSON.parse(value)));
|
|
259
|
-
} catch {
|
|
260
|
-
return value.replace(
|
|
261
|
-
/((?:api[-_]?key|auth|credential|mnemonic|pass(?:word)?|private[-_]?key|secret|seed|srp|token|vault)=)[^\s]+/giu,
|
|
262
|
-
"$1<redacted>"
|
|
263
|
-
);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
function isSensitiveKey(key) {
|
|
267
|
-
return SENSITIVE_KEY.test(key);
|
|
268
|
-
}
|
|
269
|
-
function redactStructuredValue(value) {
|
|
270
|
-
if (Array.isArray(value)) return value.map(redactStructuredValue);
|
|
271
|
-
if (!value || typeof value !== "object") return value;
|
|
272
|
-
return Object.fromEntries(
|
|
273
|
-
Object.entries(value).map(([key, entry]) => [
|
|
274
|
-
key,
|
|
275
|
-
isSensitiveKey(key) ? "<redacted>" : redactStructuredValue(entry)
|
|
276
|
-
])
|
|
277
|
-
);
|
|
278
|
-
}
|
|
279
|
-
function assertRelativeRuntimeDir(value) {
|
|
280
|
-
if (!value || path.isAbsolute(value) || !/^[A-Za-z0-9._/-]+$/u.test(value)) {
|
|
281
|
-
throw new Error(`--runtime-dir must be a safe relative path: ${value}`);
|
|
282
|
-
}
|
|
283
|
-
if (value.split("/").some((part) => !part || part === "." || part === "..")) {
|
|
284
|
-
throw new Error(
|
|
285
|
-
`--runtime-dir contains an unsafe path component: ${value}`
|
|
286
|
-
);
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
function isCommandJournalRecord(value) {
|
|
290
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
291
|
-
const record = value;
|
|
292
|
-
return record.schemaVersion === 1 && typeof record.command === "string" && Array.isArray(record.args) && record.args.every((entry) => typeof entry === "string") && typeof record.target === "string" && (record.verdict === "running" || record.verdict === "pass" || record.verdict === "fail") && (record.exitCode === null || typeof record.exitCode === "number") && Array.isArray(record.evidencePaths) && record.evidencePaths.every((entry) => typeof entry === "string") && typeof record.startedAt === "string" && (record.finishedAt === null || typeof record.finishedAt === "string");
|
|
293
|
-
}
|
|
294
|
-
function errorMessage(error) {
|
|
295
|
-
return error instanceof Error ? error.message : String(error);
|
|
296
|
-
}
|
|
297
|
-
export {
|
|
298
|
-
COMMAND_JOURNAL_FILE,
|
|
299
|
-
commandJournalPath,
|
|
300
|
-
isSensitiveKey,
|
|
301
|
-
readCommandJournal,
|
|
302
|
-
recordCommandEvidence,
|
|
303
|
-
recordCommandOutput,
|
|
304
|
-
recordCommandStage,
|
|
305
|
-
redactCommandArgs,
|
|
306
|
-
redactStructuredValue,
|
|
307
|
-
withCommandJournal
|
|
308
|
-
};
|