@deeeed/metamask-harness 0.16.0 → 0.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +44 -0
- package/README.md +90 -137
- package/adapters/core/cleanup.sh +0 -0
- package/adapters/core/inject.sh +0 -0
- package/adapters/extension/cleanup.mjs +0 -0
- package/adapters/extension/console-tail.mjs +51 -24
- package/adapters/extension/ensure-browser.sh +0 -0
- package/adapters/extension/inject.mjs +5 -9
- package/adapters/extension/launch-browser.cjs +0 -0
- package/adapters/extension/launch-webpack.cjs +83 -0
- package/adapters/extension/launch.sh +8 -11
- package/adapters/extension/live.sh +2 -0
- package/adapters/extension/readiness.mjs +0 -0
- package/adapters/extension/reattach.sh +1 -0
- package/adapters/extension/refresh-build.sh +0 -0
- package/adapters/extension/seed-fixture.sh +4 -12
- package/adapters/extension/sidepanel-toggle.sh +4 -1
- package/adapters/extension/snapshot-dist.sh +0 -0
- package/adapters/extension/stamp-runtime-title.cjs +49 -0
- package/adapters/extension/start-watch.sh +16 -4
- package/adapters/extension/stop-viewers.sh +1 -1
- package/adapters/extension/sync-webpack-dist.cjs +105 -0
- package/adapters/extension/verify.sh +1 -1
- package/adapters/extension/wallet-fixture-state.cjs +0 -0
- package/adapters/manifest.json +46 -38
- package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +0 -0
- package/adapters/mobile/bridge-runtime/console-forwarder.cjs +1 -1
- package/adapters/mobile/bridge-runtime/lib/target-discovery.cjs +2 -3
- package/adapters/mobile/bridge-runtime/setup-wallet.sh +0 -0
- package/adapters/mobile/cleanup.sh +16 -176
- package/adapters/mobile/inject.sh +88 -671
- package/adapters/mobile/launch-console-forwarder.cjs +39 -0
- package/adapters/mobile/lib/metro-listener.sh +0 -0
- package/adapters/mobile/lib/tmux-viewer.sh +0 -0
- package/adapters/mobile/open-device.sh +0 -0
- package/adapters/mobile/prewarm-bundle.sh +0 -0
- package/adapters/mobile/start-console-forwarder.sh +70 -0
- package/adapters/mobile/start-metro.sh +0 -41
- package/adapters/mobile/stop-metro.sh +0 -0
- package/adapters/mobile/verify.sh +4 -61
- package/adapters/mobile/wait-for-bridge.sh +0 -0
- package/adapters/mobile/yarn-setup.sh +0 -0
- package/adapters/shared/activate-repo-node.sh +0 -0
- package/adapters/shared/activate-repo-ruby.sh +0 -0
- package/adapters/shared/cli-ux.sh +0 -0
- package/adapters/shared/ensure-runner-deps.sh +0 -0
- package/adapters/shared/harness-path.sh +0 -0
- package/adapters/shared/hash-helpers.sh +0 -0
- package/adapters/shared/install-repo-deps.sh +0 -0
- package/adapters/shared/json-field.sh +0 -0
- package/adapters/shared/open-debug.mjs +90 -36
- package/adapters/shared/open-log-window.sh +0 -0
- package/adapters/shared/reap-checkout-metros.sh +0 -0
- package/adapters/shared/resolve-slot-ports-core.mjs +0 -6
- package/adapters/shared/resolve-slot-ports.mjs +0 -0
- package/adapters/shared/resolve-slot-ports.sh +0 -5
- package/adapters/shared/sync-wallet-fixture.sh +0 -0
- package/adapters/shared/tmux-session.sh +0 -0
- package/adapters/shared/tmux-viewer.sh +0 -0
- package/dist/adapters/extension/console-capture.js +84 -0
- package/dist/adapters/extension/runtime-decision.js +14 -2
- package/dist/adapters/extension/surface.js +3 -2
- package/dist/adapters/mobile/prepare.js +22 -7
- package/dist/adapters/mobile/surface.js +1 -1
- package/dist/adapters/resolve-slot-ports.js +0 -2
- package/dist/adapters/slot-ports.js +8 -0
- package/dist/cli.js +5 -11
- package/dist/command-contract.js +441 -0
- package/dist/command-journal.js +225 -0
- package/dist/commands/call.js +43 -20
- package/dist/commands/check.js +9 -3
- package/dist/commands/debug.js +38 -31
- package/dist/commands/device-target.js +27 -12
- package/dist/commands/doctor.js +19 -6
- package/dist/commands/fixtures.js +19 -11
- package/dist/commands/last.js +52 -0
- package/dist/commands/launch/index.js +166 -59
- package/dist/commands/logs.js +8 -6
- package/dist/commands/manifest.js +114 -29
- package/dist/commands/parse-args.js +1 -0
- package/dist/commands/provision.js +10 -3
- package/dist/commands/run-engine.js +27 -42
- package/dist/commands/run-report.js +1 -1
- package/dist/commands/run.js +243 -41
- package/dist/commands/shared.js +11 -1
- package/dist/commands/status-probe.js +1 -1
- package/dist/commands/status.js +1 -1
- package/dist/commands/stop.js +7 -2
- package/dist/doctor.js +1 -4
- package/dist/harness.js +19 -125
- package/dist/json-stream.js +57 -0
- package/dist/live-adapter-contract.js +6 -20
- package/dist/mm-harness-cli.js +111 -41
- package/dist/paths.js +2 -5
- package/dist/run-diagnostics.js +35 -40
- package/dist/runner.js +32 -1
- package/dist/runtime-context.js +26 -8
- package/docs/CHEATSHEET.md +39 -52
- package/docs/CLI-ERGONOMICS-AUDIT.md +32 -0
- package/docs/CLI-ERGONOMICS-HUMAN-QA.md +48 -0
- package/docs/CODE-MAP.md +1 -1
- package/docs/UX-PRINCIPLES.md +2 -0
- package/docs/VIDEO-DEMO-VALIDATION.md +62 -0
- package/docs/architecture.md +65 -375
- package/docs/live-adapter-contract.md +3 -1
- package/library/actions/core/perps/_controller.mjs +10 -55
- package/library/manifests/mobile.action-manifest.json +1 -1
- package/package.json +3 -2
- package/scripts/completions.sh +0 -0
- package/scripts/install-completions.sh +0 -0
- package/adapters/mobile/overlay/app/dev-tools/AgenticService/AgentStepHud.tsx.patch +0 -185
- package/adapters/mobile/overlay/app/dev-tools/AgenticService/AgenticService.ts.patch +0 -1662
- package/adapters/mobile/overlay/compat/README.md +0 -28
- package/adapters/mobile/overlay/compat/rn81-message-event-source.patch +0 -42
- package/adapters/shared/resolve-farmslot-ports-core.mjs +0 -3
- package/adapters/shared/resolve-farmslot-ports.mjs +0 -5
- package/adapters/shared/resolve-farmslot-ports.sh +0 -7
- package/docs/CLI-SPEC.md +0 -1102
- package/docs/MENTAL-MODEL.md +0 -295
package/dist/commands/logs.js
CHANGED
|
@@ -19,9 +19,10 @@ async function handleLogs(argv) {
|
|
|
19
19
|
const logSources = surface.logSources(target);
|
|
20
20
|
const appLogSource = surface.appLogSource(target);
|
|
21
21
|
const sourceLabels = logSources.map((entry) => entry.label);
|
|
22
|
-
const
|
|
22
|
+
const appSourceLabel = appLogSource?.label ?? "app";
|
|
23
|
+
const defaultSource = appLogSource && appSourceLabel !== "app" ? appSourceLabel : sourceLabels[0];
|
|
23
24
|
const source = str(options, "source") ?? defaultSource;
|
|
24
|
-
const validSources = [...sourceLabels, ...appLogSource ? ["app"] : []];
|
|
25
|
+
const validSources = [...sourceLabels, ...appLogSource ? [appSourceLabel] : [], ...appLogSource && appSourceLabel !== "app" ? ["app"] : []];
|
|
25
26
|
if (!validSources.includes(source)) {
|
|
26
27
|
return usageOut(json, "logs", `--source must be one of: ${validSources.join(", ")}.`, `mm-harness logs --source ${defaultSource}`);
|
|
27
28
|
}
|
|
@@ -32,11 +33,12 @@ async function handleLogs(argv) {
|
|
|
32
33
|
}
|
|
33
34
|
process.env.RECIPE_LOG_EVENTS = events;
|
|
34
35
|
}
|
|
35
|
-
const
|
|
36
|
-
const
|
|
36
|
+
const isAppSource = source === "app" || source === appSourceLabel;
|
|
37
|
+
const requested = isAppSource ? appLogSource : logSources.find((entry) => entry.label === source);
|
|
38
|
+
const ordered = isAppSource ? requested ? [requested] : [] : requested ? [requested] : logSources;
|
|
37
39
|
const logFile = ordered.find((entry) => fs.existsSync(entry.path))?.path;
|
|
38
40
|
if (!logFile) {
|
|
39
|
-
if (
|
|
41
|
+
if (isAppSource && appLogSource) {
|
|
40
42
|
const message = `application console unavailable for this checkout (${path.basename(appLogSource.path)} is absent).`;
|
|
41
43
|
const userAction = surface.hints.launch;
|
|
42
44
|
if (json) {
|
|
@@ -55,7 +57,7 @@ async function handleLogs(argv) {
|
|
|
55
57
|
}
|
|
56
58
|
return EXIT.runtime;
|
|
57
59
|
}
|
|
58
|
-
const names =
|
|
60
|
+
const names = isAppSource && appLogSource ? path.basename(appLogSource.path) : logSources.map((entry) => path.basename(entry.path)).join(" / ");
|
|
59
61
|
return usageOut(
|
|
60
62
|
json,
|
|
61
63
|
"logs",
|
|
@@ -1,37 +1,29 @@
|
|
|
1
1
|
import { loadActionManifest, validateManifest } from "../manifest.js";
|
|
2
2
|
import { EXIT } from "./shared.js";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
isRecord,
|
|
6
|
-
optionFlag,
|
|
7
|
-
optionString,
|
|
8
|
-
resolveAdapter
|
|
9
|
-
} from "./parse-args.js";
|
|
10
|
-
async function handleManifest({ options }) {
|
|
11
|
-
const { adapter } = resolveAdapter(options);
|
|
12
|
-
const actionManifestPath = actionManifestPathOption(options, adapter);
|
|
13
|
-
const manifest = loadActionManifest(adapter, optionString(options, "actionManifest"));
|
|
14
|
-
await validateManifest(manifest);
|
|
15
|
-
if (optionFlag(options, "json")) console.log(JSON.stringify(manifest, null, 2));
|
|
16
|
-
else console.log(actionManifestPath);
|
|
17
|
-
return 0;
|
|
18
|
-
}
|
|
19
|
-
async function handleActions({ options }) {
|
|
3
|
+
import { isRecord, optionFlag, optionString, resolveAdapter } from "./parse-args.js";
|
|
4
|
+
async function handleActions({ options, positional }) {
|
|
20
5
|
const { adapter } = resolveAdapter(options);
|
|
21
6
|
const manifest = loadActionManifest(adapter, optionString(options, "actionManifest"));
|
|
22
7
|
await validateManifest(manifest);
|
|
8
|
+
if (optionFlag(options, "raw")) {
|
|
9
|
+
console.log(JSON.stringify(manifest, null, 2));
|
|
10
|
+
return EXIT.ok;
|
|
11
|
+
}
|
|
23
12
|
const json = optionFlag(options, "json");
|
|
24
13
|
const action = optionString(options, "action");
|
|
14
|
+
const query = positional[0]?.trim();
|
|
25
15
|
const category = optionString(options, "category")?.toLowerCase();
|
|
26
16
|
const categoriesOnly = optionFlag(options, "categories");
|
|
27
17
|
const all = describeManifestActions(manifest);
|
|
28
18
|
const categories = summarizeActionCategories(all);
|
|
29
19
|
if (categoriesOnly && (action || category)) {
|
|
30
20
|
const message = "--categories cannot be combined with --action or --category.";
|
|
21
|
+
const userAction = `mm-harness actions --adapter ${adapter} --categories`;
|
|
31
22
|
if (json) {
|
|
32
|
-
console.log(JSON.stringify({ schemaVersion: 1, command: "actions", adapter, error: { code: "ACTION_FILTER_CONFLICT", message } }, null, 2));
|
|
23
|
+
console.log(JSON.stringify({ schemaVersion: 1, command: "actions", adapter, error: { code: "ACTION_FILTER_CONFLICT", message, userAction } }, null, 2));
|
|
33
24
|
} else {
|
|
34
|
-
console.error(`\u2717 mm-harness actions: ${message}
|
|
25
|
+
console.error(`\u2717 mm-harness actions: ${message}
|
|
26
|
+
Next: ${userAction}`);
|
|
35
27
|
}
|
|
36
28
|
return EXIT.usage;
|
|
37
29
|
}
|
|
@@ -52,10 +44,10 @@ async function handleActions({ options }) {
|
|
|
52
44
|
}
|
|
53
45
|
return EXIT.usage;
|
|
54
46
|
}
|
|
55
|
-
const actions = action ? fuzzyResolveActions(categoryActions, action) : categoryActions;
|
|
47
|
+
const actions = action ? fuzzyResolveActions(categoryActions, action) : query ? searchActions(categoryActions, query) : categoryActions;
|
|
56
48
|
if (action && actions.length === 0) {
|
|
57
49
|
const message = `no action matches "${action}" for the ${adapter} adapter.`;
|
|
58
|
-
const userAction = `mm-harness actions --adapter ${adapter}
|
|
50
|
+
const userAction = `mm-harness actions --adapter ${adapter}`;
|
|
59
51
|
if (json) {
|
|
60
52
|
console.log(JSON.stringify({ schemaVersion: 1, command: "actions", adapter, action, category, error: { code: "ACTION_UNKNOWN", message, userAction } }, null, 2));
|
|
61
53
|
} else {
|
|
@@ -64,24 +56,70 @@ async function handleActions({ options }) {
|
|
|
64
56
|
}
|
|
65
57
|
return EXIT.usage;
|
|
66
58
|
}
|
|
59
|
+
if (query && actions.length === 0) {
|
|
60
|
+
const message = `no action matches search "${query}" for the ${adapter} adapter.`;
|
|
61
|
+
const userAction = `mm-harness actions --adapter ${adapter} --categories`;
|
|
62
|
+
if (json) {
|
|
63
|
+
console.log(JSON.stringify({
|
|
64
|
+
schemaVersion: 1,
|
|
65
|
+
command: "actions",
|
|
66
|
+
adapter,
|
|
67
|
+
query,
|
|
68
|
+
category,
|
|
69
|
+
error: { code: "ACTION_SEARCH_EMPTY", message, userAction }
|
|
70
|
+
}, null, 2));
|
|
71
|
+
} else {
|
|
72
|
+
console.error(`\u2717 mm-harness actions: ${message}
|
|
73
|
+
Next: ${userAction}`);
|
|
74
|
+
}
|
|
75
|
+
return EXIT.usage;
|
|
76
|
+
}
|
|
77
|
+
const relatedActions = action && actions.length === 1 ? findRelatedActions(all, actions[0]) : void 0;
|
|
67
78
|
if (json) {
|
|
68
|
-
console.log(JSON.stringify({
|
|
79
|
+
console.log(JSON.stringify({
|
|
80
|
+
schemaVersion: 1,
|
|
81
|
+
command: "actions",
|
|
82
|
+
adapter,
|
|
83
|
+
...query ? { query } : {},
|
|
84
|
+
category,
|
|
85
|
+
actions,
|
|
86
|
+
...relatedActions ? { relatedActions } : {}
|
|
87
|
+
}, null, 2));
|
|
69
88
|
} else {
|
|
70
89
|
for (const entry of actions) {
|
|
71
90
|
const fields = entry.fields.length ? ` fields=${entry.fields.join(",")}` : "";
|
|
72
91
|
console.log(`${entry.name} (${entry.kind})${fields}${entry.description ? ` \u2014 ${entry.description}` : ""}`);
|
|
73
92
|
}
|
|
93
|
+
if (relatedActions) console.log(`Related: ${relatedActions.join(", ")}`);
|
|
74
94
|
}
|
|
75
95
|
return 0;
|
|
76
96
|
}
|
|
77
97
|
function fuzzyResolveActions(entries, query) {
|
|
78
98
|
const exactFull = entries.filter((e) => e.name === query);
|
|
79
99
|
if (exactFull.length > 0) return exactFull;
|
|
80
|
-
const
|
|
81
|
-
const exactSegment = entries.filter((e) =>
|
|
100
|
+
const finalSegment2 = (name) => name.split(".").pop() ?? name;
|
|
101
|
+
const exactSegment = entries.filter((e) => finalSegment2(e.name) === query);
|
|
82
102
|
if (exactSegment.length > 0) return exactSegment;
|
|
83
|
-
return entries.filter((e) =>
|
|
103
|
+
return entries.filter((e) => finalSegment2(e.name).includes(query));
|
|
84
104
|
}
|
|
105
|
+
function searchActions(entries, query) {
|
|
106
|
+
const terms = searchTerms(query);
|
|
107
|
+
if (terms.length === 0) return [];
|
|
108
|
+
return entries.map((entry) => {
|
|
109
|
+
const scores = terms.map((term) => actionSearchScore(entry, term));
|
|
110
|
+
return { entry, scores, score: scores.reduce((total, value) => total + value, 0) };
|
|
111
|
+
}).filter(({ score, scores }) => score > 0 && scores.every((value) => value > 0)).sort((left, right) => right.score - left.score || left.entry.name.localeCompare(right.entry.name)).map(({ entry }) => entry);
|
|
112
|
+
}
|
|
113
|
+
function findRelatedActions(entries, selected, limit = 5) {
|
|
114
|
+
const selectedNameTerms = searchTerms(finalSegment(selected.name).replaceAll("_", " "));
|
|
115
|
+
return entries.filter((entry) => entry.name !== selected.name).map((entry) => {
|
|
116
|
+
const nameTerms = new Set(searchTerms(finalSegment(entry.name).replaceAll("_", " ")));
|
|
117
|
+
const sharedScore = selectedNameTerms.filter((term) => nameTerms.has(term)).reduce((score2, term) => score2 + (GENERIC_OPERATION_TERMS.has(term) ? 5 : 30), 0);
|
|
118
|
+
const score = (entry.category === selected.category ? 100 : 0) + sharedScore;
|
|
119
|
+
return { name: entry.name, score };
|
|
120
|
+
}).filter(({ score }) => score > 0).sort((left, right) => right.score - left.score || left.name.localeCompare(right.name)).slice(0, limit).map(({ name }) => name);
|
|
121
|
+
}
|
|
122
|
+
const GENERIC_OPERATION_TERMS = /* @__PURE__ */ new Set(["assert", "call", "close", "ensure", "place", "read", "start", "teardown"]);
|
|
85
123
|
function summarizeActionCategories(actions) {
|
|
86
124
|
const counts = /* @__PURE__ */ new Map();
|
|
87
125
|
for (const action of actions) counts.set(action.category, (counts.get(action.category) ?? 0) + 1);
|
|
@@ -114,22 +152,69 @@ function describeManifestAction(name, kind, metadata) {
|
|
|
114
152
|
return {
|
|
115
153
|
name,
|
|
116
154
|
kind,
|
|
117
|
-
category: actionCategory(name),
|
|
155
|
+
category: actionCategory(name, record.category),
|
|
118
156
|
description: typeof record.description === "string" ? record.description : "",
|
|
119
157
|
fields: properties,
|
|
120
158
|
schema,
|
|
121
159
|
examples: record.examples
|
|
122
160
|
};
|
|
123
161
|
}
|
|
124
|
-
function actionCategory(name) {
|
|
162
|
+
function actionCategory(name, configured) {
|
|
163
|
+
if (typeof configured === "string" && configured.trim()) return configured.trim().toLowerCase();
|
|
125
164
|
const segments = name.split(".");
|
|
126
165
|
if (segments[0] === "metamask" && segments.length > 2) return segments[1] ?? "metamask";
|
|
127
|
-
|
|
166
|
+
if (segments[0] === "app" || segments[0] === "cdp") return "runtime";
|
|
167
|
+
if (segments[0] === "ui") return "ui";
|
|
168
|
+
if (name.startsWith("assert_")) return "assertion";
|
|
169
|
+
if (name === "watch_logs" || name === "index_artifacts") return "evidence";
|
|
170
|
+
if (name === "command" || name === "wait" || name === "call" || name === "end") return "control";
|
|
171
|
+
return segments.length > 1 ? segments[0] || "utility" : "utility";
|
|
172
|
+
}
|
|
173
|
+
function actionSearchScore(entry, term) {
|
|
174
|
+
const name = entry.name.toLowerCase();
|
|
175
|
+
const segment = finalSegment(name);
|
|
176
|
+
const nameTerms = searchTerms(name.replaceAll(".", " ").replaceAll("_", " "));
|
|
177
|
+
const fields = entry.fields.map((field) => field.toLowerCase());
|
|
178
|
+
const description = entry.description.toLowerCase();
|
|
179
|
+
if (name === term) return 120;
|
|
180
|
+
if (segment === term) return 110;
|
|
181
|
+
if (entry.category === term) return 100;
|
|
182
|
+
if (fields.includes(term)) return 90;
|
|
183
|
+
if (nameTerms.includes(term)) return 80;
|
|
184
|
+
if (name.includes(term)) return 70;
|
|
185
|
+
if (fields.some((field) => field.includes(term))) return 60;
|
|
186
|
+
if (description.includes(term)) return 50;
|
|
187
|
+
if ([...nameTerms, ...fields].some((candidate) => fuzzyTermMatch(term, candidate))) return 40;
|
|
188
|
+
return 0;
|
|
189
|
+
}
|
|
190
|
+
function fuzzyTermMatch(term, candidate) {
|
|
191
|
+
if (term.length < 4 || candidate.length < 4) return false;
|
|
192
|
+
const threshold = Math.max(1, Math.floor(Math.max(term.length, candidate.length) / 4));
|
|
193
|
+
return levenshtein(term, candidate) <= threshold;
|
|
194
|
+
}
|
|
195
|
+
function searchTerms(value) {
|
|
196
|
+
return value.toLowerCase().match(/[a-z0-9]+/gu) ?? [];
|
|
197
|
+
}
|
|
198
|
+
function finalSegment(name) {
|
|
199
|
+
return name.split(".").pop() ?? name;
|
|
200
|
+
}
|
|
201
|
+
function levenshtein(left, right) {
|
|
202
|
+
const prior = Array.from({ length: right.length + 1 }, (_, index) => index);
|
|
203
|
+
for (let leftIndex = 1; leftIndex <= left.length; leftIndex += 1) {
|
|
204
|
+
const current = [leftIndex];
|
|
205
|
+
for (let rightIndex = 1; rightIndex <= right.length; rightIndex += 1) {
|
|
206
|
+
const substitution = (prior[rightIndex - 1] ?? 0) + (left[leftIndex - 1] === right[rightIndex - 1] ? 0 : 1);
|
|
207
|
+
current[rightIndex] = Math.min((current[rightIndex - 1] ?? 0) + 1, (prior[rightIndex] ?? 0) + 1, substitution);
|
|
208
|
+
}
|
|
209
|
+
for (let index = 0; index < current.length; index += 1) prior[index] = current[index] ?? 0;
|
|
210
|
+
}
|
|
211
|
+
return prior[right.length] ?? right.length;
|
|
128
212
|
}
|
|
129
213
|
export {
|
|
130
214
|
describeManifestActions,
|
|
215
|
+
findRelatedActions,
|
|
131
216
|
fuzzyResolveActions,
|
|
132
217
|
handleActions,
|
|
133
|
-
|
|
218
|
+
searchActions,
|
|
134
219
|
summarizeActionCategories
|
|
135
220
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { detectAdapter } from "../harness.js";
|
|
2
2
|
import { assertAdapter } from "../paths.js";
|
|
3
3
|
import { getAdapterSurface } from "../adapters/surface.js";
|
|
4
|
-
import { ADAPTER_DETECT_NEXT, usageOut } from "./shared.js";
|
|
4
|
+
import { ADAPTER_DETECT_NEXT, usageOut, writeInteractiveProgress } from "./shared.js";
|
|
5
5
|
import {
|
|
6
6
|
applyWatcherPortOption,
|
|
7
7
|
optionFlag,
|
|
@@ -25,9 +25,13 @@ async function handleProvision({ positional, options, rawArgv }) {
|
|
|
25
25
|
}
|
|
26
26
|
const surface = getAdapterSurface(adapter);
|
|
27
27
|
const rerunCommand = provisionRerunCommand(rawArgv, options, adapter, target);
|
|
28
|
+
const platform = optionString(options, "platform") ?? optionString(options, "devicePlatform") ?? (positional[0] === "runway" ? positional[1] : positional[0]) ?? "ios";
|
|
29
|
+
if (adapter === "mobile") {
|
|
30
|
+
writeInteractiveProgress(json, `\u2192 provision mobile ${platform} \u2014 resolving simulator and Runway artifact`);
|
|
31
|
+
}
|
|
28
32
|
const result = await surface.runwayProvision.run(target, {
|
|
29
33
|
json,
|
|
30
|
-
platform
|
|
34
|
+
platform,
|
|
31
35
|
branch: optionString(options, "branch"),
|
|
32
36
|
defaultBranch: optionString(options, "defaultBranch"),
|
|
33
37
|
run: optionString(options, "run"),
|
|
@@ -42,8 +46,10 @@ async function handleProvision({ positional, options, rawArgv }) {
|
|
|
42
46
|
resolveOnly: optionFlag(options, "resolveOnly"),
|
|
43
47
|
rerunCommand
|
|
44
48
|
});
|
|
49
|
+
const next = result.status === "pass" && adapter === "mobile" && result.resolveOnly !== true ? `mm-harness launch ${String(result.platform ?? platform)} --adapter mobile --target ${shellQuote(target)}` : void 0;
|
|
50
|
+
const output = next ? { ...result, next } : result;
|
|
45
51
|
if (json) {
|
|
46
|
-
console.log(JSON.stringify(
|
|
52
|
+
console.log(JSON.stringify(output, null, 2));
|
|
47
53
|
} else if (result.status === "pass") {
|
|
48
54
|
const cache = typeof result.cache === "object" && result.cache ? result.cache : void 0;
|
|
49
55
|
const simulator = typeof result.simulator === "object" && result.simulator ? result.simulator : void 0;
|
|
@@ -54,6 +60,7 @@ async function handleProvision({ positional, options, rawArgv }) {
|
|
|
54
60
|
const action = result.skipped ? "already provisioned" : "provisioned";
|
|
55
61
|
console.error(`\u2713 ${action} ${adapter} ${result.platform ?? ""} simulator=${simulator?.name ?? "unknown"} cache=${cache?.status ?? "unknown"}`);
|
|
56
62
|
}
|
|
63
|
+
if (next) console.error(` Next: ${next}`);
|
|
57
64
|
} else {
|
|
58
65
|
console.error(`\u2717 mm-harness provision: ${result.error?.message ?? "provision failed"}
|
|
59
66
|
Next: ${result.error?.userAction ?? rerunCommand}`);
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
optionString,
|
|
32
32
|
isRecord,
|
|
33
33
|
shellQuoteArg,
|
|
34
|
+
targetPath,
|
|
34
35
|
usageError
|
|
35
36
|
} from "./parse-args.js";
|
|
36
37
|
async function runRecipe(adapter, recipe, artifactsDir, projectRoot, actionManifestPath, runtimeOptions = {}) {
|
|
@@ -66,7 +67,8 @@ async function runRecipe(adapter, recipe, artifactsDir, projectRoot, actionManif
|
|
|
66
67
|
await validateManifest(manifest);
|
|
67
68
|
const { createMetaMaskRunner } = await import("../runner.js");
|
|
68
69
|
const runner = await createMetaMaskRunner(adapter, manifest, {
|
|
69
|
-
quietStdout: runtimeOptions.stdoutIsMachineContract === true
|
|
70
|
+
quietStdout: runtimeOptions.stdoutIsMachineContract === true,
|
|
71
|
+
onActionEvent: runtimeOptions.onActionEvent
|
|
70
72
|
});
|
|
71
73
|
const runRequest = {
|
|
72
74
|
recipePath: path.resolve(recipe),
|
|
@@ -198,12 +200,7 @@ function libraryRecipeNames(adapter) {
|
|
|
198
200
|
return [...new Set(names)].sort();
|
|
199
201
|
}
|
|
200
202
|
function recipeNameCandidates(recipeArg, adapter) {
|
|
201
|
-
|
|
202
|
-
const candidates = [];
|
|
203
|
-
for (const name of names) {
|
|
204
|
-
candidates.push(...singleRecipeNameCandidates(name, adapter));
|
|
205
|
-
}
|
|
206
|
-
return [...new Set(candidates)];
|
|
203
|
+
return singleRecipeNameCandidates(recipeArg, adapter);
|
|
207
204
|
}
|
|
208
205
|
function singleRecipeNameCandidates(name, adapter) {
|
|
209
206
|
if (name.endsWith(".recipe.json")) {
|
|
@@ -229,32 +226,6 @@ function domainPathForRecipeName(name) {
|
|
|
229
226
|
function uniqueStrings(values) {
|
|
230
227
|
return [...new Set(values)];
|
|
231
228
|
}
|
|
232
|
-
const LEGACY_RECIPE_ALIASES = {
|
|
233
|
-
"action-validation": ["runner.action-validation"],
|
|
234
|
-
"action-validation.extension.recipe.json": ["runner.action-validation.extension.recipe.json"],
|
|
235
|
-
"action-validation.mobile.recipe.json": ["runner.action-validation.mobile.recipe.json"],
|
|
236
|
-
"app-lifecycle-android-smoke": ["app.lifecycle.android-smoke"],
|
|
237
|
-
"app-lifecycle-android-smoke.mobile.recipe.json": ["app.lifecycle.android-smoke.mobile.recipe.json"],
|
|
238
|
-
"order-lifecycle": ["perps.order-lifecycle"],
|
|
239
|
-
"order-lifecycle.core.recipe.json": ["perps.order-lifecycle.core.recipe.json"],
|
|
240
|
-
"perps-lifecycle": ["perps.lifecycle"],
|
|
241
|
-
"perps-lifecycle.recipe.json": ["perps.lifecycle.recipe.json"],
|
|
242
|
-
"perps-performance": ["perps.performance"],
|
|
243
|
-
"perps-performance-background-resume": ["perps.performance.background-resume"],
|
|
244
|
-
"perps-performance-background-resume.mobile.recipe.json": ["perps.performance.background-resume.mobile.recipe.json"],
|
|
245
|
-
"perps-performance-cold-start": ["perps.performance.cold-start"],
|
|
246
|
-
"perps-performance-cold-start.mobile.recipe.json": ["perps.performance.cold-start.mobile.recipe.json"],
|
|
247
|
-
"perps-performance-warm-start": ["perps.performance.warm-start"],
|
|
248
|
-
"perps-performance-warm-start.mobile.recipe.json": ["perps.performance.warm-start.mobile.recipe.json"],
|
|
249
|
-
"perps-performance.mobile.recipe.json": ["perps.performance.mobile.recipe.json"],
|
|
250
|
-
"read-markets": ["perps.read-markets"],
|
|
251
|
-
"read-markets.core.recipe.json": ["perps.read-markets.core.recipe.json"],
|
|
252
|
-
"smoke": ["runner.smoke"],
|
|
253
|
-
"smoke.extension.recipe.json": ["runner.smoke.extension.recipe.json"],
|
|
254
|
-
"smoke.mobile.recipe.json": ["runner.smoke.mobile.recipe.json"],
|
|
255
|
-
"trading-lifecycle": ["perps.trading-lifecycle"],
|
|
256
|
-
"trading-lifecycle.core.recipe.json": ["perps.trading-lifecycle.core.recipe.json"]
|
|
257
|
-
};
|
|
258
229
|
async function validateRunRecipeStatic(recipeArg, adapter, options) {
|
|
259
230
|
const librarySources = await resolveMetaMaskLibrarySources(optionString(options, "library"));
|
|
260
231
|
const resolved = resolveRunRecipeArg(recipeArg, adapter, librarySources);
|
|
@@ -348,6 +319,7 @@ async function actionInsteadOfRecipeHint(recipeArg, adapter, options) {
|
|
|
348
319
|
const device = optionString(options, "device");
|
|
349
320
|
const actionManifest = optionString(options, "actionManifest");
|
|
350
321
|
const parts = ["mm-harness", "call", matches[0]];
|
|
322
|
+
parts.push("--adapter", adapter, "--target", shellQuoteArg(targetPath(options)));
|
|
351
323
|
if (device) parts.push("--device", shellQuoteArg(device));
|
|
352
324
|
if (actionManifest) parts.push("--action-manifest", shellQuoteArg(actionManifest));
|
|
353
325
|
return ` This is an action, not a recipe. Use: ${parts.join(" ")}.`;
|
|
@@ -405,10 +377,12 @@ async function runOneNode(adapter, action, args, target, actionManifest) {
|
|
|
405
377
|
async function prepareHeal(adapter, target, options, json, prepareOptions = {}) {
|
|
406
378
|
if (recipeRunning(target)) {
|
|
407
379
|
const msg = "a recipe is currently running \u2014 refusing to start while another recipe executes.";
|
|
380
|
+
const userAction = `inspect the checkout state with: mm-harness status --target ${shellQuoteArg(target)} --json; retry after the active recipe finishes`;
|
|
408
381
|
if (json) {
|
|
409
|
-
console.log(JSON.stringify({ schemaVersion: 1, status: "fail", recoverable: false, error: { code: "RECIPE_RUNNING", message: msg } }, null, 2));
|
|
382
|
+
console.log(JSON.stringify({ schemaVersion: 1, status: "fail", recoverable: false, error: { code: "RECIPE_RUNNING", message: msg, userAction } }, null, 2));
|
|
410
383
|
} else {
|
|
411
|
-
console.error(`\u2717 mm-harness: ${msg}
|
|
384
|
+
console.error(`\u2717 mm-harness: ${msg}
|
|
385
|
+
Next: ${userAction}`);
|
|
412
386
|
}
|
|
413
387
|
return EXIT.bounded;
|
|
414
388
|
}
|
|
@@ -421,21 +395,25 @@ async function prepareHeal(adapter, target, options, json, prepareOptions = {})
|
|
|
421
395
|
return EXIT.usage;
|
|
422
396
|
}
|
|
423
397
|
const state = newHealState();
|
|
398
|
+
prepareOptions.onPhase?.("install");
|
|
424
399
|
const ensured = await ensureOverlay(adapter, target, heal, state, json);
|
|
425
400
|
if (!ensured.ok) {
|
|
401
|
+
const userAction = `mm-harness install --adapter ${adapter} --target ${shellQuoteArg(target)}`;
|
|
426
402
|
if (json) {
|
|
427
403
|
console.log(
|
|
428
404
|
JSON.stringify(
|
|
429
|
-
{ schemaVersion: 1, status: "fail", recoverable: false, mutations: state.mutations, error: { code: "OVERLAY_INSTALL_FAILED", message: ensured.error } },
|
|
405
|
+
{ schemaVersion: 1, status: "fail", recoverable: false, mutations: state.mutations, error: { code: "OVERLAY_INSTALL_FAILED", message: ensured.error, userAction } },
|
|
430
406
|
null,
|
|
431
407
|
2
|
|
432
408
|
)
|
|
433
409
|
);
|
|
434
410
|
} else {
|
|
435
|
-
console.error(`\u2717 overlay auto-ensure failed: ${ensured.error}
|
|
411
|
+
console.error(`\u2717 overlay auto-ensure failed: ${ensured.error}
|
|
412
|
+
Next: ${userAction}`);
|
|
436
413
|
}
|
|
437
414
|
return EXIT.infra;
|
|
438
415
|
}
|
|
416
|
+
prepareOptions.onPhase?.("healthcheck");
|
|
439
417
|
if (adapter === "extension") {
|
|
440
418
|
const previousCdpPort = process.env.CDP_PORT;
|
|
441
419
|
const previousRecipeCdpPort = process.env.RECIPE_CDP_PORT;
|
|
@@ -448,7 +426,9 @@ async function prepareHeal(adapter, target, options, json, prepareOptions = {})
|
|
|
448
426
|
}
|
|
449
427
|
if (explicitWatcherPort) process.env.WATCHER_PORT = explicitWatcherPort;
|
|
450
428
|
try {
|
|
451
|
-
const current = await ensureExtensionProofRuntime(target, heal, state, json
|
|
429
|
+
const current = await ensureExtensionProofRuntime(target, heal, state, json, {
|
|
430
|
+
onRecovery: (code) => prepareOptions.onPhase?.("recover", { code })
|
|
431
|
+
});
|
|
452
432
|
if (current !== null) return current;
|
|
453
433
|
} finally {
|
|
454
434
|
restoreEnv("CDP_PORT", previousCdpPort);
|
|
@@ -457,7 +437,9 @@ async function prepareHeal(adapter, target, options, json, prepareOptions = {})
|
|
|
457
437
|
}
|
|
458
438
|
}
|
|
459
439
|
if (adapter === "mobile" && prepareOptions.skipMobileSourceFreshness !== true) {
|
|
460
|
-
const current = await ensureMobileProofRuntime(target, heal, state, json
|
|
440
|
+
const current = await ensureMobileProofRuntime(target, heal, state, json, {
|
|
441
|
+
onRecovery: (code) => prepareOptions.onPhase?.("recover", { code })
|
|
442
|
+
});
|
|
461
443
|
if (current !== null) return current;
|
|
462
444
|
}
|
|
463
445
|
return { state, heal };
|
|
@@ -502,6 +484,7 @@ async function ensureMobileProofRuntime(target, heal, state, json, deps = {}) {
|
|
|
502
484
|
}
|
|
503
485
|
const recoveryCode = "mobile.source_reloaded";
|
|
504
486
|
state.attemptedRecoveries.push(recoveryCode);
|
|
487
|
+
deps.onRecovery?.(recoveryCode);
|
|
505
488
|
if (!json) console.error(`\u2192 Mobile proof preflight: source ${initial.status}; restarting the app before execution`);
|
|
506
489
|
const restart = deps.restart ?? (async () => {
|
|
507
490
|
const result2 = await runOneNode(
|
|
@@ -588,6 +571,7 @@ async function ensureExtensionProofRuntime(target, heal, state, json, deps = {})
|
|
|
588
571
|
}
|
|
589
572
|
const recoveryCode = tier === "build" ? "extension.runtime_rebuilt" : "extension.runtime_reloaded";
|
|
590
573
|
state.attemptedRecoveries.push(recoveryCode);
|
|
574
|
+
deps.onRecovery?.(recoveryCode);
|
|
591
575
|
if (!json) console.error(`\u2192 Extension proof preflight: ${initial.reasonCode}; ${tier === "build" ? "rebuilding" : "reloading"} before execution`);
|
|
592
576
|
const launch = deps.launch ?? (async (requestedTier) => {
|
|
593
577
|
const { launchExtension } = await import("./launch/extension.js");
|
|
@@ -642,7 +626,7 @@ function readRunFailureText(result) {
|
|
|
642
626
|
return "";
|
|
643
627
|
}
|
|
644
628
|
}
|
|
645
|
-
async function executeWithHealBounds(exec, adapter, target, heal, state, recover = () => recoverRunInfra(adapter, target, false)) {
|
|
629
|
+
async function executeWithHealBounds(exec, adapter, target, heal, state, recover = () => recoverRunInfra(adapter, target, false), onRecovery = () => void 0) {
|
|
646
630
|
let result = await exec();
|
|
647
631
|
for (; ; ) {
|
|
648
632
|
if (result.status === "pass") return { result, violation: null };
|
|
@@ -651,6 +635,7 @@ async function executeWithHealBounds(exec, adapter, target, heal, state, recover
|
|
|
651
635
|
if (heal === "off") return { result, violation: null };
|
|
652
636
|
const recoveryCode = RUN_RECOVERY_CODE[adapter];
|
|
653
637
|
state.attemptedRecoveries.push(recoveryCode);
|
|
638
|
+
onRecovery(recoveryCode);
|
|
654
639
|
await recover();
|
|
655
640
|
result = await exec();
|
|
656
641
|
if (result.status === "pass") {
|
|
@@ -660,7 +645,7 @@ async function executeWithHealBounds(exec, adapter, target, heal, state, recover
|
|
|
660
645
|
}
|
|
661
646
|
}
|
|
662
647
|
function emitHealViolation(json, command, result, violation, state, adapter) {
|
|
663
|
-
const userAction = adapter === "core" && violation.code === "WALLET_STATE_REQUIRED" ? 'set MM_TEST_ACCOUNT_ADDRESS=<0x\u2026> in env, or add "account": "<0x\u2026>" to the node block in the recipe' : violation.userAction;
|
|
648
|
+
const userAction = adapter === "core" && violation.code === "WALLET_STATE_REQUIRED" ? 'set MM_TEST_ACCOUNT_ADDRESS=<0x\u2026> in env, or add "account": "<0x\u2026>" to the node block in the recipe' : violation.userAction ?? `inspect ${shellQuoteArg(result.summaryPath)} and ${shellQuoteArg(result.tracePath)}; fix the application or recipe failure before retrying`;
|
|
664
649
|
if (json) {
|
|
665
650
|
console.log(
|
|
666
651
|
JSON.stringify(
|
|
@@ -680,7 +665,7 @@ function emitHealViolation(json, command, result, violation, state, adapter) {
|
|
|
680
665
|
code: violation.code,
|
|
681
666
|
message: violation.message,
|
|
682
667
|
retryable: false,
|
|
683
|
-
userAction
|
|
668
|
+
userAction,
|
|
684
669
|
originalError: violation.originalError ?? null
|
|
685
670
|
}
|
|
686
671
|
},
|
|
@@ -34,7 +34,7 @@ function renderRunReport(summary, entries) {
|
|
|
34
34
|
lines.push(
|
|
35
35
|
"",
|
|
36
36
|
"## Side findings",
|
|
37
|
-
`- REVIEW ${sideFindingTotal} distinct application warning/error event(s);
|
|
37
|
+
`- REVIEW ${sideFindingTotal} distinct application warning/error event(s) (non-blocking; expanded below and stored in diagnostics.json)`
|
|
38
38
|
);
|
|
39
39
|
}
|
|
40
40
|
lines.push("", "## Steps");
|