@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/devices.js
DELETED
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { execFileSync } from "node:child_process";
|
|
2
|
-
import { resolveMobileToolPath } from "../library/actions/mobile/platform/tool-paths.mjs";
|
|
3
|
-
const IOS_DEVICE_DISCOVERY_TIMEOUT_MS = 3e4;
|
|
4
|
-
function reportProbeFailure(errors, platform, error) {
|
|
5
|
-
const code = error?.code;
|
|
6
|
-
if (code === "ENOENT") return;
|
|
7
|
-
const timeoutSeconds = platform === "ios" ? IOS_DEVICE_DISCOVERY_TIMEOUT_MS / 1e3 : 5;
|
|
8
|
-
const reason = code === "ETIMEDOUT" ? `timed out after ${timeoutSeconds} seconds` : "failed";
|
|
9
|
-
const probeCode = code ?? error?.status;
|
|
10
|
-
errors.push({
|
|
11
|
-
platform,
|
|
12
|
-
message: `${platform} device discovery ${reason}; device availability is unknown.`,
|
|
13
|
-
userAction: platform === "ios" ? "xcrun simctl list devices booted -j" : "adb devices -l",
|
|
14
|
-
...probeCode !== void 0 ? { probeCode } : {}
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
function listConnectedDevices(platform, errors = []) {
|
|
18
|
-
const devices = [];
|
|
19
|
-
if (!platform || platform === "android") devices.push(...listAndroidDevices(errors));
|
|
20
|
-
if (!platform || platform === "ios") devices.push(...listIosSimulators("booted", errors));
|
|
21
|
-
return devices;
|
|
22
|
-
}
|
|
23
|
-
function findAvailableIosSimulatorById(id, errors = []) {
|
|
24
|
-
for (const simulator of listIosSimulators("available", errors)) {
|
|
25
|
-
if (simulator.id === id) return simulator;
|
|
26
|
-
}
|
|
27
|
-
return void 0;
|
|
28
|
-
}
|
|
29
|
-
function listAndroidDevices(errors) {
|
|
30
|
-
const adbPath = resolveMobileToolPath("adb");
|
|
31
|
-
if (!adbPath) return [];
|
|
32
|
-
let output;
|
|
33
|
-
try {
|
|
34
|
-
output = execFileSync(adbPath, ["devices", "-l"], {
|
|
35
|
-
encoding: "utf8",
|
|
36
|
-
timeout: 5e3,
|
|
37
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
38
|
-
});
|
|
39
|
-
} catch (error) {
|
|
40
|
-
reportProbeFailure(errors, "android", error);
|
|
41
|
-
return [];
|
|
42
|
-
}
|
|
43
|
-
if (!output.trimStart().startsWith("List of devices attached")) {
|
|
44
|
-
errors.push({ platform: "android", message: "Android device discovery returned an invalid response; device availability is unknown.", userAction: "adb devices -l" });
|
|
45
|
-
return [];
|
|
46
|
-
}
|
|
47
|
-
const devices = [];
|
|
48
|
-
for (const line of output.split("\n").slice(1)) {
|
|
49
|
-
const trimmed = line.trim();
|
|
50
|
-
if (!trimmed) continue;
|
|
51
|
-
const fields = trimmed.split(/\s+/u);
|
|
52
|
-
const id = fields[0];
|
|
53
|
-
const state = fields[1] ?? "unknown";
|
|
54
|
-
if (!id) continue;
|
|
55
|
-
const modelField = fields.find((f) => f.startsWith("model:"));
|
|
56
|
-
const name = modelField ? modelField.slice("model:".length) : void 0;
|
|
57
|
-
devices.push({ platform: "android", id, state, ...name ? { name } : {} });
|
|
58
|
-
}
|
|
59
|
-
return devices;
|
|
60
|
-
}
|
|
61
|
-
function listIosSimulators(scope, errors) {
|
|
62
|
-
let output;
|
|
63
|
-
try {
|
|
64
|
-
output = execFileSync("xcrun", ["simctl", "list", "devices", scope, "-j"], {
|
|
65
|
-
encoding: "utf8",
|
|
66
|
-
timeout: IOS_DEVICE_DISCOVERY_TIMEOUT_MS,
|
|
67
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
68
|
-
});
|
|
69
|
-
} catch (error) {
|
|
70
|
-
reportProbeFailure(errors, "ios", error);
|
|
71
|
-
return [];
|
|
72
|
-
}
|
|
73
|
-
let parsed;
|
|
74
|
-
try {
|
|
75
|
-
parsed = JSON.parse(output);
|
|
76
|
-
if (!parsed?.devices || typeof parsed.devices !== "object" || Array.isArray(parsed.devices) || Object.values(parsed.devices).some((devices2) => !Array.isArray(devices2) || devices2.some((device) => !device || typeof device.udid !== "string"))) {
|
|
77
|
-
throw new Error("Invalid simctl device inventory");
|
|
78
|
-
}
|
|
79
|
-
} catch {
|
|
80
|
-
errors.push({ platform: "ios", message: "iOS device discovery returned an invalid response; device availability is unknown.", userAction: `xcrun simctl list devices ${scope} -j` });
|
|
81
|
-
return [];
|
|
82
|
-
}
|
|
83
|
-
const devices = [];
|
|
84
|
-
for (const runtimeDevices of Object.values(parsed.devices ?? {})) {
|
|
85
|
-
for (const device of runtimeDevices) {
|
|
86
|
-
if (!device.udid) continue;
|
|
87
|
-
devices.push({
|
|
88
|
-
platform: "ios",
|
|
89
|
-
id: device.udid,
|
|
90
|
-
state: device.state ?? "unknown",
|
|
91
|
-
...device.name ? { name: device.name } : {}
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
return devices;
|
|
96
|
-
}
|
|
97
|
-
export {
|
|
98
|
-
findAvailableIosSimulatorById,
|
|
99
|
-
listConnectedDevices
|
|
100
|
-
};
|
|
@@ -1,332 +0,0 @@
|
|
|
1
|
-
import { execFileSync } from "node:child_process";
|
|
2
|
-
import { createHash } from "node:crypto";
|
|
3
|
-
import fs, { constants as fsConstants } from "node:fs";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
import { getAdapterSurface } from "./adapters/surface.js";
|
|
6
|
-
import { indexArtifactManifest } from "./artifact-files.js";
|
|
7
|
-
import { importRecipeProtocol, runnerDir } from "./paths.js";
|
|
8
|
-
const gitContexts = /* @__PURE__ */ new Map();
|
|
9
|
-
let protocolPromise;
|
|
10
|
-
class ProvenanceDriftError extends Error {
|
|
11
|
-
code = "PROVENANCE_DRIFT";
|
|
12
|
-
exitCode = 5;
|
|
13
|
-
userAction;
|
|
14
|
-
provenancePath;
|
|
15
|
-
drift;
|
|
16
|
-
constructor(provenancePath, drift, cause) {
|
|
17
|
-
const fields = [...new Set(drift.map((entry) => entry.field))].join(", ");
|
|
18
|
-
super(
|
|
19
|
-
`Execution inputs changed after preparation (${fields}); evidence is invalid.`,
|
|
20
|
-
cause === void 0 ? void 0 : { cause }
|
|
21
|
-
);
|
|
22
|
-
this.name = "ProvenanceDriftError";
|
|
23
|
-
this.provenancePath = provenancePath;
|
|
24
|
-
this.drift = drift;
|
|
25
|
-
this.userAction = `restore the prepared recipe, library, and product source, then rerun; inspect ${provenancePath}`;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
async function captureExecutionProvenance(input, phase) {
|
|
29
|
-
const protocol = await (protocolPromise ??= importRecipeProtocol());
|
|
30
|
-
const recipeDigest = input.recipePath ? recipeFileDigest(protocol, input.recipePath) : protocol.digestRecipeDocument(input.recipeDocument);
|
|
31
|
-
const libraries = (input.librarySources ?? []).map((source, index) => ({
|
|
32
|
-
name: source.name ?? `library-${index + 1}`,
|
|
33
|
-
...sourceSnapshot(source.root)
|
|
34
|
-
}));
|
|
35
|
-
const runnerRoot = input.runnerRoot ?? runnerDir;
|
|
36
|
-
return {
|
|
37
|
-
phase,
|
|
38
|
-
capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
39
|
-
recipeDigest,
|
|
40
|
-
product: sourceSnapshot(
|
|
41
|
-
input.projectRoot,
|
|
42
|
-
input.adapter,
|
|
43
|
-
input.excludedProductRoots
|
|
44
|
-
),
|
|
45
|
-
runner: sourceSnapshot(runnerRoot, void 0, [], [
|
|
46
|
-
"src",
|
|
47
|
-
"adapters",
|
|
48
|
-
"library",
|
|
49
|
-
"bin",
|
|
50
|
-
"dist",
|
|
51
|
-
"package.json"
|
|
52
|
-
]),
|
|
53
|
-
libraries,
|
|
54
|
-
helpers: (input.helperPaths ?? []).map((helperPath) => ({
|
|
55
|
-
path: displayPath(input.projectRoot, helperPath),
|
|
56
|
-
sourceFingerprint: fileFingerprint(helperPath)
|
|
57
|
-
}))
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
function executionProvenanceDrift(start, current) {
|
|
61
|
-
const drift = [];
|
|
62
|
-
if (current.phase === "start") return drift;
|
|
63
|
-
const phase = current.phase;
|
|
64
|
-
compare(drift, phase, "recipeDigest", start.recipeDigest, current.recipeDigest);
|
|
65
|
-
compareSource(drift, phase, "product", start.product, current.product);
|
|
66
|
-
compareSource(drift, phase, "runner", start.runner, current.runner);
|
|
67
|
-
if (start.libraries.length !== current.libraries.length) {
|
|
68
|
-
compare(
|
|
69
|
-
drift,
|
|
70
|
-
phase,
|
|
71
|
-
"libraries.length",
|
|
72
|
-
String(start.libraries.length),
|
|
73
|
-
String(current.libraries.length)
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
const count = Math.min(start.libraries.length, current.libraries.length);
|
|
77
|
-
for (let index = 0; index < count; index += 1) {
|
|
78
|
-
const expected = start.libraries[index];
|
|
79
|
-
const actual = current.libraries[index];
|
|
80
|
-
const prefix = `libraries[${index}]`;
|
|
81
|
-
compare(drift, phase, `${prefix}.name`, expected.name, actual.name);
|
|
82
|
-
compareSource(drift, phase, prefix, expected, actual);
|
|
83
|
-
}
|
|
84
|
-
if (start.helpers.length !== current.helpers.length) {
|
|
85
|
-
compare(
|
|
86
|
-
drift,
|
|
87
|
-
phase,
|
|
88
|
-
"helpers.length",
|
|
89
|
-
String(start.helpers.length),
|
|
90
|
-
String(current.helpers.length)
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
const helperCount = Math.min(start.helpers.length, current.helpers.length);
|
|
94
|
-
for (let index = 0; index < helperCount; index += 1) {
|
|
95
|
-
compare(drift, phase, `helpers[${index}].path`, start.helpers[index].path, current.helpers[index].path);
|
|
96
|
-
compare(
|
|
97
|
-
drift,
|
|
98
|
-
phase,
|
|
99
|
-
`helpers[${index}].sourceFingerprint`,
|
|
100
|
-
start.helpers[index].sourceFingerprint,
|
|
101
|
-
current.helpers[index].sourceFingerprint
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
return drift;
|
|
105
|
-
}
|
|
106
|
-
async function writeExecutionProvenance(artifactsDir, snapshots, drift, artifactManifestPath) {
|
|
107
|
-
const provenancePath = path.join(path.resolve(artifactsDir), "execution-provenance.json");
|
|
108
|
-
const record = {
|
|
109
|
-
schemaVersion: 1,
|
|
110
|
-
valid: drift.length === 0,
|
|
111
|
-
snapshots,
|
|
112
|
-
drift
|
|
113
|
-
};
|
|
114
|
-
fs.mkdirSync(path.dirname(provenancePath), { recursive: true });
|
|
115
|
-
fs.writeFileSync(provenancePath, `${JSON.stringify(record, null, 2)}
|
|
116
|
-
`);
|
|
117
|
-
if (artifactManifestPath) {
|
|
118
|
-
await indexArtifactManifest(artifactManifestPath, [
|
|
119
|
-
{
|
|
120
|
-
path: "execution-provenance.json",
|
|
121
|
-
type: "json",
|
|
122
|
-
label: "Execution provenance",
|
|
123
|
-
category: "system",
|
|
124
|
-
metadata: {
|
|
125
|
-
valid: record.valid,
|
|
126
|
-
errorCode: record.valid ? null : "PROVENANCE_DRIFT"
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
]);
|
|
130
|
-
}
|
|
131
|
-
return provenancePath;
|
|
132
|
-
}
|
|
133
|
-
function recipeFileDigest(protocol, recipePath) {
|
|
134
|
-
const source = readRegularFileNoFollow(recipePath);
|
|
135
|
-
try {
|
|
136
|
-
return protocol.digestRecipeDocument(JSON.parse(source.toString("utf8")));
|
|
137
|
-
} catch {
|
|
138
|
-
return `invalid:sha256:${createHash("sha256").update(source).digest("hex")}`;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
function sourceSnapshot(root, adapter, excludedRoots = [], includedRoots = []) {
|
|
142
|
-
const git = gitContext(root);
|
|
143
|
-
if (!git) {
|
|
144
|
-
return {
|
|
145
|
-
head: null,
|
|
146
|
-
status: "not-a-git-checkout",
|
|
147
|
-
sourceFingerprint: directoryFingerprint(root, excludedRoots, includedRoots)
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
const pathspecs = sourcePathspecs(git, excludedRoots, includedRoots);
|
|
151
|
-
const status = gitText(git.topLevel, [
|
|
152
|
-
"status",
|
|
153
|
-
"--porcelain=v1",
|
|
154
|
-
"--untracked-files=normal",
|
|
155
|
-
"--",
|
|
156
|
-
...pathspecs
|
|
157
|
-
]);
|
|
158
|
-
const head = gitBuffer(git.topLevel, ["rev-parse", "HEAD"]);
|
|
159
|
-
const platformFingerprint = adapter ? getAdapterSurface(adapter).sourceFingerprint : void 0;
|
|
160
|
-
return {
|
|
161
|
-
head: head.toString("utf8").trim() || null,
|
|
162
|
-
status,
|
|
163
|
-
sourceFingerprint: platformFingerprint && path.resolve(root) === git.topLevel ? platformFingerprint(root) : nestedGitSourceFingerprint(root, git, pathspecs, excludedRoots, includedRoots, head)
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
function nestedGitSourceFingerprint(root, git, pathspecs, excludedRoots, includedRoots, head) {
|
|
167
|
-
const gitFingerprint = gitSourceFingerprint(git.topLevel, pathspecs, head);
|
|
168
|
-
if (git.pathspec === "." && includedRoots.length === 0) return gitFingerprint;
|
|
169
|
-
return createHash("sha256").update(gitFingerprint).update("\0nested-source\0").update(directoryFingerprint(root, excludedRoots, includedRoots)).digest("hex");
|
|
170
|
-
}
|
|
171
|
-
function gitContext(root) {
|
|
172
|
-
const cacheKey = path.resolve(root);
|
|
173
|
-
if (gitContexts.has(cacheKey)) return gitContexts.get(cacheKey) ?? null;
|
|
174
|
-
try {
|
|
175
|
-
const resolved = cacheKey;
|
|
176
|
-
const topLevel = fs.realpathSync(
|
|
177
|
-
gitText(resolved, ["rev-parse", "--show-toplevel"]).trim()
|
|
178
|
-
);
|
|
179
|
-
const relative = path.relative(topLevel, fs.realpathSync(resolved));
|
|
180
|
-
const context = relative === ".." || relative.startsWith(`..${path.sep}`) ? null : { topLevel, pathspec: relative || "." };
|
|
181
|
-
gitContexts.set(cacheKey, context);
|
|
182
|
-
return context;
|
|
183
|
-
} catch {
|
|
184
|
-
gitContexts.set(cacheKey, null);
|
|
185
|
-
return null;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
function gitSourceFingerprint(topLevel, pathspecs, head) {
|
|
189
|
-
const hash = createHash("sha256");
|
|
190
|
-
hash.update(head);
|
|
191
|
-
hash.update("\0diff\0");
|
|
192
|
-
hash.update(
|
|
193
|
-
gitBuffer(topLevel, [
|
|
194
|
-
"diff",
|
|
195
|
-
"--no-ext-diff",
|
|
196
|
-
"--binary",
|
|
197
|
-
head.toString("utf8").trim(),
|
|
198
|
-
"--",
|
|
199
|
-
...pathspecs
|
|
200
|
-
])
|
|
201
|
-
);
|
|
202
|
-
const untracked = gitText(topLevel, [
|
|
203
|
-
"ls-files",
|
|
204
|
-
"--others",
|
|
205
|
-
"--exclude-standard",
|
|
206
|
-
"-z",
|
|
207
|
-
"--",
|
|
208
|
-
...pathspecs
|
|
209
|
-
]).split("\0").filter(Boolean).sort();
|
|
210
|
-
for (const relative of untracked) {
|
|
211
|
-
hash.update(`\0untracked\0${relative}\0`);
|
|
212
|
-
const absolute = path.join(topLevel, relative);
|
|
213
|
-
hash.update(readPathIdentity(absolute));
|
|
214
|
-
}
|
|
215
|
-
return hash.digest("hex");
|
|
216
|
-
}
|
|
217
|
-
function sourcePathspecs(git, excludedRoots, includedRoots) {
|
|
218
|
-
const exclusions = excludedRoots.flatMap((root) => {
|
|
219
|
-
const relative = path.relative(git.topLevel, fs.existsSync(root) ? fs.realpathSync(root) : path.resolve(root));
|
|
220
|
-
return relative !== ".." && !relative.startsWith(`..${path.sep}`) ? [`:(exclude)${relative}`] : [];
|
|
221
|
-
});
|
|
222
|
-
const inclusions = includedRoots.length > 0 ? includedRoots.map((root) => path.join(git.pathspec === "." ? "" : git.pathspec, root)) : [git.pathspec];
|
|
223
|
-
return [...inclusions, ...exclusions];
|
|
224
|
-
}
|
|
225
|
-
function directoryFingerprint(root, excludedRoots, includedRoots) {
|
|
226
|
-
const hash = createHash("sha256");
|
|
227
|
-
const resolvedRoot = path.resolve(root);
|
|
228
|
-
const roots = includedRoots.length > 0 ? includedRoots : [""];
|
|
229
|
-
for (const included of roots) {
|
|
230
|
-
hashDirectory(
|
|
231
|
-
hash,
|
|
232
|
-
resolvedRoot,
|
|
233
|
-
included,
|
|
234
|
-
new Set(excludedRoots.map((entry) => path.resolve(entry)))
|
|
235
|
-
);
|
|
236
|
-
}
|
|
237
|
-
return hash.digest("hex");
|
|
238
|
-
}
|
|
239
|
-
function hashDirectory(hash, root, relative, excludedRoots) {
|
|
240
|
-
const absolute = path.join(root, relative);
|
|
241
|
-
if ([...excludedRoots].some(
|
|
242
|
-
(excluded) => absolute === excluded || absolute.startsWith(`${excluded}${path.sep}`)
|
|
243
|
-
)) return;
|
|
244
|
-
let stat;
|
|
245
|
-
try {
|
|
246
|
-
stat = fs.lstatSync(absolute);
|
|
247
|
-
} catch (error) {
|
|
248
|
-
if (error.code === "ENOENT") {
|
|
249
|
-
hash.update(`absent\0${relative}\0`);
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
throw error;
|
|
253
|
-
}
|
|
254
|
-
if (stat.isDirectory()) {
|
|
255
|
-
hash.update(`directory\0${relative}\0`);
|
|
256
|
-
for (const name of fs.readdirSync(absolute).sort()) {
|
|
257
|
-
if (relative === "" && (name === ".git" || name === "node_modules" || name === "temp")) continue;
|
|
258
|
-
hashDirectory(hash, root, path.join(relative, name), excludedRoots);
|
|
259
|
-
}
|
|
260
|
-
return;
|
|
261
|
-
}
|
|
262
|
-
if (stat.isSymbolicLink()) {
|
|
263
|
-
throw new Error(`Execution provenance source must not be a symbolic link: ${absolute}`);
|
|
264
|
-
}
|
|
265
|
-
hash.update(`file\0${relative}\0`);
|
|
266
|
-
hash.update(readRegularFileNoFollow(absolute));
|
|
267
|
-
hash.update("\0");
|
|
268
|
-
}
|
|
269
|
-
function fileFingerprint(filePath) {
|
|
270
|
-
return createHash("sha256").update(readRegularFileNoFollow(filePath)).digest("hex");
|
|
271
|
-
}
|
|
272
|
-
function readPathIdentity(filePath) {
|
|
273
|
-
const stat = fs.lstatSync(filePath);
|
|
274
|
-
if (stat.isSymbolicLink()) {
|
|
275
|
-
throw new Error(`Execution provenance source must not be a symbolic link: ${filePath}`);
|
|
276
|
-
}
|
|
277
|
-
return readRegularFileNoFollow(filePath);
|
|
278
|
-
}
|
|
279
|
-
function readRegularFileNoFollow(filePath) {
|
|
280
|
-
let fd;
|
|
281
|
-
try {
|
|
282
|
-
fd = fs.openSync(filePath, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW);
|
|
283
|
-
} catch (error) {
|
|
284
|
-
if (error.code === "ELOOP") {
|
|
285
|
-
throw new Error(`Execution provenance source must not be a symbolic link: ${filePath}`);
|
|
286
|
-
}
|
|
287
|
-
throw error;
|
|
288
|
-
}
|
|
289
|
-
try {
|
|
290
|
-
if (!fs.fstatSync(fd).isFile()) {
|
|
291
|
-
throw new Error(`Execution provenance source is not a regular file: ${filePath}`);
|
|
292
|
-
}
|
|
293
|
-
return fs.readFileSync(fd);
|
|
294
|
-
} finally {
|
|
295
|
-
fs.closeSync(fd);
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
function displayPath(projectRoot, filePath) {
|
|
299
|
-
const relative = path.relative(path.resolve(projectRoot), path.resolve(filePath));
|
|
300
|
-
return relative !== ".." && !relative.startsWith(`..${path.sep}`) ? relative || "." : path.resolve(filePath);
|
|
301
|
-
}
|
|
302
|
-
function gitText(cwd, args) {
|
|
303
|
-
return gitBuffer(cwd, args).toString("utf8");
|
|
304
|
-
}
|
|
305
|
-
function gitBuffer(cwd, args) {
|
|
306
|
-
return execFileSync("git", args, {
|
|
307
|
-
cwd,
|
|
308
|
-
encoding: "buffer",
|
|
309
|
-
maxBuffer: 128 * 1024 * 1024,
|
|
310
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
311
|
-
});
|
|
312
|
-
}
|
|
313
|
-
function compareSource(drift, phase, prefix, start, current) {
|
|
314
|
-
compare(drift, phase, `${prefix}.head`, start.head, current.head);
|
|
315
|
-
compare(drift, phase, `${prefix}.status`, start.status, current.status);
|
|
316
|
-
compare(
|
|
317
|
-
drift,
|
|
318
|
-
phase,
|
|
319
|
-
`${prefix}.sourceFingerprint`,
|
|
320
|
-
start.sourceFingerprint,
|
|
321
|
-
current.sourceFingerprint
|
|
322
|
-
);
|
|
323
|
-
}
|
|
324
|
-
function compare(drift, phase, field, start, current) {
|
|
325
|
-
if (start !== current) drift.push({ phase, field, start, current });
|
|
326
|
-
}
|
|
327
|
-
export {
|
|
328
|
-
ProvenanceDriftError,
|
|
329
|
-
captureExecutionProvenance,
|
|
330
|
-
executionProvenanceDrift,
|
|
331
|
-
writeExecutionProvenance
|
|
332
|
-
};
|