@deeeed/metamask-harness 0.35.0 → 0.37.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 +39 -0
- package/README.md +9 -1
- package/adapters/extension/artifact-runtime-state.cjs +128 -0
- package/adapters/extension/check-infura-readiness.cjs +102 -0
- package/adapters/extension/inject.mjs +2 -0
- package/adapters/extension/launch-browser.cjs +22 -0
- package/adapters/extension/live.sh +103 -18
- package/adapters/extension/readiness.mjs +77 -36
- package/adapters/extension/snapshot-dist.sh +88 -3
- package/adapters/extension/start-watch.sh +15 -0
- package/adapters/extension/verify.sh +6 -2
- package/adapters/extension/wallet-fixture-state.cjs +3 -1
- package/adapters/manifest.json +25 -1
- package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +73 -42
- package/adapters/mobile/reset-app-data.sh +154 -0
- package/adapters/shared/log-tui.mjs +1 -1
- package/dist/adapters/extension/artifact-integrity.js +38 -0
- package/dist/adapters/extension/extension-id.js +23 -4
- package/dist/adapters/extension/product-config.js +29 -1
- package/dist/adapters/extension/release-artifact.js +386 -0
- package/dist/adapters/extension/runtime-decision.js +161 -20
- package/dist/adapters/extension/runtime.js +127 -0
- package/dist/adapters/mobile/release-artifact-state.js +124 -0
- package/dist/adapters/mobile/release-artifact.js +295 -0
- package/dist/adapters.js +29 -4
- package/dist/cli-commands.js +1 -1
- package/dist/cli.js +2 -2
- package/dist/command-contract.js +17 -1
- package/dist/commands/call.js +2 -1
- package/dist/commands/device-target.js +5 -0
- package/dist/commands/fixtures.js +106 -31
- package/dist/commands/launch/extension.js +92 -7
- package/dist/commands/launch/index.js +13 -0
- package/dist/commands/launch/mobile.js +2 -0
- package/dist/commands/provision.js +2 -0
- package/dist/commands/run-engine.js +89 -5
- package/dist/commands/run.js +3 -1
- package/dist/commands/runtime-launch.js +178 -10
- package/dist/heal-bounds.js +1 -1
- package/dist/live-adapter-contract.js +3 -1
- package/dist/metamask-action-validation.js +47 -1
- package/dist/mm-harness-cli.js +37 -4
- package/dist/recipe-security.js +3 -0
- package/dist/run-diagnostics.js +1 -1
- package/docs/RECIPES.md +29 -0
- package/docs/RELEASE-QA-CAPABILITY-MAP.md +150 -0
- package/library/actions/extension/perps/perps.mjs +2 -0
- package/library/actions/extension/perps/read_snapshot.mjs +470 -0
- package/library/actions/extension/platform/cdp.mjs +6 -3
- package/library/actions/extension/wallet/import.mjs +201 -0
- package/library/actions/extension/wallet/reset.mjs +98 -0
- package/library/actions/extension/wallet/secret-input.mjs +98 -0
- package/library/actions/extension/wallet/state.mjs +1 -0
- package/library/actions/mobile/analytics/consent-settings.mjs +112 -0
- package/library/actions/mobile/analytics/set_consent.mjs +4 -112
- package/library/actions/mobile/platform/bridge.mjs +8 -0
- package/library/actions/mobile/platform/observe-ui.mjs +84 -2
- package/library/actions/mobile/ui/native-navigation.mjs +225 -0
- package/library/actions/mobile/ui/navigate.mjs +7 -0
- package/library/actions/mobile/wallet/import.mjs +328 -0
- package/library/actions/mobile/wallet/native-ui.mjs +493 -0
- package/library/actions/mobile/wallet/read_state.mjs +16 -0
- package/library/actions/mobile/wallet/reset-helper.mjs +99 -0
- package/library/actions/mobile/wallet/reset.mjs +20 -0
- package/library/actions/shared/ui/locators.mjs +7 -0
- package/library/actions/shared/wallet/import-source.mjs +101 -0
- package/library/manifests/extension.action-manifest.json +228 -0
- package/library/manifests/mobile.action-manifest.json +124 -0
- package/library/recipes/extension/runner/action-validation.recipe.json +12 -1
- package/library/recipes/wallet/import.recipe.json +102 -0
- package/library/recipes/wallet/reset-import.recipe.json +107 -0
- package/package.json +1 -1
- package/scripts/completions.sh +2 -2
|
@@ -13,6 +13,9 @@ import {
|
|
|
13
13
|
import { resolveActionManifest, validateManifest } from "../manifest.js";
|
|
14
14
|
import { getAdapterSurface } from "../adapters/surface.js";
|
|
15
15
|
import { extensionProductConfigBlock } from "../adapters/extension/product-config.js";
|
|
16
|
+
import {
|
|
17
|
+
requireCurrentMobileReleaseArtifactState
|
|
18
|
+
} from "../adapters/mobile/release-artifact-state.js";
|
|
16
19
|
import {
|
|
17
20
|
importRecipeHarness,
|
|
18
21
|
importRecipeProtocol,
|
|
@@ -137,10 +140,11 @@ async function resolveRecipeExecution(adapter, recipe, artifactsDir, projectRoot
|
|
|
137
140
|
const { createMetaMaskRunner } = await import("../runner.js");
|
|
138
141
|
const recipeDocument = runtimeRecipeDocument(recipe, absoluteRecipePath);
|
|
139
142
|
const suppressHudForLifecycleTiming = adapter === "mobile" && mobileRecipeRequiresUninterruptedLifecycleTiming(recipeDocument);
|
|
143
|
+
const suppressHudForOpaqueMobile = adapter === "mobile" && process.env.METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME === "1";
|
|
140
144
|
const runner = await createMetaMaskRunner(adapter, manifest, {
|
|
141
145
|
quietStdout: runtimeOptions.stdoutIsMachineContract === true,
|
|
142
146
|
suppressLibraryResolutionLogs: runtimeOptions.suppressLibraryResolutionLogs,
|
|
143
|
-
autoHud: suppressHudForLifecycleTiming ? false : trust.source && trust.source.trust !== "trusted" ? false : runtimeOptions.autoHud,
|
|
147
|
+
autoHud: suppressHudForLifecycleTiming || suppressHudForOpaqueMobile ? false : trust.source && trust.source.trust !== "trusted" ? false : runtimeOptions.autoHud,
|
|
144
148
|
onActionEvent: runtimeOptions.onActionEvent,
|
|
145
149
|
actionSources,
|
|
146
150
|
// A direct engineer invocation is the explicit trust boundary for a
|
|
@@ -216,6 +220,10 @@ function activateRecipeRuntimeEnvironment(adapter, projectRoot, runtimeOptions)
|
|
|
216
220
|
const previousWatcherPort = process.env.WATCHER_PORT;
|
|
217
221
|
const previousMetroPort = process.env.METRO_PORT;
|
|
218
222
|
const previousExtensionAutolaunch = process.env.METAMASK_RECIPE_EXTENSION_AUTOLAUNCH;
|
|
223
|
+
const previousMobileOpaqueRuntime = process.env.METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME;
|
|
224
|
+
const previousAndroidPackageId = process.env.ANDROID_PACKAGE_ID;
|
|
225
|
+
const previousAdbSerial = process.env.ADB_SERIAL;
|
|
226
|
+
const previousAndroidSerial = process.env.ANDROID_SERIAL;
|
|
219
227
|
getAdapterSurface(adapter).resolveSlotPorts(projectRoot);
|
|
220
228
|
if (runtimeOptions.cdpPort) {
|
|
221
229
|
process.env.CDP_PORT = runtimeOptions.cdpPort;
|
|
@@ -228,6 +236,21 @@ function activateRecipeRuntimeEnvironment(adapter, projectRoot, runtimeOptions)
|
|
|
228
236
|
if (runtimeOptions.launchExistingDist) {
|
|
229
237
|
process.env.METAMASK_RECIPE_EXTENSION_AUTOLAUNCH = "1";
|
|
230
238
|
}
|
|
239
|
+
if (adapter === "mobile") {
|
|
240
|
+
const state = requireCurrentMobileReleaseArtifactState(projectRoot);
|
|
241
|
+
if (state) {
|
|
242
|
+
const selected = process.env.ADB_SERIAL ?? process.env.ANDROID_SERIAL;
|
|
243
|
+
if (selected && selected !== state.deviceSerial) {
|
|
244
|
+
throw new Error(
|
|
245
|
+
`The Mobile release artifact is installed on ${state.deviceSerial}, not ${selected}. Next: rerun runtime-launch for ${selected}.`
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
process.env.METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME = "1";
|
|
249
|
+
process.env.ANDROID_PACKAGE_ID = state.packageId;
|
|
250
|
+
process.env.ADB_SERIAL = state.deviceSerial;
|
|
251
|
+
process.env.ANDROID_SERIAL = state.deviceSerial;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
231
254
|
return () => {
|
|
232
255
|
restoreEnv("CDP_PORT", previousCdpPort);
|
|
233
256
|
restoreEnv("RECIPE_CDP_PORT", previousRecipeCdpPort);
|
|
@@ -237,6 +260,13 @@ function activateRecipeRuntimeEnvironment(adapter, projectRoot, runtimeOptions)
|
|
|
237
260
|
"METAMASK_RECIPE_EXTENSION_AUTOLAUNCH",
|
|
238
261
|
previousExtensionAutolaunch
|
|
239
262
|
);
|
|
263
|
+
restoreEnv(
|
|
264
|
+
"METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME",
|
|
265
|
+
previousMobileOpaqueRuntime
|
|
266
|
+
);
|
|
267
|
+
restoreEnv("ANDROID_PACKAGE_ID", previousAndroidPackageId);
|
|
268
|
+
restoreEnv("ADB_SERIAL", previousAdbSerial);
|
|
269
|
+
restoreEnv("ANDROID_SERIAL", previousAndroidSerial);
|
|
240
270
|
};
|
|
241
271
|
}
|
|
242
272
|
function actionSourceMap(sources) {
|
|
@@ -267,12 +297,23 @@ function recipeRunEnv(adapter, runtimeOptions = {}) {
|
|
|
267
297
|
ANDROID_DEVICE: process.env.ANDROID_DEVICE,
|
|
268
298
|
ANDROID_TARGET_DEVICE_NAME: process.env.ANDROID_TARGET_DEVICE_NAME,
|
|
269
299
|
ADB_SERIAL: process.env.ADB_SERIAL,
|
|
270
|
-
ANDROID_SERIAL: process.env.ANDROID_SERIAL
|
|
300
|
+
ANDROID_SERIAL: process.env.ANDROID_SERIAL,
|
|
301
|
+
ANDROID_PACKAGE_ID: process.env.ANDROID_PACKAGE_ID,
|
|
302
|
+
METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME: process.env.METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME
|
|
271
303
|
};
|
|
272
304
|
}
|
|
273
305
|
async function prepareRuntimeIfNeeded(adapter, projectRoot, runtimeOptions) {
|
|
274
306
|
if (adapter !== "extension" || runtimeOptions.skipExtensionRuntimePrepare === true)
|
|
275
307
|
return;
|
|
308
|
+
const { releaseArtifactState, runtimeDistCheck } = await import("../adapters/extension/runtime-decision.js");
|
|
309
|
+
const artifact = releaseArtifactState(projectRoot);
|
|
310
|
+
const runtimeSnapshot = runtimeDistCheck(projectRoot, artifact);
|
|
311
|
+
if (artifact.status !== "none") {
|
|
312
|
+
if (artifact.status === "valid" && runtimeSnapshot.status === "fresh") return;
|
|
313
|
+
throw new Error(
|
|
314
|
+
"The loaded release artifact identity is invalid or stale. Next: rerun runtime-launch with the same artifact source."
|
|
315
|
+
);
|
|
316
|
+
}
|
|
276
317
|
const { prepareExtensionRuntime } = await import("../adapters/extension/runtime.js");
|
|
277
318
|
await prepareExtensionRuntime({
|
|
278
319
|
projectRoot,
|
|
@@ -306,7 +347,7 @@ async function validateRecipeAdapterAware(adapter, recipe, manifest, librarySour
|
|
|
306
347
|
);
|
|
307
348
|
const findings = [
|
|
308
349
|
...withManifest.findings,
|
|
309
|
-
...validateMetaMaskActionInputs(recipe)
|
|
350
|
+
...validateMetaMaskActionInputs(recipe, adapter)
|
|
310
351
|
];
|
|
311
352
|
if (withManifest.status === "valid" && isRecord(recipe) && libraryResolution) {
|
|
312
353
|
try {
|
|
@@ -336,7 +377,7 @@ async function validateRecipeAdapterAware(adapter, recipe, manifest, librarySour
|
|
|
336
377
|
manifest,
|
|
337
378
|
validationOptions
|
|
338
379
|
).findings,
|
|
339
|
-
...validateMetaMaskActionInputs(dependency.document)
|
|
380
|
+
...validateMetaMaskActionInputs(dependency.document, adapter)
|
|
340
381
|
);
|
|
341
382
|
}
|
|
342
383
|
} catch (error) {
|
|
@@ -782,6 +823,7 @@ async function prepareHeal(adapter, target, options, json, prepareOptions = {})
|
|
|
782
823
|
return EXIT.usage;
|
|
783
824
|
}
|
|
784
825
|
const state = newHealState();
|
|
826
|
+
const mobileArtifact = adapter === "mobile" ? requireCurrentMobileReleaseArtifactState(target) : null;
|
|
785
827
|
prepareOptions.onPhase?.("install");
|
|
786
828
|
const ensured = await ensureOverlay(
|
|
787
829
|
adapter,
|
|
@@ -847,7 +889,7 @@ async function prepareHeal(adapter, target, options, json, prepareOptions = {})
|
|
|
847
889
|
restoreEnv("WATCHER_PORT", previousWatcherPort);
|
|
848
890
|
}
|
|
849
891
|
}
|
|
850
|
-
if (adapter === "mobile" && prepareOptions.skipMobileSourceFreshness !== true) {
|
|
892
|
+
if (adapter === "mobile" && mobileArtifact === null && prepareOptions.skipMobileSourceFreshness !== true) {
|
|
851
893
|
const current = await ensureMobileProofRuntime(
|
|
852
894
|
target,
|
|
853
895
|
heal,
|
|
@@ -994,6 +1036,22 @@ async function ensureExtensionProofRuntime(target, heal, state, json, deps = {})
|
|
|
994
1036
|
});
|
|
995
1037
|
const initial = await decide();
|
|
996
1038
|
if (initial.decision === "ready") return null;
|
|
1039
|
+
if (initial.reasonCode === "release-artifact-identity-invalid" || initial.reasonCode === "release-artifact-runtime-missing" || initial.reasonCode === "release-artifact-runtime-stale") {
|
|
1040
|
+
const message = initial.reasons[0] ?? "The loaded release artifact identity is invalid.";
|
|
1041
|
+
const userAction2 = "rerun runtime-launch with the same artifact source";
|
|
1042
|
+
if (json) {
|
|
1043
|
+
console.log(JSON.stringify({
|
|
1044
|
+
schemaVersion: 1,
|
|
1045
|
+
status: "fail",
|
|
1046
|
+
recoverable: false,
|
|
1047
|
+
error: { code: "RELEASE_ARTIFACT_NOT_CURRENT", message, userAction: userAction2 }
|
|
1048
|
+
}, null, 2));
|
|
1049
|
+
} else {
|
|
1050
|
+
console.error(`\u2717 ${message}`);
|
|
1051
|
+
console.error(` Next: ${userAction2}`);
|
|
1052
|
+
}
|
|
1053
|
+
return EXIT.infra;
|
|
1054
|
+
}
|
|
997
1055
|
if (initial.reasonCode === "runtime-dist-check-failed") {
|
|
998
1056
|
const message = `Extension proof runtime freshness could not be checked: ${initial.reasons[0] ?? initial.decision}`;
|
|
999
1057
|
const userAction2 = `mm-harness doctor --adapter extension --target ${JSON.stringify(path.resolve(target))}`;
|
|
@@ -1142,10 +1200,35 @@ const RUN_RECOVERY_CODE = {
|
|
|
1142
1200
|
async function recoverRunInfra(adapter, target, json) {
|
|
1143
1201
|
getAdapterSurface(adapter).resolveSlotPorts(target);
|
|
1144
1202
|
if (adapter === "extension") {
|
|
1203
|
+
const { releaseArtifactState } = await import("../adapters/extension/runtime-decision.js");
|
|
1204
|
+
const artifact = releaseArtifactState(target);
|
|
1205
|
+
if (artifact.status !== "none") {
|
|
1206
|
+
return {
|
|
1207
|
+
status: 1,
|
|
1208
|
+
output: "Release artifact recovery cannot switch to a checkout build.\nNext: rerun runtime-launch with the same artifact source."
|
|
1209
|
+
};
|
|
1210
|
+
}
|
|
1145
1211
|
const { launchExtension } = await import("./launch/extension.js");
|
|
1146
1212
|
return launchExtension(target, "quick", false);
|
|
1147
1213
|
}
|
|
1148
1214
|
if (adapter === "mobile") {
|
|
1215
|
+
try {
|
|
1216
|
+
const artifact = requireCurrentMobileReleaseArtifactState(target);
|
|
1217
|
+
if (artifact) {
|
|
1218
|
+
const { relaunchAndroidReleaseArtifactRuntime } = await import("../adapters/mobile/release-artifact.js");
|
|
1219
|
+
await relaunchAndroidReleaseArtifactRuntime(artifact);
|
|
1220
|
+
return {
|
|
1221
|
+
status: 0,
|
|
1222
|
+
output: `Relaunched ${artifact.packageId} ${artifact.versionName} (${artifact.versionCode}) on ${artifact.deviceSerial}.`
|
|
1223
|
+
};
|
|
1224
|
+
}
|
|
1225
|
+
} catch (error) {
|
|
1226
|
+
return {
|
|
1227
|
+
status: 1,
|
|
1228
|
+
output: `Release artifact recovery could not revalidate and relaunch the pinned Android package: ${error instanceof Error ? error.message : String(error)}
|
|
1229
|
+
Next: rerun runtime-launch with the same artifact source.`
|
|
1230
|
+
};
|
|
1231
|
+
}
|
|
1149
1232
|
const { launchMobile } = await import("./launch/mobile.js");
|
|
1150
1233
|
const platform = resolveMobileRecoveryPlatform();
|
|
1151
1234
|
return launchMobile(target, platform, "quick", json);
|
|
@@ -1251,6 +1334,7 @@ export {
|
|
|
1251
1334
|
mobileRecipeRequiresUninterruptedLifecycleTiming,
|
|
1252
1335
|
preflightRecipe,
|
|
1253
1336
|
prepareHeal,
|
|
1337
|
+
prepareRuntimeIfNeeded,
|
|
1254
1338
|
recoverRunInfra,
|
|
1255
1339
|
resolveMetaMaskLibrarySources,
|
|
1256
1340
|
resolveMobileRecoveryPlatform,
|
package/dist/commands/run.js
CHANGED
|
@@ -29,6 +29,7 @@ import { handleDescribeRecipe, handleListExecutables } from "./list-executables.
|
|
|
29
29
|
import { applyDeviceTargeting } from "./device-target.js";
|
|
30
30
|
import { getAdapterSurface } from "../adapters/surface.js";
|
|
31
31
|
import { coreDependencyBlock } from "./core-readiness.js";
|
|
32
|
+
import { readMobileReleaseArtifactState } from "../adapters/mobile/release-artifact-state.js";
|
|
32
33
|
import { writeRunReport } from "./run-report.js";
|
|
33
34
|
import { acquireCheckoutLock } from "../checkout-lock.js";
|
|
34
35
|
import { JsonStreamWriter } from "../json-stream.js";
|
|
@@ -250,7 +251,8 @@ async function handleRunInner({ positional, options }, stream) {
|
|
|
250
251
|
}
|
|
251
252
|
try {
|
|
252
253
|
const prepared = await prepareHeal(adapter, target, options, machine, {
|
|
253
|
-
onPhase: (phase, fields) => stream.phase(phase, fields)
|
|
254
|
+
onPhase: (phase, fields) => stream.phase(phase, fields),
|
|
255
|
+
skipMobileSourceFreshness: adapter === "mobile" && readMobileReleaseArtifactState(target) !== null
|
|
254
256
|
});
|
|
255
257
|
if (typeof prepared === "number") {
|
|
256
258
|
stream.error({
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
acquireExtensionReleaseArtifact
|
|
6
|
+
} from "../adapters/extension/release-artifact.js";
|
|
7
|
+
import { installAndroidReleaseArtifact } from "../adapters/mobile/release-artifact.js";
|
|
8
|
+
import {
|
|
9
|
+
clearMobileReleaseArtifactState,
|
|
10
|
+
writeMobileReleaseArtifactState
|
|
11
|
+
} from "../adapters/mobile/release-artifact-state.js";
|
|
4
12
|
import { recipeHarnessPath } from "../paths.js";
|
|
5
13
|
import {
|
|
6
14
|
adapterOption,
|
|
@@ -13,9 +21,56 @@ import {
|
|
|
13
21
|
usageError
|
|
14
22
|
} from "./parse-args.js";
|
|
15
23
|
async function handleRuntimeLaunch({ options }) {
|
|
24
|
+
if (optionFlag(options, "help")) {
|
|
25
|
+
console.log(`mm-harness runtime-launch --adapter <extension|mobile> --target <checkout> [flags]
|
|
26
|
+
|
|
27
|
+
--artifact-file <path> Artifact downloaded by the validation skill
|
|
28
|
+
--artifact-version <X.Y.Z> Expected manifest or Android version name
|
|
29
|
+
--artifact-sha256 <digest> Trusted SHA-256 from the acquisition step
|
|
30
|
+
--artifact-build-id <id> Android version code
|
|
31
|
+
--artifact-package-id <id> Android application ID
|
|
32
|
+
--artifact-cache <dir> Artifact cache root
|
|
33
|
+
--platform <android> Mobile artifact platform (iOS is unsupported)
|
|
34
|
+
--device <serial> Explicit Android device serial
|
|
35
|
+
--cdp-port <port> Extension CDP port
|
|
36
|
+
--chrome-user-data-dir Isolated Chrome profile path
|
|
37
|
+
--artifacts-dir <dir> Validation evidence directory
|
|
38
|
+
--json Machine-readable terminal report
|
|
39
|
+
|
|
40
|
+
The harness verifies and runs a local artifact but never downloads one. Extension
|
|
41
|
+
launch resets an isolated profile. Android install requires an explicit serial.`);
|
|
42
|
+
return 0;
|
|
43
|
+
}
|
|
16
44
|
const adapter = adapterOption(options);
|
|
17
45
|
const target = targetPath(options);
|
|
46
|
+
const artifactFile = optionString(options, "artifactFile");
|
|
47
|
+
const artifactVersion = optionString(options, "artifactVersion");
|
|
48
|
+
const artifactSha256 = optionString(options, "artifactSha256");
|
|
49
|
+
const artifactBuildId = optionString(options, "artifactBuildId");
|
|
50
|
+
const artifactPackageId = optionString(options, "artifactPackageId");
|
|
51
|
+
const artifactCache = optionString(options, "artifactCache");
|
|
52
|
+
const artifactRequested = Boolean(
|
|
53
|
+
artifactFile || artifactVersion || artifactSha256 || artifactBuildId || artifactPackageId || artifactCache
|
|
54
|
+
);
|
|
55
|
+
if (artifactRequested && (!artifactFile || !artifactVersion || !artifactSha256)) {
|
|
56
|
+
throw usageError("artifact launch requires --artifact-file, --artifact-version, and --artifact-sha256 together.");
|
|
57
|
+
}
|
|
58
|
+
if (adapter === "mobile") {
|
|
59
|
+
return handleAndroidArtifactLaunch({
|
|
60
|
+
options,
|
|
61
|
+
target,
|
|
62
|
+
artifactFile,
|
|
63
|
+
artifactVersion,
|
|
64
|
+
artifactSha256,
|
|
65
|
+
artifactBuildId,
|
|
66
|
+
artifactPackageId,
|
|
67
|
+
artifactCache
|
|
68
|
+
});
|
|
69
|
+
}
|
|
18
70
|
if (adapter !== "extension") throw new Error("runtime-launch currently applies to the extension adapter.");
|
|
71
|
+
if (artifactBuildId || artifactPackageId || optionString(options, "platform") || optionString(options, "device")) {
|
|
72
|
+
throw usageError("Extension runtime-launch does not accept Android artifact options.");
|
|
73
|
+
}
|
|
19
74
|
const cdpPort = parsePort(
|
|
20
75
|
optionString(options, "cdpPort") ?? process.env.CDP_PORT ?? process.env.RECIPE_CDP_PORT,
|
|
21
76
|
"runtime-launch requires --cdp-port <port>."
|
|
@@ -29,7 +84,19 @@ async function handleRuntimeLaunch({ options }) {
|
|
|
29
84
|
if (startWatch && buildLavaMoat) {
|
|
30
85
|
throw usageError("runtime-launch accepts only one of --start-watch or --build-lavamoat.");
|
|
31
86
|
}
|
|
32
|
-
|
|
87
|
+
if (artifactRequested && (startWatch || buildLavaMoat || optionString(options, "remoteFlag"))) {
|
|
88
|
+
throw usageError("artifact launch cannot be combined with build, watch, or remote-flag options.");
|
|
89
|
+
}
|
|
90
|
+
let artifact;
|
|
91
|
+
if (artifactRequested) {
|
|
92
|
+
artifact = await acquireExtensionReleaseArtifact({
|
|
93
|
+
file: artifactFile,
|
|
94
|
+
expectedVersion: artifactVersion,
|
|
95
|
+
expectedSha256: artifactSha256,
|
|
96
|
+
cacheRoot: artifactCache
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
const mode = artifact ? "release-artifact" : buildLavaMoat ? "build-lavamoat" : startWatch ? "start-watch" : "existing-dist";
|
|
33
100
|
const liveScript = recipeHarnessPath(target, "extension", "scripts", "live.sh");
|
|
34
101
|
const command = [
|
|
35
102
|
"bash",
|
|
@@ -42,6 +109,15 @@ async function handleRuntimeLaunch({ options }) {
|
|
|
42
109
|
"--artifacts-dir",
|
|
43
110
|
artifactsDir
|
|
44
111
|
];
|
|
112
|
+
if (artifact) {
|
|
113
|
+
command.push(
|
|
114
|
+
"--external-artifact",
|
|
115
|
+
"--dist-dir",
|
|
116
|
+
artifact.extensionDir,
|
|
117
|
+
"--artifact-provenance",
|
|
118
|
+
artifact.provenancePath
|
|
119
|
+
);
|
|
120
|
+
}
|
|
45
121
|
if (chromeUserDataDir) command.push("--chrome-user-data-dir", chromeUserDataDir);
|
|
46
122
|
const remoteFlag = optionString(options, "remoteFlag");
|
|
47
123
|
if (remoteFlag) command.push("--remote-flag", remoteFlag);
|
|
@@ -52,7 +128,7 @@ async function handleRuntimeLaunch({ options }) {
|
|
|
52
128
|
cdpPort,
|
|
53
129
|
artifactsDir,
|
|
54
130
|
reason: "harness_live_script_missing",
|
|
55
|
-
fix: `Run mm-harness install --adapter extension --target ${shellQuote(target)}, then rerun: ${runtimeLaunchCommand(target, cdpPort, chromeUserDataDir, mode)}`,
|
|
131
|
+
fix: `Run mm-harness install --adapter extension --target ${shellQuote(target)}, then rerun: ${runtimeLaunchCommand(target, cdpPort, chromeUserDataDir, mode, artifactInvocation(options))}`,
|
|
56
132
|
command
|
|
57
133
|
});
|
|
58
134
|
printRuntimeLaunchReport(report2, optionFlag(options, "json"));
|
|
@@ -61,7 +137,7 @@ async function handleRuntimeLaunch({ options }) {
|
|
|
61
137
|
fs.mkdirSync(artifactsDir, { recursive: true });
|
|
62
138
|
const jsonMode = optionFlag(options, "json");
|
|
63
139
|
if (!jsonMode) {
|
|
64
|
-
const detail = mode === "build-lavamoat" ? "production-like LavaMoat build" : mode === "start-watch" ? "test webpack watcher" : "existing dist";
|
|
140
|
+
const detail = mode === "release-artifact" ? `release artifact ${artifact?.manifestVersion}` : mode === "build-lavamoat" ? "production-like LavaMoat build" : mode === "start-watch" ? "test webpack watcher" : "existing dist";
|
|
65
141
|
console.error(`recipe: runtime-launch starting (cdp=${cdpPort}, ${detail})`);
|
|
66
142
|
} else {
|
|
67
143
|
console.error(JSON.stringify({
|
|
@@ -69,7 +145,7 @@ async function handleRuntimeLaunch({ options }) {
|
|
|
69
145
|
type: "progress",
|
|
70
146
|
command: "rebuild",
|
|
71
147
|
phase: "runtime-launch",
|
|
72
|
-
message: mode === "build-lavamoat" ? "production-like LavaMoat build starting" : mode === "start-watch" ? "test webpack watcher starting" : "launching existing dist",
|
|
148
|
+
message: mode === "release-artifact" ? `release artifact ${artifact?.manifestVersion} launch starting` : mode === "build-lavamoat" ? "production-like LavaMoat build starting" : mode === "start-watch" ? "test webpack watcher starting" : "launching existing dist",
|
|
73
149
|
cdpPort
|
|
74
150
|
}));
|
|
75
151
|
}
|
|
@@ -90,7 +166,8 @@ async function handleRuntimeLaunch({ options }) {
|
|
|
90
166
|
summaryPath,
|
|
91
167
|
reason: "runtime_ready",
|
|
92
168
|
fix: "",
|
|
93
|
-
command
|
|
169
|
+
command,
|
|
170
|
+
artifact
|
|
94
171
|
});
|
|
95
172
|
printRuntimeLaunchReport(report2, optionFlag(options, "json"));
|
|
96
173
|
return 0;
|
|
@@ -109,20 +186,111 @@ async function handleRuntimeLaunch({ options }) {
|
|
|
109
186
|
launchLogPath: fs.existsSync(launchLogPath) ? launchLogPath : void 0,
|
|
110
187
|
verifySummaryPath: fs.existsSync(verifySummaryPath) ? verifySummaryPath : void 0,
|
|
111
188
|
reason,
|
|
112
|
-
fix: `Read ${failurePath}, fix the first error, then rerun: ${runtimeLaunchCommand(target, cdpPort, chromeUserDataDir, mode)}`,
|
|
189
|
+
fix: `Read ${failurePath}, fix the first error, then rerun: ${runtimeLaunchCommand(target, cdpPort, chromeUserDataDir, mode, artifactInvocation(options))}`,
|
|
113
190
|
command,
|
|
191
|
+
artifact,
|
|
114
192
|
exitCode: result.status ?? 1
|
|
115
193
|
});
|
|
116
194
|
printRuntimeLaunchReport(report, optionFlag(options, "json"));
|
|
117
195
|
return 1;
|
|
118
196
|
}
|
|
197
|
+
async function handleAndroidArtifactLaunch(input) {
|
|
198
|
+
const {
|
|
199
|
+
options,
|
|
200
|
+
target,
|
|
201
|
+
artifactFile,
|
|
202
|
+
artifactVersion,
|
|
203
|
+
artifactSha256,
|
|
204
|
+
artifactBuildId,
|
|
205
|
+
artifactPackageId,
|
|
206
|
+
artifactCache
|
|
207
|
+
} = input;
|
|
208
|
+
const platform = optionString(options, "platform") ?? "android";
|
|
209
|
+
if (platform === "ios") {
|
|
210
|
+
throw usageError("Downloaded iOS artifact launch is unsupported. Next: use --platform android with a local APK.");
|
|
211
|
+
}
|
|
212
|
+
if (platform !== "android") throw usageError("--platform must be android for Mobile artifact launch.");
|
|
213
|
+
if (!artifactFile || !artifactVersion || !artifactSha256 || !artifactBuildId || !artifactPackageId) {
|
|
214
|
+
throw usageError(
|
|
215
|
+
"Android artifact launch requires --artifact-file, --artifact-version, --artifact-build-id, --artifact-package-id, and --artifact-sha256 together."
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
const deviceSerial = optionString(options, "device");
|
|
219
|
+
if (!deviceSerial) {
|
|
220
|
+
throw usageError("Android artifact launch requires --device <serial>. Next: run adb devices -l and rerun with one serial.");
|
|
221
|
+
}
|
|
222
|
+
if (artifactCache || optionString(options, "cdpPort") || optionString(options, "chromeUserDataDir") || optionString(options, "remoteFlag") || optionFlag(options, "startWatch") || optionFlag(options, "buildLavamoat")) {
|
|
223
|
+
throw usageError("Android artifact launch cannot be combined with Extension build, profile, cache, or CDP options.");
|
|
224
|
+
}
|
|
225
|
+
const artifactsDir = path.resolve(
|
|
226
|
+
optionString(options, "artifactsDir") ?? recipeHarnessPath(target, "mobile", "runtime-launch", (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/gu, ""))
|
|
227
|
+
);
|
|
228
|
+
fs.mkdirSync(artifactsDir, { recursive: true });
|
|
229
|
+
const summaryPath = path.join(artifactsDir, "summary.json");
|
|
230
|
+
try {
|
|
231
|
+
clearMobileReleaseArtifactState(target);
|
|
232
|
+
const artifact = await installAndroidReleaseArtifact({
|
|
233
|
+
file: artifactFile,
|
|
234
|
+
expectedVersion: artifactVersion,
|
|
235
|
+
expectedBuildId: artifactBuildId,
|
|
236
|
+
expectedSha256: artifactSha256,
|
|
237
|
+
packageId: artifactPackageId,
|
|
238
|
+
deviceSerial
|
|
239
|
+
});
|
|
240
|
+
const runtimeState = writeMobileReleaseArtifactState(target, artifact);
|
|
241
|
+
const report = runtimeLaunchReport("pass", {
|
|
242
|
+
adapter: "mobile",
|
|
243
|
+
platform,
|
|
244
|
+
target,
|
|
245
|
+
artifactsDir,
|
|
246
|
+
summaryPath,
|
|
247
|
+
reason: "runtime_ready",
|
|
248
|
+
fix: "",
|
|
249
|
+
artifact,
|
|
250
|
+
runtimeState
|
|
251
|
+
});
|
|
252
|
+
fs.writeFileSync(summaryPath, `${JSON.stringify(report, null, 2)}
|
|
253
|
+
`, { encoding: "utf8", mode: 384 });
|
|
254
|
+
printRuntimeLaunchReport(report, optionFlag(options, "json"));
|
|
255
|
+
return 0;
|
|
256
|
+
} catch (error) {
|
|
257
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
258
|
+
const report = runtimeLaunchReport("fail", {
|
|
259
|
+
adapter: "mobile",
|
|
260
|
+
platform,
|
|
261
|
+
target,
|
|
262
|
+
artifactsDir,
|
|
263
|
+
summaryPath,
|
|
264
|
+
reason: "android_artifact_launch_failed",
|
|
265
|
+
error: message,
|
|
266
|
+
fix: `Fix the first error, then rerun with --device ${shellQuote(deviceSerial)} and the same verified artifact identity.`
|
|
267
|
+
});
|
|
268
|
+
fs.writeFileSync(summaryPath, `${JSON.stringify(report, null, 2)}
|
|
269
|
+
`, { encoding: "utf8", mode: 384 });
|
|
270
|
+
printRuntimeLaunchReport(report, optionFlag(options, "json"));
|
|
271
|
+
return 1;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
119
274
|
function runtimeLaunchLiveFlag(mode) {
|
|
120
|
-
return mode === "existing-dist" ? "--launch-existing-dist" : `--${mode}`;
|
|
275
|
+
return mode === "existing-dist" || mode === "release-artifact" ? "--launch-existing-dist" : `--${mode}`;
|
|
121
276
|
}
|
|
122
|
-
function runtimeLaunchCommand(target, cdpPort, chromeUserDataDir, mode = "existing-dist") {
|
|
277
|
+
function runtimeLaunchCommand(target, cdpPort, chromeUserDataDir, mode = "existing-dist", artifact) {
|
|
123
278
|
const base = `mm-harness runtime-launch --adapter extension --target ${shellQuote(target)} --cdp-port ${cdpPort}`;
|
|
124
|
-
|
|
125
|
-
|
|
279
|
+
let command = mode === "existing-dist" || mode === "release-artifact" ? base : `${base} --${mode}`;
|
|
280
|
+
if (artifact?.file) command += ` --artifact-file ${shellQuote(artifact.file)}`;
|
|
281
|
+
if (artifact?.version) command += ` --artifact-version ${shellQuote(artifact.version)}`;
|
|
282
|
+
if (artifact?.sha256) command += ` --artifact-sha256 ${shellQuote(artifact.sha256)}`;
|
|
283
|
+
if (artifact?.cache) command += ` --artifact-cache ${shellQuote(artifact.cache)}`;
|
|
284
|
+
return chromeUserDataDir ? `${command} --chrome-user-data-dir ${shellQuote(chromeUserDataDir)}` : command;
|
|
285
|
+
}
|
|
286
|
+
function artifactInvocation(options) {
|
|
287
|
+
const artifact = {
|
|
288
|
+
file: optionString(options, "artifactFile"),
|
|
289
|
+
version: optionString(options, "artifactVersion"),
|
|
290
|
+
sha256: optionString(options, "artifactSha256"),
|
|
291
|
+
cache: optionString(options, "artifactCache")
|
|
292
|
+
};
|
|
293
|
+
return Object.values(artifact).some(Boolean) ? artifact : void 0;
|
|
126
294
|
}
|
|
127
295
|
function runtimeLaunchReport(status, fields) {
|
|
128
296
|
return {
|
package/dist/heal-bounds.js
CHANGED
|
@@ -86,7 +86,7 @@ function checkHealBounds(target, output, state) {
|
|
|
86
86
|
originalError
|
|
87
87
|
};
|
|
88
88
|
}
|
|
89
|
-
const failureClass = classifyFailure(output);
|
|
89
|
+
const failureClass = classifyFailure(conciseFailureForHuman(output));
|
|
90
90
|
if (failureClass === "screenshot-protected") {
|
|
91
91
|
return {
|
|
92
92
|
code: "SCREENSHOT_PROTECTED",
|
|
@@ -158,7 +158,9 @@ async function prepareLiveAdapterScript({
|
|
|
158
158
|
target: `node${process.versions.node.split(".")[0]}`,
|
|
159
159
|
legalComments: "none",
|
|
160
160
|
sourcemap: false,
|
|
161
|
-
banner: {
|
|
161
|
+
banner: {
|
|
162
|
+
js: `import { createRequire as __mmCreateRequire } from 'node:module';const require = __mmCreateRequire(import.meta.url);process.argv[1] = ${JSON.stringify(entryReal)};`
|
|
163
|
+
},
|
|
162
164
|
plugins: [bindImportMetaUrls()],
|
|
163
165
|
logLevel: "silent"
|
|
164
166
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
function isRecord(value) {
|
|
2
2
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
3
3
|
}
|
|
4
|
-
function validateMetaMaskActionInputs(recipe) {
|
|
4
|
+
function validateMetaMaskActionInputs(recipe, adapter) {
|
|
5
5
|
if (!isRecord(recipe) || !isRecord(recipe.workflow)) return [];
|
|
6
6
|
const nodes = isRecord(recipe.workflow.nodes) ? recipe.workflow.nodes : void 0;
|
|
7
7
|
if (!nodes) return [];
|
|
@@ -9,6 +9,52 @@ function validateMetaMaskActionInputs(recipe) {
|
|
|
9
9
|
for (const [nodeId, node] of Object.entries(nodes)) {
|
|
10
10
|
if (!isRecord(node)) continue;
|
|
11
11
|
const action = node.action;
|
|
12
|
+
if (action === "metamask.perps.read_snapshot" && adapter === "extension") {
|
|
13
|
+
if (node.market !== void 0 && node.markets !== void 0) {
|
|
14
|
+
findings.push({
|
|
15
|
+
severity: "error",
|
|
16
|
+
code: "recipe.invalid_param",
|
|
17
|
+
path: `workflow.nodes.${nodeId}.markets`,
|
|
18
|
+
message: `${action} accepts market or markets, not both`
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
if (isRecord(node.history)) {
|
|
22
|
+
const start = node.history.start_time_ms;
|
|
23
|
+
const end = node.history.end_time_ms;
|
|
24
|
+
if (typeof start === "number" && typeof end === "number" && start > end) {
|
|
25
|
+
findings.push({
|
|
26
|
+
severity: "error",
|
|
27
|
+
code: "recipe.invalid_param",
|
|
28
|
+
path: `workflow.nodes.${nodeId}.history.start_time_ms`,
|
|
29
|
+
message: `${action} history.start_time_ms must not exceed history.end_time_ms`
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
if (typeof node.history.limit === "number" && node.history.limit > 500) {
|
|
33
|
+
findings.push({
|
|
34
|
+
severity: "error",
|
|
35
|
+
code: "recipe.invalid_param",
|
|
36
|
+
path: `workflow.nodes.${nodeId}.history.limit`,
|
|
37
|
+
message: `${action} history.limit must not exceed 500`
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (typeof node.max_items_per_section === "number" && node.max_items_per_section > 500) {
|
|
42
|
+
findings.push({
|
|
43
|
+
severity: "error",
|
|
44
|
+
code: "recipe.invalid_param",
|
|
45
|
+
path: `workflow.nodes.${nodeId}.max_items_per_section`,
|
|
46
|
+
message: `${action} max_items_per_section must not exceed 500`
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
if (typeof node.timeout_ms === "number" && node.timeout_ms > 6e4) {
|
|
50
|
+
findings.push({
|
|
51
|
+
severity: "error",
|
|
52
|
+
code: "recipe.invalid_param",
|
|
53
|
+
path: `workflow.nodes.${nodeId}.timeout_ms`,
|
|
54
|
+
message: `${action} timeout_ms must not exceed 60000`
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
12
58
|
const state = typeof node.state === "string" ? node.state.toLowerCase() : void 0;
|
|
13
59
|
const createsOrder = action === "metamask.perps.place_order" || (action === "metamask.perps.ensure_positions" || action === "metamask.perps.ensure_orders") && (state === "open" || state === "present");
|
|
14
60
|
if (!createsOrder) continue;
|
package/dist/mm-harness-cli.js
CHANGED
|
@@ -199,6 +199,7 @@ Example:
|
|
|
199
199
|
--list List actions accepted by call for this adapter; no <action> needed
|
|
200
200
|
--arg k=v Action field value (repeatable; equivalent to key=value shorthand)
|
|
201
201
|
--device <udid|serial|name> Mobile only: target this device (sets IOS_SIMULATOR / ADB_SERIAL and updates agentic-runtime.json). Without it, >1 connected mobile device fails fast and lists them. Do not shut down other devices to force selection.
|
|
202
|
+
--cdp-port <port> Extension only: bind the action to this live browser runtime.
|
|
202
203
|
--adapter <mobile|extension|core> Target adapter (auto-detected inside a checkout)
|
|
203
204
|
--target <path> Checkout path (default: cwd)
|
|
204
205
|
--artifacts-dir <dir> Where to write evidence (default: temp dir)
|
|
@@ -452,7 +453,7 @@ Example:
|
|
|
452
453
|
Extension quick launch REUSES a healthy live runtime (reload in place, no rebuild);
|
|
453
454
|
--build is the escape hatch that always clean-builds + relaunches a fresh runtime.
|
|
454
455
|
|
|
455
|
-
--build Full
|
|
456
|
+
--build Full production-like build + relaunch (escape hatch; skips extension reuse)
|
|
456
457
|
--clear-metro Mobile only: clear Metro's transform cache before launch
|
|
457
458
|
--verify Launch then poll CDP/bridge until ready (absorbs the old \`live\`)
|
|
458
459
|
--runway Post-launch runway check (mobile only; teaching error elsewhere)
|
|
@@ -537,11 +538,41 @@ Example:
|
|
|
537
538
|
mm-harness update
|
|
538
539
|
mm-harness update --check --json`
|
|
539
540
|
},
|
|
541
|
+
{
|
|
542
|
+
name: "runtime-launch",
|
|
543
|
+
summary: "Launch and verify an Extension runtime or local Android release artifact.",
|
|
544
|
+
example: "mm-harness runtime-launch --adapter extension --target <checkout> --cdp-port 9222 --artifact-file <zip> --artifact-version <X.Y.Z> --artifact-sha256 <digest>",
|
|
545
|
+
helpText: `mm-harness runtime-launch --adapter <extension|mobile> --target <checkout> [flags]
|
|
546
|
+
|
|
547
|
+
Verify and run an artifact already downloaded by the validation skill. The
|
|
548
|
+
harness performs no network acquisition. Extension ZIPs run through the
|
|
549
|
+
packaged browser/CDP path; Android APKs install to one explicitly named device.
|
|
550
|
+
|
|
551
|
+
--artifact-file <path> Artifact downloaded by the validation skill
|
|
552
|
+
--artifact-version <X.Y.Z> Expected manifest or Android version name
|
|
553
|
+
--artifact-build-id <id> Android version code
|
|
554
|
+
--artifact-package-id <id> Android application ID
|
|
555
|
+
--artifact-sha256 <digest> Trusted SHA-256 from the acquisition step
|
|
556
|
+
--artifact-cache <dir> Extension artifact cache root
|
|
557
|
+
--platform <android> Mobile platform (downloaded iOS is unsupported)
|
|
558
|
+
--device <serial> Explicit Android device serial
|
|
559
|
+
--cdp-port <port> Extension CDP port
|
|
560
|
+
--chrome-user-data-dir <dir> Isolated Chrome profile path
|
|
561
|
+
--artifacts-dir <dir> Validation evidence directory
|
|
562
|
+
--start-watch Build through the checkout watcher
|
|
563
|
+
--build-lavamoat Build through the production-like path
|
|
564
|
+
--remote-flag <KEY=VARIANT> Pin checkout snapshot flags (not allowed for artifacts)
|
|
565
|
+
--json Machine-readable terminal report
|
|
566
|
+
|
|
567
|
+
Example:
|
|
568
|
+
mm-harness runtime-launch --adapter extension --target /path/to/checkout --cdp-port 9222 --artifact-file /path/to/metamask-chrome-X.Y.Z.zip --artifact-version X.Y.Z --artifact-sha256 <digest>
|
|
569
|
+
mm-harness runtime-launch --adapter mobile --platform android --target /path/to/checkout --device <serial> --artifact-file /path/to/app.apk --artifact-version X.Y.Z --artifact-build-id <code> --artifact-package-id io.metamask --artifact-sha256 <digest>`
|
|
570
|
+
},
|
|
540
571
|
{
|
|
541
572
|
name: "fixtures",
|
|
542
|
-
summary: "Manage the canonical wallet fixture (wallet DATA only) \u2014 init / sync / set / generate / finalize.",
|
|
573
|
+
summary: "Manage the canonical wallet fixture (wallet DATA only) \u2014 init / sync / set / reset / generate / finalize.",
|
|
543
574
|
example: "mm-harness fixtures set",
|
|
544
|
-
helpText: `mm-harness fixtures [<init|sync|set|generate|finalize>] [flags]
|
|
575
|
+
helpText: `mm-harness fixtures [<init|sync|set|reset|generate|finalize>] [flags]
|
|
545
576
|
|
|
546
577
|
Manage the ONE canonical wallet fixture per checkout \u2014 wallet DATA only.
|
|
547
578
|
With no action, show the current fixture status and safe next command.
|
|
@@ -550,6 +581,8 @@ Example:
|
|
|
550
581
|
sync Refresh the wallet fixture files on the target.
|
|
551
582
|
set Apply the canonical fixture (SRP/password/accounts); the password is
|
|
552
583
|
read FROM the fixture, never typed.
|
|
584
|
+
reset Delete the current Mobile/Extension wallet, then apply and validate
|
|
585
|
+
the canonical fixture from a clean state.
|
|
553
586
|
generate Render the extension fixture-state.json from a wallet fixture, for
|
|
554
587
|
pre-launch profile prefill (extension-only). Requires --fixture and --out.
|
|
555
588
|
finalize Seed account labels/selection into the LIVE extension over CDP and
|
|
@@ -578,6 +611,7 @@ Example:
|
|
|
578
611
|
mm-harness fixtures init --dev
|
|
579
612
|
mm-harness fixtures sync
|
|
580
613
|
mm-harness fixtures set
|
|
614
|
+
mm-harness fixtures reset
|
|
581
615
|
mm-harness fixtures generate --fixture wallet-fixture.json --out fixture-state.json
|
|
582
616
|
mm-harness fixtures finalize --fixture wallet-fixture.json --state fixture-state.json --cdp-port 6661 --extension-dir dist/chrome`
|
|
583
617
|
}
|
|
@@ -742,7 +776,6 @@ const HIDDEN = [
|
|
|
742
776
|
"completion-candidates",
|
|
743
777
|
"runtime-health",
|
|
744
778
|
"runtime-decision",
|
|
745
|
-
"runtime-launch",
|
|
746
779
|
"resolve-extension",
|
|
747
780
|
"ensure-ready",
|
|
748
781
|
"self-test"
|
package/dist/recipe-security.js
CHANGED
|
@@ -4,6 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
const READ_ONLY_CUSTOM_ACTIONS = /* @__PURE__ */ new Set([
|
|
5
5
|
"ui.locators",
|
|
6
6
|
"metamask.wallet.fixture_status",
|
|
7
|
+
"metamask.wallet.validate_import",
|
|
7
8
|
"metamask.wallet.list_accounts",
|
|
8
9
|
"metamask.wallet.read_state",
|
|
9
10
|
"metamask.perps.read_positions",
|
|
@@ -19,6 +20,8 @@ const READ_ONLY_CUSTOM_ACTIONS = /* @__PURE__ */ new Set([
|
|
|
19
20
|
]);
|
|
20
21
|
const APP_MUTATION_CUSTOM_ACTIONS = /* @__PURE__ */ new Set([
|
|
21
22
|
"metamask.wallet.setup",
|
|
23
|
+
"metamask.wallet.import",
|
|
24
|
+
"metamask.wallet.reset",
|
|
22
25
|
"metamask.wallet.ensure_unlocked",
|
|
23
26
|
"metamask.wallet.select_account",
|
|
24
27
|
"metamask.deeplink.open",
|