@deeeed/metamask-harness 0.41.0 → 0.42.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 +24 -0
- package/README.md +7 -0
- package/adapters/manifest.json +25 -1
- package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +70 -10
- package/adapters/mobile/bridge-runtime/console-forwarder.cjs +115 -15
- package/adapters/mobile/bridge-runtime/lib/cdp-broker.cjs +752 -0
- package/adapters/mobile/bridge-runtime/lib/devtools-proxy.cjs +177 -0
- package/adapters/mobile/bridge-runtime/lib/target-discovery.cjs +13 -3
- package/adapters/mobile/coalesce-metro-log.cjs +24 -0
- package/adapters/mobile/launch-metro.cjs +9 -8
- package/adapters/mobile/metro-log-generation.cjs +106 -0
- package/adapters/mobile/reload-app.mjs +67 -0
- package/adapters/mobile/start-console-forwarder.sh +17 -2
- package/adapters/mobile/start-metro.sh +23 -18
- package/adapters/mobile/stop-metro.sh +15 -7
- package/adapters/shared/open-debug.mjs +172 -2
- package/adapters/shared/reap-checkout-metros.sh +17 -0
- package/dist/adapters/extension/network-observer.js +300 -0
- package/dist/adapters/mobile/metro-env.js +0 -5
- package/dist/adapters/mobile/prepare.js +1 -3
- package/dist/adapters/mobile/runtime-decision.js +6 -30
- package/dist/adapters.js +14 -1
- package/dist/cli-commands.js +6 -3
- package/dist/cli.js +4 -0
- package/dist/command-contract.js +3 -0
- package/dist/commands/call.js +45 -20
- package/dist/commands/launch/index.js +25 -5
- package/dist/commands/reload.js +80 -0
- package/dist/commands/run.js +49 -22
- package/dist/mm-harness-cli.js +17 -1
- package/dist/network-observation.js +271 -0
- package/docs/NETWORK-CAPTURE.md +98 -0
- package/docs/QA.md +2 -0
- package/docs/RECIPES.md +10 -0
- package/library/actions/mobile/app/network_assert.mjs +14 -0
- package/library/actions/mobile/app/network_capture.mjs +72 -0
- package/library/actions/mobile/platform/bridge.mjs +7 -2
- package/library/actions/shared/app/network-artifact.mjs +10 -0
- package/library/actions/shared/app/network-assert.mjs +154 -0
- package/library/manifests/extension.action-manifest.json +88 -0
- package/library/manifests/mobile.action-manifest.json +107 -0
- package/library/recipes/mobile/perps/performance.recipe.json +11 -11
- package/package.json +1 -1
- package/scripts/completions.sh +2 -1
package/dist/adapters.js
CHANGED
|
@@ -12,6 +12,7 @@ import { observeNativeUi } from "../library/actions/mobile/platform/observe-ui.m
|
|
|
12
12
|
import { nativeAgentDeviceStateDir } from "../library/actions/mobile/platform/native-session-name.mjs";
|
|
13
13
|
import { resolveMobileToolPath } from "../library/actions/mobile/platform/tool-paths.mjs";
|
|
14
14
|
import { resolveWalletImportCredentials, validateWalletImportOptions } from "../library/actions/shared/wallet/import-source.mjs";
|
|
15
|
+
import { handleRunNetworkAction } from "./network-observation.js";
|
|
15
16
|
const execFileAsync = promisify(execFile);
|
|
16
17
|
const NATIVE_PROVIDER_UI_ACTIONS = /* @__PURE__ */ new Set([
|
|
17
18
|
"ui.press",
|
|
@@ -103,7 +104,11 @@ const LIVE_ONLY_WALLET_ACTIONS = /* @__PURE__ */ new Set([
|
|
|
103
104
|
"metamask.wallet.list_accounts",
|
|
104
105
|
"metamask.wallet.read_state"
|
|
105
106
|
]);
|
|
106
|
-
const LIVE_ONLY_APP_ACTIONS = /* @__PURE__ */ new Set([
|
|
107
|
+
const LIVE_ONLY_APP_ACTIONS = /* @__PURE__ */ new Set([
|
|
108
|
+
"ui.navigate",
|
|
109
|
+
"app.network_capture",
|
|
110
|
+
"app.network_assert"
|
|
111
|
+
]);
|
|
107
112
|
function requiresLiveAdapter(platform, action) {
|
|
108
113
|
return LIVE_ONLY_ACTIONS.has(action) || platform === "core" && CORE_ONLY_PERPS_ACTIONS.has(action) || LIVE_ONLY_WALLET_ACTIONS.has(action) || LIVE_ONLY_APP_ACTIONS.has(action);
|
|
109
114
|
}
|
|
@@ -140,6 +145,13 @@ function liveAdapterPathHint(platform, action) {
|
|
|
140
145
|
return `library/actions/${platform}/${action.replaceAll(".", "/")}.mjs`;
|
|
141
146
|
}
|
|
142
147
|
async function semanticResult(platform, action, node, context, forceLive = false, preparedLiveAdapters) {
|
|
148
|
+
const networkAction = await handleRunNetworkAction(
|
|
149
|
+
platform,
|
|
150
|
+
action,
|
|
151
|
+
node,
|
|
152
|
+
context
|
|
153
|
+
);
|
|
154
|
+
if (networkAction) return networkAction;
|
|
143
155
|
const live = await runLiveFirst(
|
|
144
156
|
platform,
|
|
145
157
|
action,
|
|
@@ -253,6 +265,7 @@ function createMetaMaskSemanticAdapters(platform, declaredCustomActions = [], pr
|
|
|
253
265
|
];
|
|
254
266
|
const bundledActions = [
|
|
255
267
|
...walletActions,
|
|
268
|
+
...platform !== "core" ? ["app.network_capture", "app.network_assert"] : [],
|
|
256
269
|
"metamask.assets.read_visible_state",
|
|
257
270
|
"metamask.assets.open_details",
|
|
258
271
|
"metamask.assets.read_details",
|
package/dist/cli-commands.js
CHANGED
|
@@ -15,6 +15,7 @@ const SPEC = {
|
|
|
15
15
|
{ name: "sync", desc: "Refresh harness + canonicalize wallet fixture", flags: ["--json"] },
|
|
16
16
|
{ name: "logs", aliases: ["tail"], desc: "Compact build events or full log", flags: ["--full", "-f", "--window", "--events", "--source", "--json"] },
|
|
17
17
|
{ name: "debug", aliases: ["devtools", "inspect"], desc: "Open DevTools UI", flags: ["--json", "--no-open"] },
|
|
18
|
+
{ name: "reload", desc: "Reload the connected app runtime", flags: ["--json"] },
|
|
18
19
|
{ name: "fixtures", desc: "Manage the canonical wallet fixture (sync/set/reset/generate)", args: ["sync", "set", "reset", "generate"], flags: ["--fixture", "--out", "--adapter", "--target", "--device", "--json"] },
|
|
19
20
|
{ name: "actions", desc: "List runnable recipe actions", flags: ["--json", "--matrix", "--categories", "--category", "--action", "--library"] },
|
|
20
21
|
{ name: "doctor", desc: "Check harness/orchestration health", flags: ["--json", "--target", "--adapter", "--runtime-dir", "--expect-live", "--print-ready", "--cdp-port", "--device"] },
|
|
@@ -31,10 +32,11 @@ const SPEC = {
|
|
|
31
32
|
{ name: "ready", aliases: ["ensure-ready"], desc: "Ensure one healthy extension home tab" },
|
|
32
33
|
{ name: "watch", aliases: ["start-watch", "watcher"], desc: "Start/reuse webpack watcher", flags: ["--full", "-f"] },
|
|
33
34
|
{ name: "stop", aliases: ["stop-watch", "stop-watcher"], desc: "Stop this checkout watcher" },
|
|
34
|
-
{ name: "rebuild", aliases: ["reset", "reopen", "
|
|
35
|
+
{ name: "rebuild", aliases: ["reset", "reopen", "browser", "launch", "full-launch", "runtime-launch", "build", "build:once", "build-once", "refresh-once"], desc: "Clean webpack + browser launch", flags: ["--json", "--dry-run", "--full"] },
|
|
35
36
|
{ name: "sidepanel", desc: "Side panel helper", args: ["cycle", "open", "close", "toggle", "status"] },
|
|
36
37
|
{ name: "prepare", flags: ["--target", "--cdp-port", "--runtime-dir", "--validate"] },
|
|
37
|
-
{ name: "debug", args: ["page", "worker"], flags: ["--json", "--no-open"] }
|
|
38
|
+
{ name: "debug", args: ["page", "worker"], flags: ["--json", "--no-open"] },
|
|
39
|
+
{ name: "reload", desc: "Reload the live Extension UI over CDP", flags: ["--json"] }
|
|
38
40
|
],
|
|
39
41
|
mobile: [
|
|
40
42
|
{ name: "ios", aliases: ["start"], desc: "Start Metro + launch iOS dev client" },
|
|
@@ -53,7 +55,8 @@ const SPEC = {
|
|
|
53
55
|
{ name: "screenshot", desc: "Capture simulator/device screenshot", args: ["path"] },
|
|
54
56
|
{ name: "dev-menu", aliases: ["devmenu"], desc: "Open RN developer menu", flags: ["--json", "--no-open"] },
|
|
55
57
|
{ name: "prepare", flags: ["--target", "--platform", "--preflight-mode", "--port", "--simulator", "--adb-serial", "--runtime-dir", "--wallet-setup", "--wallet-fixture"] },
|
|
56
|
-
{ name: "debug", flags: ["--json", "--no-open", "--action"] }
|
|
58
|
+
{ name: "debug", flags: ["--json", "--no-open", "--action"] },
|
|
59
|
+
{ name: "reload", desc: "Reload the app without restarting Metro", flags: ["--json"] }
|
|
57
60
|
]
|
|
58
61
|
};
|
|
59
62
|
const GLOBAL_FLAGS = ["--json", "--dry-run", "--full", "-f", "--help", "-h", "--no-open", "--no-color"];
|
package/dist/cli.js
CHANGED
|
@@ -15,6 +15,7 @@ import { handleCompletionCandidates, invalidateCompletionCache } from "./command
|
|
|
15
15
|
import { handleLaunch } from "./commands/launch/index.js";
|
|
16
16
|
import { handleLogs } from "./commands/logs.js";
|
|
17
17
|
import { handleDebug } from "./commands/debug.js";
|
|
18
|
+
import { handleReload } from "./commands/reload.js";
|
|
18
19
|
import { handleFixtures } from "./commands/fixtures.js";
|
|
19
20
|
import { handleRecipeQuality } from "./commands/recipe-quality.js";
|
|
20
21
|
import { handleStatus } from "./commands/status.js";
|
|
@@ -47,6 +48,8 @@ DAILY LOOP \u2014 what a teammate runs many times a day:
|
|
|
47
48
|
mm-harness logs
|
|
48
49
|
debug Open the debug console (extension DevTools / mobile RN).
|
|
49
50
|
mm-harness debug
|
|
51
|
+
reload Reload the connected Mobile or Extension runtime.
|
|
52
|
+
mm-harness reload
|
|
50
53
|
fixtures Sync files, set/reset the wallet, generate fixture-state, or finalize labels over CDP.
|
|
51
54
|
mm-harness fixtures sync # or: set | reset | generate --fixture <f> --out <o> | finalize \u2026
|
|
52
55
|
|
|
@@ -118,6 +121,7 @@ async function main(argv) {
|
|
|
118
121
|
if (command === "stop") return handleStop(argv.slice(1));
|
|
119
122
|
if (command === "logs") return handleLogs(argv.slice(1));
|
|
120
123
|
if (command === "debug") return handleDebug(argv.slice(1));
|
|
124
|
+
if (command === "reload") return handleReload(argv.slice(1));
|
|
121
125
|
if (command === "fixtures") return handleFixtures(argv.slice(1));
|
|
122
126
|
if (command === "recipe-quality") return handleRecipeQuality(argv.slice(1));
|
|
123
127
|
if (command === "check") return handleCheck(argv.slice(1));
|
package/dist/command-contract.js
CHANGED
package/dist/commands/call.js
CHANGED
|
@@ -43,6 +43,9 @@ import {
|
|
|
43
43
|
import { readMobileReleaseArtifactState } from "../adapters/mobile/release-artifact-state.js";
|
|
44
44
|
import { acquireCheckoutLock } from "../checkout-lock.js";
|
|
45
45
|
import { formatRunDiagnosticsForHuman, readRunDiagnosticsDocument } from "../run-diagnostics.js";
|
|
46
|
+
import {
|
|
47
|
+
startRunNetworkObservation
|
|
48
|
+
} from "../network-observation.js";
|
|
46
49
|
import { recipeTrustFailure } from "../recipe-security.js";
|
|
47
50
|
import { isSensitiveKey, recordCommandEvidence, redactStructuredValue } from "../command-journal.js";
|
|
48
51
|
import { closest } from "../command-contract.js";
|
|
@@ -209,12 +212,14 @@ async function handleCall(argv) {
|
|
|
209
212
|
recordCommandEvidence(artifactsDir);
|
|
210
213
|
const requestedRuntimeOptions = runtimeOptionsFromCli(options);
|
|
211
214
|
const inheritedSource = process.env.FARMSLOT_RECIPE_SOURCE_TRUST || process.env.FARMSLOT_RECIPE_SOURCE_KIND || process.env.FARMSLOT_RECIPE_SOURCE_NAME || process.env.FARMSLOT_RECIPE_SOURCE_DIGEST;
|
|
215
|
+
let networkObservation;
|
|
212
216
|
const callRuntimeOptions = {
|
|
213
217
|
...requestedRuntimeOptions,
|
|
214
218
|
...librarySources ? { librarySources } : {},
|
|
215
219
|
autoHud: false,
|
|
216
220
|
suppressLibraryResolutionLogs: true,
|
|
217
221
|
stdoutIsMachineContract: json,
|
|
222
|
+
onActionEvent: ({ nodeId, action, status }) => networkObservation?.onActionEvent({ nodeId, action, status }),
|
|
218
223
|
...requestedRuntimeOptions.source ? { source: requestedRuntimeOptions.source } : inheritedSource ? {} : {
|
|
219
224
|
source: {
|
|
220
225
|
kind: "operator",
|
|
@@ -275,29 +280,49 @@ async function handleCall(argv) {
|
|
|
275
280
|
});
|
|
276
281
|
if (typeof prepared === "number") return prepared;
|
|
277
282
|
const { state, heal } = prepared;
|
|
278
|
-
|
|
279
|
-
() => {
|
|
280
|
-
const execution = preflightedExecution;
|
|
281
|
-
preflightedExecution = void 0;
|
|
282
|
-
return runRecipe(
|
|
283
|
-
adapter,
|
|
284
|
-
recipe,
|
|
285
|
-
artifactsDir,
|
|
286
|
-
target,
|
|
287
|
-
actionManifestOverride,
|
|
288
|
-
callRuntimeOptions,
|
|
289
|
-
execution
|
|
290
|
-
);
|
|
291
|
-
},
|
|
283
|
+
networkObservation = await startRunNetworkObservation(
|
|
292
284
|
adapter,
|
|
293
285
|
target,
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
cdpPort:
|
|
298
|
-
watcherPort:
|
|
299
|
-
}
|
|
286
|
+
artifactsDir,
|
|
287
|
+
process.env,
|
|
288
|
+
{
|
|
289
|
+
cdpPort: callRuntimeOptions.cdpPort,
|
|
290
|
+
watcherPort: callRuntimeOptions.watcherPort
|
|
291
|
+
}
|
|
300
292
|
);
|
|
293
|
+
let executionResult;
|
|
294
|
+
try {
|
|
295
|
+
executionResult = await executeWithHealBounds(
|
|
296
|
+
() => {
|
|
297
|
+
const execution = preflightedExecution;
|
|
298
|
+
preflightedExecution = void 0;
|
|
299
|
+
return runRecipe(
|
|
300
|
+
adapter,
|
|
301
|
+
recipe,
|
|
302
|
+
artifactsDir,
|
|
303
|
+
target,
|
|
304
|
+
actionManifestOverride,
|
|
305
|
+
callRuntimeOptions,
|
|
306
|
+
execution
|
|
307
|
+
);
|
|
308
|
+
},
|
|
309
|
+
adapter,
|
|
310
|
+
target,
|
|
311
|
+
heal,
|
|
312
|
+
state,
|
|
313
|
+
() => recoverRunInfra(adapter, target, json, {
|
|
314
|
+
cdpPort: optionString(options, "cdpPort"),
|
|
315
|
+
watcherPort: optionString(options, "watcherPort") ?? optionString(options, "metroPort")
|
|
316
|
+
})
|
|
317
|
+
);
|
|
318
|
+
} catch (error) {
|
|
319
|
+
await networkObservation?.finalize();
|
|
320
|
+
networkObservation = void 0;
|
|
321
|
+
throw error;
|
|
322
|
+
}
|
|
323
|
+
const { result, violation } = executionResult;
|
|
324
|
+
await networkObservation?.finalize(result.artifactManifestPath);
|
|
325
|
+
networkObservation = void 0;
|
|
301
326
|
if (violation !== null) {
|
|
302
327
|
const conciseFailure = violation.originalError ? conciseFailureForHuman(violation.originalError) : "";
|
|
303
328
|
const example = describedAction ? actionExampleCommand(
|
|
@@ -53,6 +53,21 @@ const LAUNCH_BOOLEANS = /* @__PURE__ */ new Set([
|
|
|
53
53
|
"jsonStream"
|
|
54
54
|
]);
|
|
55
55
|
const DEFAULT_EXTENSION_DAPP_URL = "https://metamask.github.io/test-dapp/";
|
|
56
|
+
function metroLaunchEvidence(adapter, target) {
|
|
57
|
+
if (adapter !== "mobile") return void 0;
|
|
58
|
+
const evidencePath = recipeRuntimePath(target, "metro-generation.json");
|
|
59
|
+
let descriptor;
|
|
60
|
+
try {
|
|
61
|
+
descriptor = fs.openSync(evidencePath, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
|
|
62
|
+
const stat = fs.fstatSync(descriptor);
|
|
63
|
+
if (!stat.isFile() || stat.size > 256 * 1024) return void 0;
|
|
64
|
+
return JSON.parse(fs.readFileSync(descriptor, "utf8"));
|
|
65
|
+
} catch {
|
|
66
|
+
return void 0;
|
|
67
|
+
} finally {
|
|
68
|
+
if (descriptor !== void 0) fs.closeSync(descriptor);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
56
71
|
async function handleLaunch(argv) {
|
|
57
72
|
const { options } = parseFlags(argv, LAUNCH_BOOLEANS);
|
|
58
73
|
const json = flag(options, "json");
|
|
@@ -627,14 +642,15 @@ async function finishLaunch(jsonOutput, machine, stream, adapter, mobileTarget,
|
|
|
627
642
|
platform: mobileTarget ?? (adapter === "extension" ? displayMode : null),
|
|
628
643
|
tier,
|
|
629
644
|
recovered: state.recovered,
|
|
630
|
-
mutations: state.mutations
|
|
645
|
+
mutations: state.mutations,
|
|
646
|
+
metro: metroLaunchEvidence(adapter, target)
|
|
631
647
|
});
|
|
632
648
|
}
|
|
633
649
|
return exitCode;
|
|
634
650
|
}
|
|
635
|
-
return launchPass(jsonOutput, stream, adapter, mobileTarget, tier, displayMode, state);
|
|
651
|
+
return launchPass(jsonOutput, stream, adapter, mobileTarget, tier, displayMode, target, state);
|
|
636
652
|
}
|
|
637
|
-
function launchPass(json, stream, adapter, mobileTarget, tier, displayMode, state) {
|
|
653
|
+
function launchPass(json, stream, adapter, mobileTarget, tier, displayMode, target, state) {
|
|
638
654
|
if (stream.enabled) {
|
|
639
655
|
for (const mutation of state.mutations) stream.mutation(mutation);
|
|
640
656
|
for (const recovery of state.recovered) stream.recovery(recovery);
|
|
@@ -643,7 +659,8 @@ function launchPass(json, stream, adapter, mobileTarget, tier, displayMode, stat
|
|
|
643
659
|
platform: mobileTarget ?? (adapter === "extension" ? displayMode : null),
|
|
644
660
|
tier,
|
|
645
661
|
recovered: state.recovered,
|
|
646
|
-
mutations: state.mutations
|
|
662
|
+
mutations: state.mutations,
|
|
663
|
+
metro: metroLaunchEvidence(adapter, target)
|
|
647
664
|
});
|
|
648
665
|
} else if (json) {
|
|
649
666
|
console.log(
|
|
@@ -658,6 +675,7 @@ function launchPass(json, stream, adapter, mobileTarget, tier, displayMode, stat
|
|
|
658
675
|
phase: "launch",
|
|
659
676
|
recovered: state.recovered,
|
|
660
677
|
mutations: state.mutations,
|
|
678
|
+
metro: metroLaunchEvidence(adapter, target),
|
|
661
679
|
exitCode: EXIT.ok
|
|
662
680
|
},
|
|
663
681
|
null,
|
|
@@ -706,7 +724,8 @@ function launchFail(json, stream, adapter, mobileTarget, tier, state, target, fa
|
|
|
706
724
|
recovered: state.recovered,
|
|
707
725
|
mutations: state.mutations,
|
|
708
726
|
recoverable: failure.recoverable,
|
|
709
|
-
attemptedRecoveries: state.attemptedRecoveries
|
|
727
|
+
attemptedRecoveries: state.attemptedRecoveries,
|
|
728
|
+
metro: metroLaunchEvidence(adapter, target)
|
|
710
729
|
});
|
|
711
730
|
} else if (json) {
|
|
712
731
|
console.log(
|
|
@@ -723,6 +742,7 @@ function launchFail(json, stream, adapter, mobileTarget, tier, state, target, fa
|
|
|
723
742
|
mutations: state.mutations,
|
|
724
743
|
recoverable: failure.recoverable,
|
|
725
744
|
attemptedRecoveries: state.attemptedRecoveries,
|
|
745
|
+
metro: metroLaunchEvidence(adapter, target),
|
|
726
746
|
exitCode: failure.exitCode,
|
|
727
747
|
error: {
|
|
728
748
|
code: failure.code,
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { runnerDir } from "../paths.js";
|
|
3
|
+
import { getAdapterSurface } from "../adapters/surface.js";
|
|
4
|
+
import {
|
|
5
|
+
ADAPTER_DETECT_NEXT,
|
|
6
|
+
EXIT,
|
|
7
|
+
flag,
|
|
8
|
+
parseFlags,
|
|
9
|
+
resolveAdapter,
|
|
10
|
+
spawnScript,
|
|
11
|
+
targetOf,
|
|
12
|
+
usageOut
|
|
13
|
+
} from "./shared.js";
|
|
14
|
+
const RELOAD_BOOLEANS = /* @__PURE__ */ new Set(["json"]);
|
|
15
|
+
async function handleReload(argv) {
|
|
16
|
+
const { options } = parseFlags(argv, RELOAD_BOOLEANS);
|
|
17
|
+
const json = flag(options, "json");
|
|
18
|
+
const target = targetOf(options);
|
|
19
|
+
const adapter = resolveAdapter(options, target);
|
|
20
|
+
if (!adapter) {
|
|
21
|
+
return usageOut(
|
|
22
|
+
json,
|
|
23
|
+
"reload",
|
|
24
|
+
`could not detect the MetaMask repo type for ${target}`,
|
|
25
|
+
ADAPTER_DETECT_NEXT
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
if (adapter === "core") {
|
|
29
|
+
return usageOut(
|
|
30
|
+
json,
|
|
31
|
+
"reload",
|
|
32
|
+
"core is headless; there is no app runtime to reload.",
|
|
33
|
+
"mm-harness run <core-recipe>"
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
getAdapterSurface(adapter).resolveSlotPorts(target);
|
|
37
|
+
if (adapter === "extension") {
|
|
38
|
+
if (!process.env.CDP_PORT) {
|
|
39
|
+
return usageOut(
|
|
40
|
+
json,
|
|
41
|
+
"reload",
|
|
42
|
+
"the Extension CDP port could not be resolved.",
|
|
43
|
+
"mm-harness launch"
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
const script2 = path.join(runnerDir, "adapters/extension/reattach.sh");
|
|
47
|
+
const args2 = ["--target", target, "--cdp-port", process.env.CDP_PORT];
|
|
48
|
+
if (process.env.WATCHER_PORT) {
|
|
49
|
+
args2.push("--watcher-port", process.env.WATCHER_PORT);
|
|
50
|
+
}
|
|
51
|
+
const result2 = spawnScript(script2, args2, target, json);
|
|
52
|
+
if (json) {
|
|
53
|
+
console.log(
|
|
54
|
+
JSON.stringify(
|
|
55
|
+
{
|
|
56
|
+
ok: result2.status === 0,
|
|
57
|
+
adapter,
|
|
58
|
+
method: "cdp-reattach",
|
|
59
|
+
command: "reload",
|
|
60
|
+
cdpPort: Number(process.env.CDP_PORT),
|
|
61
|
+
...result2.status === 0 ? {} : { error: result2.output.slice(-4e3) || "Extension reload failed." }
|
|
62
|
+
},
|
|
63
|
+
null,
|
|
64
|
+
2
|
|
65
|
+
)
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
return result2.status === 0 ? EXIT.ok : EXIT.runtime;
|
|
69
|
+
}
|
|
70
|
+
const script = path.join(runnerDir, "adapters/mobile/reload-app.mjs");
|
|
71
|
+
const args = [];
|
|
72
|
+
if (process.env.WATCHER_PORT) args.push("--port", process.env.WATCHER_PORT);
|
|
73
|
+
if (json) args.push("--json");
|
|
74
|
+
const result = spawnScript(process.execPath, [script, ...args], target, json);
|
|
75
|
+
if (json) process.stdout.write(result.output);
|
|
76
|
+
return result.status === 0 ? EXIT.ok : EXIT.runtime;
|
|
77
|
+
}
|
|
78
|
+
export {
|
|
79
|
+
handleReload
|
|
80
|
+
};
|
package/dist/commands/run.js
CHANGED
|
@@ -41,6 +41,9 @@ import {
|
|
|
41
41
|
missingActionCapabilities,
|
|
42
42
|
resolveActionCapabilityMatrix
|
|
43
43
|
} from "./manifest.js";
|
|
44
|
+
import {
|
|
45
|
+
startRunNetworkObservation
|
|
46
|
+
} from "../network-observation.js";
|
|
44
47
|
async function validationCapabilityRefusals(adapter, findings, librarySources) {
|
|
45
48
|
const actionNames = findings.flatMap((finding) => {
|
|
46
49
|
if (finding.code !== "recipe.action_not_declared_by_manifest") return [];
|
|
@@ -227,12 +230,16 @@ async function handleRunInner({ positional, options }, stream) {
|
|
|
227
230
|
}
|
|
228
231
|
recordCommandEvidence(artifactsDir);
|
|
229
232
|
const librarySources = validated.librarySources;
|
|
233
|
+
let networkObservation;
|
|
230
234
|
const runtimeOptions = {
|
|
231
235
|
...runtimeOptionsFromCli(options),
|
|
232
236
|
params,
|
|
233
237
|
...librarySources ? { librarySources } : {},
|
|
234
238
|
stdoutIsMachineContract: machine,
|
|
235
|
-
onActionEvent: ({ nodeId, action, status }) =>
|
|
239
|
+
onActionEvent: ({ nodeId, action, status }) => {
|
|
240
|
+
stream.node(nodeId, action, status);
|
|
241
|
+
networkObservation?.onActionEvent({ nodeId, action, status });
|
|
242
|
+
}
|
|
236
243
|
};
|
|
237
244
|
stream.phase("authorize");
|
|
238
245
|
let preflightedExecution = await preflightRecipe(
|
|
@@ -263,30 +270,50 @@ async function handleRunInner({ positional, options }, stream) {
|
|
|
263
270
|
return prepared;
|
|
264
271
|
}
|
|
265
272
|
const { state, heal } = prepared;
|
|
266
|
-
|
|
267
|
-
const { result, violation } = await executeWithHealBounds(
|
|
268
|
-
// validated.recipeFile, not the raw arg: the arg may be a library recipe NAME
|
|
269
|
-
// that only the resolver knows how to turn into a file.
|
|
270
|
-
() => {
|
|
271
|
-
const execution = preflightedExecution;
|
|
272
|
-
preflightedExecution = void 0;
|
|
273
|
-
return runRecipe(
|
|
274
|
-
adapter,
|
|
275
|
-
validated.recipeFile,
|
|
276
|
-
artifactsDir,
|
|
277
|
-
target,
|
|
278
|
-
optionString(options, "actionManifest"),
|
|
279
|
-
runtimeOptions,
|
|
280
|
-
execution
|
|
281
|
-
);
|
|
282
|
-
},
|
|
273
|
+
networkObservation = await startRunNetworkObservation(
|
|
283
274
|
adapter,
|
|
284
275
|
target,
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
276
|
+
artifactsDir,
|
|
277
|
+
process.env,
|
|
278
|
+
{
|
|
279
|
+
cdpPort: runtimeOptions.cdpPort,
|
|
280
|
+
watcherPort: runtimeOptions.watcherPort
|
|
281
|
+
}
|
|
289
282
|
);
|
|
283
|
+
stream.phase("execute");
|
|
284
|
+
let executionResult;
|
|
285
|
+
try {
|
|
286
|
+
executionResult = await executeWithHealBounds(
|
|
287
|
+
// validated.recipeFile, not the raw arg: the arg may be a library recipe NAME
|
|
288
|
+
// that only the resolver knows how to turn into a file.
|
|
289
|
+
() => {
|
|
290
|
+
const execution = preflightedExecution;
|
|
291
|
+
preflightedExecution = void 0;
|
|
292
|
+
return runRecipe(
|
|
293
|
+
adapter,
|
|
294
|
+
validated.recipeFile,
|
|
295
|
+
artifactsDir,
|
|
296
|
+
target,
|
|
297
|
+
optionString(options, "actionManifest"),
|
|
298
|
+
runtimeOptions,
|
|
299
|
+
execution
|
|
300
|
+
);
|
|
301
|
+
},
|
|
302
|
+
adapter,
|
|
303
|
+
target,
|
|
304
|
+
heal,
|
|
305
|
+
state,
|
|
306
|
+
() => recoverRunInfra(adapter, target, machine, runtimeOptions),
|
|
307
|
+
(code) => stream.phase("recover", { code })
|
|
308
|
+
);
|
|
309
|
+
} catch (error) {
|
|
310
|
+
await networkObservation?.finalize();
|
|
311
|
+
networkObservation = void 0;
|
|
312
|
+
throw error;
|
|
313
|
+
}
|
|
314
|
+
const { result, violation } = executionResult;
|
|
315
|
+
await networkObservation?.finalize(result.artifactManifestPath);
|
|
316
|
+
networkObservation = void 0;
|
|
290
317
|
for (const mutation of state.mutations) stream.mutation(mutation);
|
|
291
318
|
for (const recovery of state.recovered) stream.recovery(recovery);
|
|
292
319
|
if (violation !== null) {
|
package/dist/mm-harness-cli.js
CHANGED
|
@@ -524,6 +524,22 @@ Example:
|
|
|
524
524
|
mm-harness debug
|
|
525
525
|
mm-harness debug --worker`
|
|
526
526
|
},
|
|
527
|
+
{
|
|
528
|
+
name: "reload",
|
|
529
|
+
summary: "Reload the connected Mobile or Extension runtime without restarting its dev server.",
|
|
530
|
+
example: "mm-harness reload",
|
|
531
|
+
helpText: `mm-harness reload [flags]
|
|
532
|
+
|
|
533
|
+
Mobile broadcasts the same reload command as Metro's interactive r key.
|
|
534
|
+
Extension refreshes its loaded UI in place over CDP. Core is headless.
|
|
535
|
+
|
|
536
|
+
--adapter <mobile|extension> Target adapter (auto-detected inside a checkout)
|
|
537
|
+
--target <path> Checkout path (default: cwd)
|
|
538
|
+
--json Machine-readable output
|
|
539
|
+
|
|
540
|
+
Example:
|
|
541
|
+
mm-harness reload`
|
|
542
|
+
},
|
|
527
543
|
{
|
|
528
544
|
name: "update",
|
|
529
545
|
summary: "Update the installed mm-harness to the published latest (--check reports only; --json = {current, latest, updateAvailable}).",
|
|
@@ -648,7 +664,7 @@ const HELP_GROUPS = [
|
|
|
648
664
|
{
|
|
649
665
|
title: "DAILY LOOP",
|
|
650
666
|
blurb: "what a teammate runs many times a day (auto-ensures the overlay; --heal owns recovery)",
|
|
651
|
-
commands: ["status", "launch", "stop", "logs", "debug", "fixtures"]
|
|
667
|
+
commands: ["status", "launch", "stop", "logs", "debug", "reload", "fixtures"]
|
|
652
668
|
},
|
|
653
669
|
{
|
|
654
670
|
title: "DISCOVER",
|