@deeeed/metamask-harness 0.20.0 → 0.22.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 +22 -0
- package/adapters/core/inject.sh +1 -3
- package/adapters/extension/inject.mjs +1 -1
- package/adapters/mobile/inject.sh +1 -1
- package/dist/adapters/extension/ensure-ready.js +44 -15
- package/dist/cli-commands.js +1 -0
- package/dist/cli.js +2 -0
- package/dist/command-contract.js +57 -0
- package/dist/commands/call.js +1 -1
- package/dist/commands/execution-template.js +28 -0
- package/dist/commands/manifest.js +15 -21
- package/dist/commands/run-engine.js +17 -8
- package/dist/library-provenance.js +33 -0
- package/dist/manifest.js +85 -101
- package/dist/mm-harness-cli.js +35 -1
- package/dist/runner.js +9 -4
- package/docs/RECIPES.md +2 -11
- package/library/actions/extension/ui/navigate.mjs +16 -6
- package/library/manifests/core.action-manifest.json +316 -501
- package/library/manifests/extension.action-manifest.json +609 -920
- package/library/manifests/mobile.action-manifest.json +630 -954
- package/package.json +4 -4
- package/scripts/completions.sh +2 -1
- package/library/library.json +0 -7
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.22.0 - 2026-07-26
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added `execution-template` as the thin public boundary for shared checklist discovery and materialization.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- `ui.navigate page=home` recognizes both the current bottom navigation and the selected legacy Tokens tab.
|
|
14
|
+
- Extension readiness recovers deterministically from transient target-list failures.
|
|
15
|
+
- Team-library calls retain their source provenance.
|
|
16
|
+
|
|
17
|
+
## 0.21.0 - 2026-07-24
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- Simplified action manifests to one schema-backed keyed action map; action kind,
|
|
22
|
+
library identity, categories, and provenance are inferred from authoritative
|
|
23
|
+
runtime inputs.
|
|
24
|
+
- Updated the Farmslot protocol, agent runtime, and recipe harness dependencies
|
|
25
|
+
for Recipe Protocol v1 trace consistency.
|
|
26
|
+
|
|
5
27
|
## 0.20.0 - 2026-07-24
|
|
6
28
|
|
|
7
29
|
### Added
|
package/adapters/core/inject.sh
CHANGED
|
@@ -111,9 +111,7 @@ install_v1_runner_assets() {
|
|
|
111
111
|
rsync -a --delete "$METAMASK_RUNNER_DIR/library/recipes/" "$HARNESS_DIR/runner/recipes/"
|
|
112
112
|
fi
|
|
113
113
|
rm -rf "$HARNESS_DIR/runner/flows"
|
|
114
|
-
|
|
115
|
-
cp "$METAMASK_RUNNER_DIR/library/library.json" "$HARNESS_DIR/runner/library.json"
|
|
116
|
-
fi
|
|
114
|
+
rm -f "$HARNESS_DIR/runner/library.json"
|
|
117
115
|
if [ ! -x "$HARNESS_DIR/runner/bin/mm-harness" ]; then
|
|
118
116
|
echo "Refusing core recipe harness install: failed to make runner executable." >&2
|
|
119
117
|
return 1
|
|
@@ -90,8 +90,8 @@ copyFile(path.join(runnerDir, 'library/manifests/mobile.action-manifest.json'),
|
|
|
90
90
|
copyFile(path.join(runnerDir, 'library/manifests/extension.action-manifest.json'), path.join(harnessDir, 'runner/manifests/extension.action-manifest.json'));
|
|
91
91
|
copyFile(path.join(runnerDir, 'library/manifests/extension.action-manifest.json'), path.join(harnessDir, 'action-manifest.json'));
|
|
92
92
|
copyDir(path.join(runnerDir, 'library/recipes'), path.join(harnessDir, 'runner/recipes'));
|
|
93
|
-
copyFile(path.join(runnerDir, 'library/library.json'), path.join(harnessDir, 'runner/library.json'));
|
|
94
93
|
fs.rmSync(path.join(harnessDir, 'runner/flows'), { recursive: true, force: true });
|
|
94
|
+
fs.rmSync(path.join(harnessDir, 'runner/library.json'), { force: true });
|
|
95
95
|
// The installed overlay has a flat scripts/ runtime API independent of the
|
|
96
96
|
// package's source layout.
|
|
97
97
|
copyFile(path.join(runnerDir, 'adapters/extension/launch-browser.cjs'), path.join(harnessDir, 'scripts/launch-browser.cjs'));
|
|
@@ -74,8 +74,8 @@ cp "$RUNNER_DIR/library/manifests/mobile.action-manifest.json" "$HARNESS_DIR/act
|
|
|
74
74
|
cp "$RUNNER_DIR/library/manifests/mobile.action-manifest.json" "$HARNESS_DIR/runner/manifests/mobile.action-manifest.json"
|
|
75
75
|
cp "$RUNNER_DIR/library/manifests/extension.action-manifest.json" "$HARNESS_DIR/runner/manifests/extension.action-manifest.json"
|
|
76
76
|
rsync -a --delete "$RUNNER_DIR/library/recipes/" "$HARNESS_DIR/runner/recipes/"
|
|
77
|
-
cp "$RUNNER_DIR/library/library.json" "$HARNESS_DIR/runner/library.json"
|
|
78
77
|
rm -rf "$HARNESS_DIR/runner/flows"
|
|
78
|
+
rm -f "$HARNESS_DIR/runner/library.json"
|
|
79
79
|
cp "$RUNNER_DIR/adapters/mobile/verify.sh" "$HARNESS_DIR/scripts/verify.sh"
|
|
80
80
|
cp "$RUNNER_DIR/adapters/shared/harness-path.sh" "$HARNESS_DIR/scripts/lib/harness-path.sh"
|
|
81
81
|
cp "$RUNNER_DIR/adapters/shared/path-defaults.json" "$HARNESS_DIR/scripts/lib/path-defaults.json"
|
|
@@ -36,9 +36,11 @@ async function openHome(port, extensionId) {
|
|
|
36
36
|
try {
|
|
37
37
|
let res = await fetch(endpoint, { method: "PUT", signal: AbortSignal.timeout(8e3) });
|
|
38
38
|
if (res.status === 404 || res.status === 405) res = await fetch(endpoint, { signal: AbortSignal.timeout(8e3) });
|
|
39
|
-
|
|
39
|
+
if (!res.ok) return null;
|
|
40
|
+
const value = await res.json();
|
|
41
|
+
return value && typeof value === "object" ? value : {};
|
|
40
42
|
} catch {
|
|
41
|
-
return
|
|
43
|
+
return null;
|
|
42
44
|
}
|
|
43
45
|
}
|
|
44
46
|
function homePages(targets, extensionId) {
|
|
@@ -94,7 +96,7 @@ async function ensureExtensionReady(target, options) {
|
|
|
94
96
|
continue;
|
|
95
97
|
}
|
|
96
98
|
action = "opened";
|
|
97
|
-
opened = await openHome(cdpPort, extensionId) || opened;
|
|
99
|
+
opened = Boolean(await openHome(cdpPort, extensionId)) || opened;
|
|
98
100
|
await sleep(1500);
|
|
99
101
|
} else {
|
|
100
102
|
action = "pruned";
|
|
@@ -149,20 +151,47 @@ async function ensureExtensionReady(target, options) {
|
|
|
149
151
|
if (action === "pruned" && (health.status !== "PASS" || after !== 1)) {
|
|
150
152
|
const listing = await jsonList(cdpPort);
|
|
151
153
|
if (listing.ok) {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
await sleep(500);
|
|
158
|
-
opened = await openHome(cdpPort, extensionId) || opened;
|
|
154
|
+
const existingHomeIds = new Set(
|
|
155
|
+
homePages(listing.targets, extensionId).map((home) => String(home.id))
|
|
156
|
+
);
|
|
157
|
+
const replacement = await openHome(cdpPort, extensionId);
|
|
158
|
+
opened = Boolean(replacement) || opened;
|
|
159
159
|
await sleep(1500);
|
|
160
|
-
const
|
|
161
|
-
if (!
|
|
162
|
-
return base({
|
|
160
|
+
const replacementListing = await jsonList(cdpPort);
|
|
161
|
+
if (!replacementListing.ok) {
|
|
162
|
+
return base({
|
|
163
|
+
extensionId,
|
|
164
|
+
opened,
|
|
165
|
+
action,
|
|
166
|
+
homeTabs: { before, closed, after },
|
|
167
|
+
reasonCode: "cdp-unreachable"
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
const replacementHomes = homePages(replacementListing.targets, extensionId);
|
|
171
|
+
const replacementId = replacement?.id && replacementHomes.some((home) => home.id === replacement.id) ? String(replacement.id) : replacementHomes.find((home) => !existingHomeIds.has(String(home.id)))?.id;
|
|
172
|
+
if (replacementId) {
|
|
173
|
+
action = "reopened";
|
|
174
|
+
for (const home of replacementHomes) {
|
|
175
|
+
if (String(home.id) === replacementId) continue;
|
|
176
|
+
await closeTab(cdpPort, String(home.id));
|
|
177
|
+
closed += 1;
|
|
178
|
+
}
|
|
179
|
+
await sleep(500);
|
|
180
|
+
const relisted = await jsonList(cdpPort);
|
|
181
|
+
if (!relisted.ok) {
|
|
182
|
+
return base({
|
|
183
|
+
extensionId,
|
|
184
|
+
opened,
|
|
185
|
+
action,
|
|
186
|
+
homeTabs: { before, closed, after },
|
|
187
|
+
reasonCode: "cdp-unreachable"
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
after = homePages(relisted.targets, extensionId).length;
|
|
191
|
+
health = await checkHealth();
|
|
192
|
+
} else {
|
|
193
|
+
after = replacementHomes.length;
|
|
163
194
|
}
|
|
164
|
-
after = homePages(relisted.targets, extensionId).length;
|
|
165
|
-
health = await checkHealth();
|
|
166
195
|
}
|
|
167
196
|
}
|
|
168
197
|
let slotTitle;
|
package/dist/cli-commands.js
CHANGED
|
@@ -18,6 +18,7 @@ const SPEC = {
|
|
|
18
18
|
{ name: "actions", desc: "List runnable recipe actions", flags: ["--json", "--categories", "--category", "--action", "--library"] },
|
|
19
19
|
{ name: "doctor", desc: "Check harness/orchestration health", flags: ["--json", "--target", "--adapter", "--runtime-dir", "--expect-live", "--print-ready", "--cdp-port", "--device"] },
|
|
20
20
|
{ name: "run", desc: "Execute a proof recipe (path or library name, e.g. run perps.smoke)", args: ["recipe.json|name"], flags: ["--list", "--device"] },
|
|
21
|
+
{ name: "execution-template", desc: "Discover and materialize shared agent checklists", args: ["list", "materialize", "lint", "new"], flags: ["--dir", "--domain-dir", "--project-worker", "--project-name", "--package-templates", "--package-id", "--flow", "--run-mode", "--platform", "--domain", "--id", "--provenance", "--include-shadowed", "--no-include-shadowed", "--title", "--force", "--json"] },
|
|
21
22
|
{ name: "recipe-quality", desc: "Build the recipe-quality artifact from compact JSON", args: ["build"], flags: ["--input", "--output", "--json"] },
|
|
22
23
|
{ name: "interactive", aliases: ["menu"], desc: "Interactive command menu" },
|
|
23
24
|
{ name: "prepare", desc: "Install harness (+ optional validate)", flags: ["--target", "--runtime-dir", "--json"] },
|
package/dist/cli.js
CHANGED
|
@@ -20,6 +20,7 @@ import { handleRecipeQuality } from "./commands/recipe-quality.js";
|
|
|
20
20
|
import { handleStatus } from "./commands/status.js";
|
|
21
21
|
import { handleCheck } from "./commands/check.js";
|
|
22
22
|
import { handleChecklist } from "./commands/checklist.js";
|
|
23
|
+
import { handleExecutionTemplate } from "./commands/execution-template.js";
|
|
23
24
|
import { handleLast } from "./commands/last.js";
|
|
24
25
|
import { parseArgs, targetPath } from "./commands/parse-args.js";
|
|
25
26
|
const COMMANDS = {
|
|
@@ -121,6 +122,7 @@ async function main(argv) {
|
|
|
121
122
|
if (command === "recipe-quality") return handleRecipeQuality(argv.slice(1));
|
|
122
123
|
if (command === "check") return handleCheck(argv.slice(1));
|
|
123
124
|
if (command === "checklist") return handleChecklist(argv.slice(1));
|
|
125
|
+
if (command === "execution-template") return handleExecutionTemplate(argv.slice(1));
|
|
124
126
|
if (command === "last") return handleLast(parseArgs(argv.slice(1), command));
|
|
125
127
|
const handler = COMMANDS[command];
|
|
126
128
|
if (!handler) throw new Error(`Unknown command: ${command}`);
|
package/dist/command-contract.js
CHANGED
|
@@ -88,6 +88,32 @@ const PUBLIC_COMMAND_CONTRACTS = {
|
|
|
88
88
|
],
|
|
89
89
|
minimumPositionals: 2
|
|
90
90
|
},
|
|
91
|
+
"execution-template": {
|
|
92
|
+
usage: "mm-harness execution-template <list|materialize|lint|new> [target] [options]",
|
|
93
|
+
options: options(HELP, JSON, {
|
|
94
|
+
"--dir": value(),
|
|
95
|
+
"--domain-dir": value(),
|
|
96
|
+
"--project-worker": value(),
|
|
97
|
+
"--project-name": value(),
|
|
98
|
+
"--package-templates": value(),
|
|
99
|
+
"--package-id": value(),
|
|
100
|
+
"--flow": value(),
|
|
101
|
+
"--run-mode": value(["autonomous", "interactive", "validation"]),
|
|
102
|
+
"--platform": value(),
|
|
103
|
+
"--domain": value(),
|
|
104
|
+
"--id": value(),
|
|
105
|
+
"--provenance": value(),
|
|
106
|
+
"--include-shadowed": bool(),
|
|
107
|
+
"--no-include-shadowed": bool(),
|
|
108
|
+
"--title": value(),
|
|
109
|
+
"--force": bool()
|
|
110
|
+
}),
|
|
111
|
+
positionals: [
|
|
112
|
+
{ label: "action", choices: ["list", "materialize", "lint", "new"] }
|
|
113
|
+
],
|
|
114
|
+
minimumPositionals: 1,
|
|
115
|
+
variadic: { label: "target", validate: (value2) => value2.length > 0 }
|
|
116
|
+
},
|
|
91
117
|
actions: {
|
|
92
118
|
options: options(HELP, JSON, TARGET, ADAPTER, ADAPTER_OR_MOBILE_PLATFORM, {
|
|
93
119
|
"--action": value(),
|
|
@@ -360,6 +386,37 @@ function validatePublicInvocation(argv, examples = {}) {
|
|
|
360
386
|
);
|
|
361
387
|
}
|
|
362
388
|
}
|
|
389
|
+
if (name === "execution-template") {
|
|
390
|
+
const action = positionals[0];
|
|
391
|
+
const validAction = ["list", "materialize", "lint", "new"].includes(action ?? "");
|
|
392
|
+
const targetCount = Math.max(0, positionals.length - 1);
|
|
393
|
+
if (validAction && action === "list" && targetCount > 0) {
|
|
394
|
+
return usageFailure(
|
|
395
|
+
"CLI_EXCESS_POSITIONAL",
|
|
396
|
+
name,
|
|
397
|
+
`execution-template list does not accept positional '${positionals[1]}'.`,
|
|
398
|
+
contract,
|
|
399
|
+
examples
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
if (validAction && (action === "materialize" || action === "lint" || action === "new") && targetCount === 0) {
|
|
403
|
+
return missingPositionalFailure(
|
|
404
|
+
name,
|
|
405
|
+
`execution-template ${action} requires <target>.`,
|
|
406
|
+
contract,
|
|
407
|
+
examples
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
if (validAction && targetCount > 1) {
|
|
411
|
+
return usageFailure(
|
|
412
|
+
"CLI_EXCESS_POSITIONAL",
|
|
413
|
+
name,
|
|
414
|
+
`unexpected positional '${positionals[2]}'; execution-template ${action} accepts one <target>.`,
|
|
415
|
+
contract,
|
|
416
|
+
examples
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
363
420
|
if (contract.leadingPositionals && !contract.requiredUnless?.some((option) => seenOptions.has(option)) && tokens.slice(0, contract.leadingPositionals).some((argument) => !argument || argument.startsWith("-"))) {
|
|
364
421
|
const missing = contract.positionals?.[0]?.label ?? "argument";
|
|
365
422
|
return missingPositionalFailure(name, `${name} requires <${missing}> first.`, contract, examples);
|
package/dist/commands/call.js
CHANGED
|
@@ -470,7 +470,7 @@ function renderCallExamples(short, examples) {
|
|
|
470
470
|
if (!Array.isArray(examples)) return [];
|
|
471
471
|
const out = [];
|
|
472
472
|
for (const example of examples.slice(0, 2)) {
|
|
473
|
-
const node = isRecord(example)
|
|
473
|
+
const node = isRecord(example) ? example : void 0;
|
|
474
474
|
if (!node) continue;
|
|
475
475
|
const tokens = Object.entries(node).filter(([key]) => key !== "action" && key !== "intent").map(([key, value]) => `${key}=${typeof value === "string" ? value : JSON.stringify(value)}`);
|
|
476
476
|
out.push(`mm-harness call ${short} ${tokens.join(" ")}`.trim());
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
const require2 = createRequire(import.meta.url);
|
|
4
|
+
async function handleExecutionTemplate(argv) {
|
|
5
|
+
let entrypoint;
|
|
6
|
+
try {
|
|
7
|
+
entrypoint = require2.resolve(
|
|
8
|
+
"@farmslot/agent-runtime/scripts/execution-template-cli.mjs"
|
|
9
|
+
);
|
|
10
|
+
} catch (error) {
|
|
11
|
+
console.error(
|
|
12
|
+
`mm-harness execution-template: installed @farmslot/agent-runtime does not expose the catalog command: ${error instanceof Error ? error.message : String(error)}`
|
|
13
|
+
);
|
|
14
|
+
return 1;
|
|
15
|
+
}
|
|
16
|
+
const result = spawnSync(process.execPath, [entrypoint, ...argv], {
|
|
17
|
+
env: process.env,
|
|
18
|
+
stdio: "inherit"
|
|
19
|
+
});
|
|
20
|
+
if (result.error) {
|
|
21
|
+
console.error(`mm-harness execution-template: ${result.error.message}`);
|
|
22
|
+
return 1;
|
|
23
|
+
}
|
|
24
|
+
return result.status ?? 1;
|
|
25
|
+
}
|
|
26
|
+
export {
|
|
27
|
+
handleExecutionTemplate
|
|
28
|
+
};
|
|
@@ -12,10 +12,12 @@ import {
|
|
|
12
12
|
import { resolveMetaMaskLibrarySources } from "./run-engine.js";
|
|
13
13
|
import { isSensitiveKey, redactStructuredValue } from "../command-journal.js";
|
|
14
14
|
import {
|
|
15
|
+
OFFICIAL_RECIPE_ACTIONS,
|
|
15
16
|
officialRecipeActionCapabilities
|
|
16
17
|
} from "@farmslot/protocol";
|
|
17
18
|
import { metaMaskActionExecutionCapabilities } from "../recipe-security.js";
|
|
18
19
|
import { closest } from "../command-contract.js";
|
|
20
|
+
const OFFICIAL_ACTIONS = new Set(OFFICIAL_RECIPE_ACTIONS);
|
|
19
21
|
async function handleActions({ options, positional }) {
|
|
20
22
|
const { adapter, target } = resolveAdapter(options);
|
|
21
23
|
const librarySources = await resolveMetaMaskLibrarySources(optionStrings(options, "library"));
|
|
@@ -211,7 +213,7 @@ function authoredExampleNode(examples, preferredValues, schema) {
|
|
|
211
213
|
if (!Array.isArray(examples)) return void 0;
|
|
212
214
|
if (preferredValues && Object.keys(preferredValues).length > 0) {
|
|
213
215
|
const normalizedValues = normalizePreferredValues(preferredValues, schema);
|
|
214
|
-
const nodes = examples.
|
|
216
|
+
const nodes = examples.filter(isRecord);
|
|
215
217
|
if (nodes.length === 0) return void 0;
|
|
216
218
|
const ranked = nodes.map((node) => ({
|
|
217
219
|
node,
|
|
@@ -226,7 +228,7 @@ function authoredExampleNode(examples, preferredValues, schema) {
|
|
|
226
228
|
return { ...ranked[0].node, ...normalizedValues };
|
|
227
229
|
}
|
|
228
230
|
for (const example of examples) {
|
|
229
|
-
if (isRecord(example)
|
|
231
|
+
if (isRecord(example)) return example;
|
|
230
232
|
}
|
|
231
233
|
return void 0;
|
|
232
234
|
}
|
|
@@ -305,22 +307,15 @@ function summarizeActionCategories(actions) {
|
|
|
305
307
|
}
|
|
306
308
|
function describeManifestActions(manifest, actionSources = /* @__PURE__ */ new Map()) {
|
|
307
309
|
const manifestRecord = isRecord(manifest) ? manifest : {};
|
|
308
|
-
const
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
}
|
|
318
|
-
return [];
|
|
319
|
-
}) : [];
|
|
320
|
-
return [
|
|
321
|
-
...official.map((name) => describeManifestAction(name, "official", metadata[name], actionSources.get(name))),
|
|
322
|
-
...custom.map((entry) => describeManifestAction(entry.name, "custom", entry.metadata, actionSources.get(entry.name)))
|
|
323
|
-
];
|
|
310
|
+
const actions = isRecord(manifestRecord.actions) ? manifestRecord.actions : {};
|
|
311
|
+
return Object.entries(actions).map(
|
|
312
|
+
([name, metadata]) => describeManifestAction(
|
|
313
|
+
name,
|
|
314
|
+
OFFICIAL_ACTIONS.has(name) ? "official" : "custom",
|
|
315
|
+
metadata,
|
|
316
|
+
actionSources.get(name)
|
|
317
|
+
)
|
|
318
|
+
);
|
|
324
319
|
}
|
|
325
320
|
function describeManifestAction(name, kind, metadata, source) {
|
|
326
321
|
const record = isRecord(metadata) ? metadata : {};
|
|
@@ -330,7 +325,7 @@ function describeManifestAction(name, kind, metadata, source) {
|
|
|
330
325
|
return {
|
|
331
326
|
name,
|
|
332
327
|
kind,
|
|
333
|
-
category: actionCategory(name
|
|
328
|
+
category: actionCategory(name),
|
|
334
329
|
description: typeof record.description === "string" ? record.description : "",
|
|
335
330
|
fields: properties,
|
|
336
331
|
schema,
|
|
@@ -347,8 +342,7 @@ function describeManifestAction(name, kind, metadata, source) {
|
|
|
347
342
|
])]
|
|
348
343
|
};
|
|
349
344
|
}
|
|
350
|
-
function actionCategory(name
|
|
351
|
-
if (typeof configured === "string" && configured.trim()) return configured.trim().toLowerCase();
|
|
345
|
+
function actionCategory(name) {
|
|
352
346
|
const segments = name.split(".");
|
|
353
347
|
if (segments[0] === "metamask" && segments.length > 2) return segments[1] ?? "metamask";
|
|
354
348
|
if (segments[0] === "app" || segments[0] === "cdp") return "runtime";
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
import { captureHelperSupportsRecordSessionSnapshots } from "../recording-target.js";
|
|
22
22
|
import { startRecipeRecording, stopRecipeRecording } from "../run-recording.js";
|
|
23
23
|
import { validateMetaMaskActionInputs } from "../metamask-action-validation.js";
|
|
24
|
+
import { gitLibraryProvenance } from "../library-provenance.js";
|
|
24
25
|
import {
|
|
25
26
|
beginRunDiagnostics,
|
|
26
27
|
finishRunDiagnostics
|
|
@@ -655,14 +656,22 @@ async function resolveMetaMaskLibrarySources(libraryEntry, recipePath) {
|
|
|
655
656
|
if (!sources.some((source) => path.resolve(source.root) === canonicalRoot)) {
|
|
656
657
|
sources.push({ name: "metamask", root: canonicalRoot });
|
|
657
658
|
}
|
|
658
|
-
return
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
659
|
+
return Promise.all(
|
|
660
|
+
sources.map(async (source) => {
|
|
661
|
+
const isBundled = source.name === "metamask";
|
|
662
|
+
const detected = isBundled ? {} : await gitLibraryProvenance(source.root);
|
|
663
|
+
return {
|
|
664
|
+
...source,
|
|
665
|
+
provenance: {
|
|
666
|
+
kind: isBundled ? "bundled" : "library",
|
|
667
|
+
trust: isBundled ? "trusted" : "unknown",
|
|
668
|
+
name: source.name ?? path.basename(source.root),
|
|
669
|
+
...detected,
|
|
670
|
+
...source.provenance
|
|
671
|
+
}
|
|
672
|
+
};
|
|
673
|
+
})
|
|
674
|
+
);
|
|
666
675
|
}
|
|
667
676
|
function synthesizeOneNodeRecipe(action, args) {
|
|
668
677
|
return {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
const execFileAsync = promisify(execFile);
|
|
4
|
+
async function gitLibraryProvenance(root) {
|
|
5
|
+
try {
|
|
6
|
+
const { stdout: tracked } = await execFileAsync("git", ["-C", root, "ls-files", "--", "."], {
|
|
7
|
+
encoding: "utf8",
|
|
8
|
+
timeout: 5e3
|
|
9
|
+
});
|
|
10
|
+
if (!tracked.trim()) return {};
|
|
11
|
+
const [{ stdout: revision }, { stdout: status }] = await Promise.all([
|
|
12
|
+
execFileAsync("git", ["-C", root, "rev-parse", "HEAD"], {
|
|
13
|
+
encoding: "utf8",
|
|
14
|
+
timeout: 5e3
|
|
15
|
+
}),
|
|
16
|
+
execFileAsync(
|
|
17
|
+
"git",
|
|
18
|
+
["-C", root, "status", "--porcelain=v1", "--untracked-files=normal", "--", "."],
|
|
19
|
+
{
|
|
20
|
+
encoding: "utf8",
|
|
21
|
+
timeout: 5e3
|
|
22
|
+
}
|
|
23
|
+
)
|
|
24
|
+
]);
|
|
25
|
+
const trimmedRevision = revision.trim();
|
|
26
|
+
return trimmedRevision ? { revision: trimmedRevision, dirty: status.trim().length > 0 } : {};
|
|
27
|
+
} catch {
|
|
28
|
+
return {};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export {
|
|
32
|
+
gitLibraryProvenance
|
|
33
|
+
};
|