@deeeed/metamask-harness 0.34.4 → 0.35.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 +10 -0
- package/adapters/core/inject.sh +3 -3
- package/adapters/extension/inject.mjs +3 -4
- package/adapters/mobile/inject.sh +4 -3
- package/adapters/mobile/verify.sh +37 -0
- package/dist/adapters/extension/runtime.js +60 -15
- package/dist/adapters/extension/surface.js +6 -1
- package/dist/adapters/mobile/surface.js +6 -1
- package/dist/doctor.js +30 -1
- package/package.json +1 -1
- package/scripts/site-contrast.mjs +7 -0
- package/site/architecture.html +14 -7
- package/site/assets/metamask-fox.svg +24 -0
- package/site/assets/progress.mjs +6 -0
- package/site/assets/style.css +88 -29
- package/site/cheatsheet.html +5 -6
- package/site/ecosystem.html +162 -0
- package/site/how-it-works.html +8 -9
- package/site/index.html +17 -12
- package/site/perps.html +103 -45
- package/site/recipes.html +5 -6
- package/site/reviewers.html +5 -6
- package/site/tutorials/index.html +5 -6
- package/site/tutorials/v1.html +5 -6
- package/site/tutorials/v2.html +6 -8
- package/site/tutorials/v3.html +5 -6
- package/site/tutorials/v4.html +5 -6
- package/site/tutorials/v5.html +5 -6
- package/site/tutorials/v6.html +5 -6
- package/site/tutorials/v7.html +5 -6
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.35.0 - 2026-08-12
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Make Extension and Mobile launch verification fail when their in-app EVM network client cannot complete a live RPC, with exact Infura configuration and retry guidance.
|
|
10
|
+
- Make Extension doctor require checkout-pinned Playwright Chromium and report the exact install command when it is missing.
|
|
11
|
+
- Make doctor report the exact dependency-install command when `--fix` finds stale or missing dependencies instead of implying they were repaired.
|
|
12
|
+
- Make the setup prompt reuse configured skill sources and require consent before cloning or editing shell startup files.
|
|
13
|
+
- Keep installed overlays delegated to the source runner that installed them before falling back to a potentially stale global runner.
|
|
14
|
+
|
|
5
15
|
## 0.34.4 - 2026-08-12
|
|
6
16
|
|
|
7
17
|
### Changed
|
package/adapters/core/inject.sh
CHANGED
|
@@ -88,11 +88,11 @@ install_v1_runner_assets() {
|
|
|
88
88
|
if [ -n "$METAMASK_RUNNER_PROTOCOL_ROOT" ]; then
|
|
89
89
|
printf 'export FARMSLOT_ROOT=${FARMSLOT_ROOT:-%s}\n' "$runner_protocol_root_q"
|
|
90
90
|
fi
|
|
91
|
-
#
|
|
92
|
-
#
|
|
91
|
+
# The recorded runner owns the installed overlay and must remain ahead of an
|
|
92
|
+
# unrelated global version. MM_HARNESS_BIN is the explicit override.
|
|
93
93
|
printf '%s\n' 'if [ -n "${MM_HARNESS_BIN:-}" ] && [ -x "${MM_HARNESS_BIN}" ]; then exec "${MM_HARNESS_BIN}" "$@"; fi'
|
|
94
|
-
printf '%s\n' 'if command -v mm-harness >/dev/null 2>&1; then exec mm-harness "$@"; fi'
|
|
95
94
|
printf 'if [ -x %s ]; then exec %s "$@"; fi\n' "$runner_exec_q" "$runner_exec_q"
|
|
95
|
+
printf '%s\n' 'if command -v mm-harness >/dev/null 2>&1; then exec mm-harness "$@"; fi'
|
|
96
96
|
printf '%s\n' 'echo "mm-harness not found. Next: npm i -g @deeeed/metamask-harness" >&2'
|
|
97
97
|
printf '%s\n' 'exit 127'
|
|
98
98
|
} > "$HARNESS_DIR/runner/bin/mm-harness"
|
|
@@ -69,17 +69,16 @@ fs.mkdirSync(path.join(harnessDir, 'runner/recipes'), { recursive: true });
|
|
|
69
69
|
fs.rmSync(path.join(harnessDir, 'scripts'), { recursive: true, force: true });
|
|
70
70
|
fs.mkdirSync(path.join(harnessDir, 'scripts/lib'), { recursive: true });
|
|
71
71
|
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
// the runner recorded at install time as a last resort; teach install on miss.
|
|
72
|
+
// Prefer the runner that installed the overlay so its runtime checks match the
|
|
73
|
+
// installed scripts. Explicit overrides still support controlled delegation.
|
|
75
74
|
const pinnedBin = shellQuote(path.join(runnerDir, 'bin/mm-harness'));
|
|
76
75
|
const delegate = [
|
|
77
76
|
'#!/usr/bin/env bash',
|
|
78
77
|
'set -euo pipefail',
|
|
79
78
|
protocolRoot ? `export FARMSLOT_ROOT=\${FARMSLOT_ROOT:-${shellQuote(protocolRoot)}}` : null,
|
|
80
79
|
'if [ -n "${MM_HARNESS_BIN:-}" ] && [ -x "${MM_HARNESS_BIN}" ]; then exec "${MM_HARNESS_BIN}" "$@"; fi',
|
|
81
|
-
'if command -v mm-harness >/dev/null 2>&1; then exec mm-harness "$@"; fi',
|
|
82
80
|
`if [ -x ${pinnedBin} ]; then exec ${pinnedBin} "$@"; fi`,
|
|
81
|
+
'if command -v mm-harness >/dev/null 2>&1; then exec mm-harness "$@"; fi',
|
|
83
82
|
'echo "mm-harness not found. Next: npm i -g @deeeed/metamask-harness" >&2',
|
|
84
83
|
'exit 127',
|
|
85
84
|
].filter(Boolean).join('\n') + '\n';
|
|
@@ -44,7 +44,7 @@ for rel in "$HARNESS_REL" "$HARNESS_REL/runner" "$HARNESS_REL/scripts"; do
|
|
|
44
44
|
done
|
|
45
45
|
done
|
|
46
46
|
|
|
47
|
-
rm -rf "$HARNESS_DIR/runner" "$HARNESS_DIR/scripts"
|
|
47
|
+
rm -rf "$HARNESS_DIR/runner" "$HARNESS_DIR/scripts" "$HARNESS_DIR/bridge-runtime"
|
|
48
48
|
mkdir -p "$HARNESS_DIR/runner/bin" "$HARNESS_DIR/runner/manifests" "$HARNESS_DIR/runner/recipes" "$HARNESS_DIR/scripts/lib"
|
|
49
49
|
|
|
50
50
|
RUNNER_EXEC_Q="$(printf '%q' "$RUNNER_DIR/bin/mm-harness")"
|
|
@@ -62,8 +62,8 @@ PROTOCOL_ROOT_Q="$(printf '%q' "$PROTOCOL_ROOT")"
|
|
|
62
62
|
printf 'export METAMASK_RUNNER_PROTOCOL_ROOT=${METAMASK_RUNNER_PROTOCOL_ROOT:-%s}\n' "$PROTOCOL_ROOT_Q"
|
|
63
63
|
fi
|
|
64
64
|
printf '%s\n' 'if [ -n "${MM_HARNESS_BIN:-}" ] && [ -x "$MM_HARNESS_BIN" ]; then exec "$MM_HARNESS_BIN" "$@"; fi'
|
|
65
|
-
printf '%s\n' 'if command -v mm-harness >/dev/null 2>&1; then exec mm-harness "$@"; fi'
|
|
66
65
|
printf 'if [ -x %s ]; then exec %s "$@"; fi\n' "$RUNNER_EXEC_Q" "$RUNNER_EXEC_Q"
|
|
66
|
+
printf '%s\n' 'if command -v mm-harness >/dev/null 2>&1; then exec mm-harness "$@"; fi'
|
|
67
67
|
printf '%s\n' 'echo "mm-harness not found. Next: npm i -g @deeeed/metamask-harness@latest" >&2' 'exit 127'
|
|
68
68
|
} > "$HARNESS_DIR/runner/bin/mm-harness"
|
|
69
69
|
chmod +x "$HARNESS_DIR/runner/bin/mm-harness"
|
|
@@ -77,6 +77,7 @@ rsync -a --delete "$RUNNER_DIR/library/recipes/" "$HARNESS_DIR/runner/recipes/"
|
|
|
77
77
|
rm -rf "$HARNESS_DIR/runner/flows"
|
|
78
78
|
rm -f "$HARNESS_DIR/runner/library.json"
|
|
79
79
|
cp "$RUNNER_DIR/adapters/mobile/verify.sh" "$HARNESS_DIR/scripts/verify.sh"
|
|
80
|
+
cp -R "$RUNNER_DIR/adapters/mobile/bridge-runtime" "$HARNESS_DIR/bridge-runtime"
|
|
80
81
|
cp "$RUNNER_DIR/adapters/shared/harness-path.sh" "$HARNESS_DIR/scripts/lib/harness-path.sh"
|
|
81
82
|
cp "$RUNNER_DIR/adapters/shared/path-defaults.json" "$HARNESS_DIR/scripts/lib/path-defaults.json"
|
|
82
83
|
chmod -R u+rwX,go+rX "$HARNESS_DIR"
|
|
@@ -114,7 +115,7 @@ fs.writeFileSync(manifestPath, JSON.stringify({
|
|
|
114
115
|
actionManifestPath: `${harnessRel}/action-manifest.json`,
|
|
115
116
|
runnerEntrypoint: `${harnessRel}/runner/bin/mm-harness`,
|
|
116
117
|
runtimeHelpers: { verify: `${harnessRel}/scripts/verify.sh` },
|
|
117
|
-
harnessInstalledPaths: [`${harnessRel}/runner`, `${harnessRel}/scripts`, `${harnessRel}/action-manifest.json`],
|
|
118
|
+
harnessInstalledPaths: [`${harnessRel}/runner`, `${harnessRel}/scripts`, `${harnessRel}/bridge-runtime`, `${harnessRel}/action-manifest.json`],
|
|
118
119
|
productOwnedPaths: ['app/dev-tools/AgenticService', 'app/core/NavigationService/NavigationService.ts'],
|
|
119
120
|
cleanupCommand,
|
|
120
121
|
productDiffExcludes: [`:(exclude)${harnessRoot}`],
|
|
@@ -325,6 +325,35 @@ JSON
|
|
|
325
325
|
) > "$log_path" 2>&1
|
|
326
326
|
}
|
|
327
327
|
|
|
328
|
+
evm_rpc_ready() {
|
|
329
|
+
local log_path="$1"
|
|
330
|
+
local bridge="$HARNESS_DIR/bridge-runtime/cdp-bridge.cjs"
|
|
331
|
+
local expression
|
|
332
|
+
expression="(async function(){try{var engine=globalThis.Engine;var controller=engine&&engine.context&&engine.context.NetworkController;if(!controller||typeof controller.getSelectedNetworkClient!=='function')throw new Error('NetworkController selected client is unavailable');var client=controller.getSelectedNetworkClient();if(!client||!client.provider||typeof client.provider.request!=='function')throw new Error('selected EVM provider is unavailable');var result=await client.provider.request({method:'eth_blockNumber'});if(typeof result!=='string'||!/^0x[0-9a-f]+$/i.test(result))throw new Error('eth_blockNumber returned an invalid result');return {ok:true,result:result};}catch(error){return {ok:false,error:error&&(error.message||String(error))};}})()"
|
|
333
|
+
[ -f "$bridge" ] || {
|
|
334
|
+
printf 'Mobile bridge is missing at %s\n' "$bridge" > "$log_path"
|
|
335
|
+
return 1
|
|
336
|
+
}
|
|
337
|
+
if ! run_with_timeout "$log_path" 25 env \
|
|
338
|
+
APP_ROOT="$TARGET" \
|
|
339
|
+
WATCHER_PORT="$port" \
|
|
340
|
+
IOS_SIMULATOR="${IOS_SIMULATOR:-$ios_simulator_resolved}" \
|
|
341
|
+
ADB_SERIAL="${ADB_SERIAL:-$adb_serial_resolved}" \
|
|
342
|
+
ANDROID_SERIAL="${ANDROID_SERIAL:-$adb_serial_resolved}" \
|
|
343
|
+
node "$bridge" eval-async "$expression"; then
|
|
344
|
+
return 1
|
|
345
|
+
fi
|
|
346
|
+
node - "$log_path" <<'NODE'
|
|
347
|
+
const fs = require('fs');
|
|
348
|
+
try {
|
|
349
|
+
const result = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
|
|
350
|
+
process.exit(result.ok === true ? 0 : 1);
|
|
351
|
+
} catch {
|
|
352
|
+
process.exit(1);
|
|
353
|
+
}
|
|
354
|
+
NODE
|
|
355
|
+
}
|
|
356
|
+
|
|
328
357
|
ensure_live_runtime() {
|
|
329
358
|
local device_target="$1"
|
|
330
359
|
local attempt
|
|
@@ -419,6 +448,14 @@ JSON
|
|
|
419
448
|
add_note "Runner v1 live bridge smoke failed; inspect logs/runner-live-smoke.log and runner-live-smoke/trace.json."
|
|
420
449
|
status="fail"
|
|
421
450
|
fi
|
|
451
|
+
|
|
452
|
+
if evm_rpc_ready "$ARTIFACTS/logs/evm-rpc-readiness.log"; then
|
|
453
|
+
checks+=("{\"name\":\"live EVM RPC readiness\",\"status\":\"pass\"}")
|
|
454
|
+
else
|
|
455
|
+
checks+=("{\"name\":\"live EVM RPC readiness\",\"status\":\"fail\",\"detail\":\"see logs/evm-rpc-readiness.log\"}")
|
|
456
|
+
add_note "EVM RPC readiness probe failed. Infura configured ≠ RPC reachable. Next: confirm $TARGET/.js.env has a real MM_INFURA_PROJECT_ID (not 00000000000), then rerun mm-harness launch $PLATFORM --verify --target '$TARGET'."
|
|
457
|
+
status="fail"
|
|
458
|
+
fi
|
|
422
459
|
fi
|
|
423
460
|
|
|
424
461
|
RECIPE_HARNESS_PREFLIGHT_MODE="$PREFLIGHT_MODE" RECIPE_HARNESS_ROOT_EXCLUDE="$HARNESS_ROOT" node - "$ARTIFACTS" "$TARGET" "$status" "${checks[@]}" <<'NODE'
|
|
@@ -153,12 +153,26 @@ async function checkExtensionRuntimeHealth(projectRoot, cdpPort, options = {}) {
|
|
|
153
153
|
if (runtime.backgroundUnresponsive === true) {
|
|
154
154
|
findings.push("Extension UI reports background connection unresponsive.");
|
|
155
155
|
}
|
|
156
|
+
if (runtime.ethereumConnectionUnavailable === true) {
|
|
157
|
+
findings.push('Extension UI reports "Unable to connect to Ethereum".');
|
|
158
|
+
}
|
|
159
|
+
if (runtime.evmRpcProbeOk !== true) {
|
|
160
|
+
const probeError = String(runtime.evmRpcProbeError ?? "unknown error").replace(/[.\s]+$/u, "");
|
|
161
|
+
findings.push(
|
|
162
|
+
`EVM RPC readiness probe failed: ${probeError}. Infura configured \u2260 RPC reachable.`
|
|
163
|
+
);
|
|
164
|
+
}
|
|
156
165
|
if (runtime.hasSubmitRequest === true && runtime.backgroundProbeOk !== true) {
|
|
157
166
|
warnings.push(`Perps background read probe failed: ${runtime.backgroundProbeError ?? "unknown error"}.`);
|
|
158
167
|
}
|
|
159
168
|
const extensionId = safeExtensionId(target);
|
|
169
|
+
const evmRpcUnreachable = runtime.ethereumConnectionUnavailable === true || runtime.evmRpcProbeOk !== true;
|
|
160
170
|
return {
|
|
161
171
|
status: findings.length === 0 ? "PASS" : "FAIL",
|
|
172
|
+
...evmRpcUnreachable ? {
|
|
173
|
+
errorCode: "EVM_RPC_UNREACHABLE",
|
|
174
|
+
userAction: evmRpcRecoveryAction(projectRoot)
|
|
175
|
+
} : {},
|
|
162
176
|
warnings,
|
|
163
177
|
cdpPort,
|
|
164
178
|
targetUrl: target.url,
|
|
@@ -219,21 +233,42 @@ async function evaluateHealth(session, timeoutMs) {
|
|
|
219
233
|
const storeState = hooks.store?.getState?.() || {};
|
|
220
234
|
const manager = hooks.getPerpsStreamManager?.();
|
|
221
235
|
const accountCache = manager?.account?.cache;
|
|
236
|
+
const networkConnectionBanner = storeState.metamask?.networkConnectionBanner;
|
|
237
|
+
const networkClientId = storeState.metamask?.selectedNetworkClientId || networkConnectionBanner?.networkClientId;
|
|
222
238
|
return Promise.race([
|
|
223
239
|
(async () => {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
240
|
+
const [backgroundProbe, evmRpcProbe] = await Promise.all([
|
|
241
|
+
(async () => {
|
|
242
|
+
if (typeof hooks.submitRequestToBackground !== 'function') {
|
|
243
|
+
return { ok: false, error: 'submitRequestToBackground is not a function' };
|
|
244
|
+
}
|
|
245
|
+
try {
|
|
246
|
+
const accountState = await hooks.submitRequestToBackground('perpsGetAccountState', []);
|
|
247
|
+
const ok = Boolean(accountState && typeof accountState === 'object');
|
|
248
|
+
return { ok, error: ok ? null : 'perpsGetAccountState returned an empty result' };
|
|
249
|
+
} catch (error) {
|
|
250
|
+
return { ok: false, error: String(error?.message || error) };
|
|
251
|
+
}
|
|
252
|
+
})(),
|
|
253
|
+
(async () => {
|
|
254
|
+
if (typeof hooks.submitRequestToBackground !== 'function') {
|
|
255
|
+
return { ok: false, error: 'submitRequestToBackground is not a function' };
|
|
256
|
+
}
|
|
257
|
+
if (typeof networkClientId !== 'string' || networkClientId.length === 0) {
|
|
258
|
+
return { ok: false, error: 'selected EVM network client is unavailable' };
|
|
259
|
+
}
|
|
260
|
+
try {
|
|
261
|
+
const code = await hooks.submitRequestToBackground('getCode', [
|
|
262
|
+
'0x0000000000000000000000000000000000000000',
|
|
263
|
+
networkClientId,
|
|
264
|
+
]);
|
|
265
|
+
const ok = typeof code === 'string' && code.startsWith('0x');
|
|
266
|
+
return { ok, error: ok ? null : 'background EVM RPC returned an invalid result' };
|
|
267
|
+
} catch (error) {
|
|
268
|
+
return { ok: false, error: String(error?.message || error) };
|
|
269
|
+
}
|
|
270
|
+
})(),
|
|
271
|
+
]);
|
|
237
272
|
return {
|
|
238
273
|
href: location.href,
|
|
239
274
|
title: document.title,
|
|
@@ -242,14 +277,17 @@ async function evaluateHealth(session, timeoutMs) {
|
|
|
242
277
|
hasStore: Boolean(hooks.store),
|
|
243
278
|
hasPerpsStreamManager: typeof hooks.getPerpsStreamManager === 'function',
|
|
244
279
|
backgroundUnresponsive: bodyText.includes('Background connection unresponsive') || bodyText.includes('MetaMask had trouble starting'),
|
|
280
|
+
ethereumConnectionUnavailable: bodyText.includes('Unable to connect to Ethereum'),
|
|
245
281
|
activeProvider: storeState.metamask?.activeProvider || null,
|
|
246
282
|
isTestnet: Boolean(storeState.metamask?.isTestnet),
|
|
247
283
|
perpsManagerInitialized: Boolean(manager?.isInitialized?.()),
|
|
248
284
|
positionsCacheIsArray: Array.isArray(manager?.positions?.cache),
|
|
249
285
|
ordersCacheIsArray: Array.isArray(manager?.orders?.cache),
|
|
250
286
|
accountCachePresent: Boolean(accountCache && typeof accountCache === 'object'),
|
|
251
|
-
backgroundProbeOk,
|
|
252
|
-
backgroundProbeError,
|
|
287
|
+
backgroundProbeOk: backgroundProbe.ok,
|
|
288
|
+
backgroundProbeError: backgroundProbe.error,
|
|
289
|
+
evmRpcProbeOk: evmRpcProbe.ok,
|
|
290
|
+
evmRpcProbeError: evmRpcProbe.error,
|
|
253
291
|
};
|
|
254
292
|
})(),
|
|
255
293
|
new Promise((resolve) => setTimeout(() => resolve({
|
|
@@ -260,8 +298,11 @@ async function evaluateHealth(session, timeoutMs) {
|
|
|
260
298
|
hasStore: Boolean(hooks.store),
|
|
261
299
|
hasPerpsStreamManager: typeof hooks.getPerpsStreamManager === 'function',
|
|
262
300
|
backgroundUnresponsive: bodyText.includes('Background connection unresponsive') || bodyText.includes('MetaMask had trouble starting'),
|
|
301
|
+
ethereumConnectionUnavailable: bodyText.includes('Unable to connect to Ethereum'),
|
|
263
302
|
backgroundProbeOk: false,
|
|
264
303
|
backgroundProbeError: 'perpsGetAccountState timed out after ${backgroundProbeTimeoutMs}ms',
|
|
304
|
+
evmRpcProbeOk: false,
|
|
305
|
+
evmRpcProbeError: 'background EVM RPC timed out after ${backgroundProbeTimeoutMs}ms',
|
|
265
306
|
}), ${backgroundProbeTimeoutMs})),
|
|
266
307
|
]);
|
|
267
308
|
})()`,
|
|
@@ -293,6 +334,9 @@ function compositorRecoveryAction(projectRoot) {
|
|
|
293
334
|
const recovery = `mm-harness launch --verify --target ${JSON.stringify(projectRoot)}`;
|
|
294
335
|
return process.platform === "darwin" ? `Unlock the macOS session, then rerun: ${recovery}` : `Restore an active display compositor, then rerun: ${recovery}`;
|
|
295
336
|
}
|
|
337
|
+
function evmRpcRecoveryAction(projectRoot) {
|
|
338
|
+
return `Confirm ${shellQuote(path.join(projectRoot, ".metamaskrc"))} has a real INFURA_PROJECT_ID (not 00000000000), then rerun: mm-harness launch --verify --target ${shellQuote(projectRoot)}`;
|
|
339
|
+
}
|
|
296
340
|
function resolveCdpPort(rawPort, slot) {
|
|
297
341
|
const raw = rawPort ?? process.env.CDP_PORT ?? process.env.RECIPE_CDP_PORT ?? slot?.cdpPort;
|
|
298
342
|
const port = Number(raw);
|
|
@@ -759,6 +803,7 @@ export {
|
|
|
759
803
|
assertHealthyExtensionRuntime,
|
|
760
804
|
checkExtensionRuntimeHealth,
|
|
761
805
|
compositorRecoveryAction,
|
|
806
|
+
evmRpcRecoveryAction,
|
|
762
807
|
extensionBackgroundProbeTimeoutMs,
|
|
763
808
|
formatHealthFailure,
|
|
764
809
|
prepareExtensionRuntime
|
|
@@ -14,6 +14,10 @@ function watcherStatus(buildLog) {
|
|
|
14
14
|
if (buildLog === "no-watch") return "down";
|
|
15
15
|
return buildLog;
|
|
16
16
|
}
|
|
17
|
+
function extensionRuntimeNextAction(reasonCode, target) {
|
|
18
|
+
const resolved = shellQuote(path.resolve(target));
|
|
19
|
+
return reasonCode === "deps-missing" || reasonCode === "deps-stale" ? `cd ${resolved} && yarn install --immutable` : `mm-harness launch --adapter extension --target ${resolved}`;
|
|
20
|
+
}
|
|
17
21
|
const extensionSurface = {
|
|
18
22
|
adapter: "extension",
|
|
19
23
|
headless: false,
|
|
@@ -42,7 +46,7 @@ const extensionSurface = {
|
|
|
42
46
|
decision: report.decision,
|
|
43
47
|
reasonCode: report.reasonCode,
|
|
44
48
|
reasons: report.reasons,
|
|
45
|
-
nextAction: report.decision === "ready" ? void 0 :
|
|
49
|
+
nextAction: report.decision === "ready" ? void 0 : extensionRuntimeNextAction(report.reasonCode, target),
|
|
46
50
|
deps: report.checks.deps.status,
|
|
47
51
|
devServer: {
|
|
48
52
|
label: "webpack",
|
|
@@ -96,5 +100,6 @@ const extensionSurface = {
|
|
|
96
100
|
}
|
|
97
101
|
};
|
|
98
102
|
export {
|
|
103
|
+
extensionRuntimeNextAction,
|
|
99
104
|
extensionSurface
|
|
100
105
|
};
|
|
@@ -5,6 +5,10 @@ import { shellQuote } from "../../commands/parse-args.js";
|
|
|
5
5
|
import { resolveMobileSlotPorts } from "../slot-ports.js";
|
|
6
6
|
import { mobileRuntimeStatus } from "./prepare.js";
|
|
7
7
|
import { hasRunwayProvisionBaseline, provisionRunwayMobile } from "./provision.js";
|
|
8
|
+
function mobileRuntimeNextAction(reasonCode, target, platform) {
|
|
9
|
+
const resolved = shellQuote(path.resolve(target));
|
|
10
|
+
return reasonCode === "deps-missing" || reasonCode === "deps-partial" || reasonCode === "deps-stale" ? `cd ${resolved} && yarn setup` : `mm-harness launch ${platform} --adapter mobile --target ${resolved}`;
|
|
11
|
+
}
|
|
8
12
|
const mobileSurface = {
|
|
9
13
|
adapter: "mobile",
|
|
10
14
|
headless: false,
|
|
@@ -21,7 +25,7 @@ const mobileSurface = {
|
|
|
21
25
|
decision: depsPending ? "launch" : report.decision,
|
|
22
26
|
reasonCode: depsPending ? "app-installed-deps-pending" : report.reasonCode,
|
|
23
27
|
reasons: depsPending ? ["Runway app is installed; JavaScript dependencies are pending until dispatch-time launch."] : report.reasons,
|
|
24
|
-
nextAction: report.decision === "ready" && !depsPending ? void 0 :
|
|
28
|
+
nextAction: report.decision === "ready" && !depsPending ? void 0 : mobileRuntimeNextAction(report.reasonCode, target, platform),
|
|
25
29
|
deps: runwayProvisioned && report.checks?.deps?.status !== "current" ? "pending" : report.checks?.deps?.status,
|
|
26
30
|
devServer: { label: "metro", status: report.checks?.metro?.status ?? "unprobed" }
|
|
27
31
|
};
|
|
@@ -58,5 +62,6 @@ const mobileSurface = {
|
|
|
58
62
|
}
|
|
59
63
|
};
|
|
60
64
|
export {
|
|
65
|
+
mobileRuntimeNextAction,
|
|
61
66
|
mobileSurface
|
|
62
67
|
};
|
package/dist/doctor.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { color } from "./cli-color.js";
|
|
4
5
|
import {
|
|
@@ -148,7 +149,8 @@ function createDoctorReport(adapter, target, manifestValidation, actionManifestP
|
|
|
148
149
|
required: false,
|
|
149
150
|
message: mode === "unsupported/no bridge" ? `No ${adapter} bridge is available for this checkout.` : `${adapter} compatibility mode: ${mode}.`
|
|
150
151
|
},
|
|
151
|
-
...adapter === "mobile" ? mobileToolDoctorChecks(mobilePlatform) : []
|
|
152
|
+
...adapter === "mobile" ? mobileToolDoctorChecks(mobilePlatform) : [],
|
|
153
|
+
...adapter === "extension" ? [extensionBrowserDoctorCheck(target)] : []
|
|
152
154
|
];
|
|
153
155
|
const requiredChecks = requiredDoctorCheckSummary(checks);
|
|
154
156
|
return {
|
|
@@ -169,6 +171,32 @@ function createDoctorReport(adapter, target, manifestValidation, actionManifestP
|
|
|
169
171
|
manifestValidation: manifestValidation.summary
|
|
170
172
|
};
|
|
171
173
|
}
|
|
174
|
+
function extensionBrowserDoctorCheck(target) {
|
|
175
|
+
const requireFromTarget = createRequire(path.join(path.resolve(target), "package.json"));
|
|
176
|
+
for (const packageName of ["@playwright/test", "playwright"]) {
|
|
177
|
+
try {
|
|
178
|
+
const playwright = requireFromTarget(packageName);
|
|
179
|
+
const executable = playwright.chromium?.executablePath?.();
|
|
180
|
+
if (executable && fs.existsSync(executable)) {
|
|
181
|
+
return {
|
|
182
|
+
id: "playwright-chromium",
|
|
183
|
+
status: "pass",
|
|
184
|
+
required: true,
|
|
185
|
+
message: "Checkout-pinned Playwright Chromium is installed.",
|
|
186
|
+
detail: executable
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
} catch {
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
id: "playwright-chromium",
|
|
194
|
+
status: "fail",
|
|
195
|
+
required: true,
|
|
196
|
+
message: "Checkout-pinned Playwright Chromium is not installed.",
|
|
197
|
+
userAction: "yarn playwright install chromium"
|
|
198
|
+
};
|
|
199
|
+
}
|
|
172
200
|
function mobileToolDoctorChecks(platform) {
|
|
173
201
|
const specs = platform === "android" ? [{ tool: "adb", platform: "Android" }] : platform === "ios" ? [{ tool: "idb", platform: "iOS" }] : [
|
|
174
202
|
{ tool: "adb", platform: "Android" },
|
|
@@ -250,6 +278,7 @@ function readJsonObject(file) {
|
|
|
250
278
|
export {
|
|
251
279
|
compatibilityMode,
|
|
252
280
|
createDoctorReport,
|
|
281
|
+
extensionBrowserDoctorCheck,
|
|
253
282
|
fixtureFileSummary,
|
|
254
283
|
fixtureSummary,
|
|
255
284
|
mobileToolDoctorChecks,
|
package/package.json
CHANGED
|
@@ -358,6 +358,13 @@ async function checkBehavior(browserEndpoint, port, pages) {
|
|
|
358
358
|
tab.click();
|
|
359
359
|
const panel = document.getElementById(tab.getAttribute('aria-controls'));
|
|
360
360
|
if (!panel || panel.hidden) throw new Error('tab ' + tab.id + ' did not reveal its panel');
|
|
361
|
+
// Panels ship visible so a dead module degrades to readable prompts
|
|
362
|
+
// rather than dead controls — which also means "revealed" alone
|
|
363
|
+
// proves nothing. Selecting one has to hide the others.
|
|
364
|
+
const shown = [...document.querySelectorAll('[role="tabpanel"]')].filter((p) => !p.hidden);
|
|
365
|
+
if (shown.length !== 1) {
|
|
366
|
+
throw new Error('tab ' + tab.id + ' left ' + shown.length + ' panels visible');
|
|
367
|
+
}
|
|
361
368
|
out.work.push(await grab(panel.querySelector('.cmd .copy')));
|
|
362
369
|
}
|
|
363
370
|
return out;
|
package/site/architecture.html
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
6
|
<title>Architecture — how the pieces fit</title>
|
|
7
7
|
<meta name="description" content="Skills, mm-harness, team recipe libraries, product repos: what lives where, the three workflows, and the mistakes newcomers make.">
|
|
8
|
-
<link rel="
|
|
8
|
+
<link rel="icon" href="assets/metamask-fox.svg" type="image/svg+xml">
|
|
9
|
+
<link rel="stylesheet" href="assets/style.css?v=4">
|
|
9
10
|
</head>
|
|
10
11
|
<body data-progress-page="architecture">
|
|
11
12
|
<a class="skip" href="#stack">Skip to the stack map</a>
|
|
@@ -13,18 +14,16 @@
|
|
|
13
14
|
<header class="topbar">
|
|
14
15
|
<div class="wrap topbar-inner">
|
|
15
16
|
<a class="brand" href="index.html">
|
|
16
|
-
<
|
|
17
|
+
<img class="brand-mark" src="assets/metamask-fox.svg" alt="" width="22" height="22">
|
|
17
18
|
<span class="brand-name">recipes</span>
|
|
18
19
|
</a>
|
|
19
20
|
<nav class="nav" aria-label="Main">
|
|
20
21
|
<a class="nav-cta" href="index.html#prompt">Quick start</a>
|
|
21
22
|
<a href="how-it-works.html">How it works</a>
|
|
22
|
-
<a href="
|
|
23
|
-
<a href="perps.html">Perps</a>
|
|
23
|
+
<a href="perps.html">Team / Perps</a>
|
|
24
24
|
<a href="cheatsheet.html">Cheatsheet</a>
|
|
25
25
|
<a href="architecture.html" aria-current="page">Architecture</a>
|
|
26
26
|
<a href="tutorials/index.html">Tutorials</a>
|
|
27
|
-
<a href="reviewers.html">For Reviewers</a>
|
|
28
27
|
</nav>
|
|
29
28
|
</div>
|
|
30
29
|
</header>
|
|
@@ -471,8 +470,16 @@
|
|
|
471
470
|
</ol>
|
|
472
471
|
|
|
473
472
|
<hr class="sep">
|
|
473
|
+
<h2>Which repo is which</h2>
|
|
474
|
+
<p>
|
|
475
|
+
The layers above are the design. The <a href="ecosystem.html">Ecosystem map</a> is the concrete
|
|
476
|
+
version: the six repositories and packages behind a run, each with its URL, and which way the
|
|
477
|
+
dependencies point.
|
|
478
|
+
</p>
|
|
479
|
+
|
|
474
480
|
<div class="btn-row">
|
|
475
|
-
<a class="btn btn-primary" href="
|
|
481
|
+
<a class="btn btn-primary" href="ecosystem.html">See the repo map →</a>
|
|
482
|
+
<a class="btn btn-ghost" href="how-it-works.html#steps">Do the walkthrough</a>
|
|
476
483
|
<a class="btn btn-ghost" href="reviewers.html">Read an evidence bundle</a>
|
|
477
484
|
</div>
|
|
478
485
|
</section>
|
|
@@ -485,6 +492,6 @@
|
|
|
485
492
|
</div>
|
|
486
493
|
</footer>
|
|
487
494
|
|
|
488
|
-
<script type="module" src="assets/progress.mjs"></script>
|
|
495
|
+
<script type="module" src="assets/progress.mjs?v=4"></script>
|
|
489
496
|
</body>
|
|
490
497
|
</html>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<svg width="35" height="34" viewBox="0 0 35 34" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="M32.7077 32.7522L25.1688 30.5174L19.4833 33.9008L15.5167 33.8991L9.82793 30.5174L2.29225 32.7522L0 25.0489L2.29225 16.4993L0 9.27094L2.29225 0.312256L14.0674 7.31554H20.9326L32.7077 0.312256L35 9.27094L32.7077 16.4993L35 25.0489L32.7077 32.7522Z" fill="#FF5C16"/>
|
|
3
|
+
<path d="M2.29395 0.312256L14.0691 7.32047L13.6008 12.1301L2.29395 0.312256Z" fill="#FF5C16"/>
|
|
4
|
+
<path d="M9.82959 25.0522L15.0106 28.9811L9.82959 30.5175V25.0522Z" fill="#FF5C16"/>
|
|
5
|
+
<path d="M14.5966 18.5565L13.6009 12.1333L7.22692 16.5009L7.22363 16.4993V16.5025L7.24335 20.9983L9.82809 18.5565H9.82974H14.5966Z" fill="#FF5C16"/>
|
|
6
|
+
<path d="M32.7077 0.312256L20.9326 7.32047L21.3993 12.1301L32.7077 0.312256Z" fill="#FF5C16"/>
|
|
7
|
+
<path d="M25.1722 25.0522L19.9912 28.9811L25.1722 30.5175V25.0522Z" fill="#FF5C16"/>
|
|
8
|
+
<path d="M27.7766 16.5025H27.7783H27.7766V16.4993L27.775 16.5009L21.401 12.1333L20.4053 18.5565H25.1722L27.7586 20.9983L27.7766 16.5025Z" fill="#FF5C16"/>
|
|
9
|
+
<path d="M9.82793 30.5175L2.29225 32.7522L0 25.0522H9.82793V30.5175Z" fill="#E34807"/>
|
|
10
|
+
<path d="M14.5947 18.5549L16.0341 27.8406L14.0393 22.6777L7.23975 20.9984L9.82613 18.5549H14.593H14.5947Z" fill="#E34807"/>
|
|
11
|
+
<path d="M25.1721 30.5175L32.7078 32.7522L35.0001 25.0522H25.1721V30.5175Z" fill="#E34807"/>
|
|
12
|
+
<path d="M20.4053 18.5549L18.9658 27.8406L20.9607 22.6777L27.7602 20.9984L25.1722 18.5549H20.4053Z" fill="#E34807"/>
|
|
13
|
+
<path d="M0 25.0488L2.29225 16.4993H7.22183L7.23991 20.9967L14.0394 22.676L16.0343 27.8389L15.0089 28.976L9.82793 25.0472H0V25.0488Z" fill="#FF8D5D"/>
|
|
14
|
+
<path d="M35.0001 25.0488L32.7078 16.4993H27.7783L27.7602 20.9967L20.9607 22.676L18.9658 27.8389L19.9912 28.976L25.1722 25.0472H35.0001V25.0488Z" fill="#FF8D5D"/>
|
|
15
|
+
<path d="M20.9325 7.31543H17.4999H14.0673L13.6006 12.1251L16.0342 27.834H18.9656L21.4008 12.1251L20.9325 7.31543Z" fill="#FF8D5D"/>
|
|
16
|
+
<path d="M2.29225 0.312256L0 9.27094L2.29225 16.4993H7.22183L13.5991 12.1301L2.29225 0.312256Z" fill="#661800"/>
|
|
17
|
+
<path d="M13.17 20.4199H10.9369L9.72095 21.6062L14.0409 22.6727L13.17 20.4182V20.4199Z" fill="#661800"/>
|
|
18
|
+
<path d="M32.7077 0.312256L34.9999 9.27094L32.7077 16.4993H27.7781L21.4009 12.1301L32.7077 0.312256Z" fill="#661800"/>
|
|
19
|
+
<path d="M21.833 20.4199H24.0694L25.2853 21.6079L20.9604 22.676L21.833 20.4182V20.4199Z" fill="#661800"/>
|
|
20
|
+
<path d="M19.4817 30.8362L19.9911 28.9794L18.9658 27.8423H16.0327L15.0073 28.9794L15.5167 30.8362" fill="#661800"/>
|
|
21
|
+
<path d="M19.4816 30.8359V33.9021H15.5166V30.8359H19.4816Z" fill="#C0C4CD"/>
|
|
22
|
+
<path d="M9.82959 30.5142L15.52 33.9008V30.8346L15.0106 28.9778L9.82959 30.5142Z" fill="#E7EBF6"/>
|
|
23
|
+
<path d="M25.1721 30.5142L19.4817 33.9008V30.8346L19.9911 28.9778L25.1721 30.5142Z" fill="#E7EBF6"/>
|
|
24
|
+
</svg>
|
package/site/assets/progress.mjs
CHANGED
|
@@ -241,11 +241,17 @@
|
|
|
241
241
|
* One tablist per group, roving tabindex: only the selected tab is in the
|
|
242
242
|
* tab order, arrows move between them. Panels stay in the DOM so their
|
|
243
243
|
* commands remain copyable the moment a tab is shown.
|
|
244
|
+
*
|
|
245
|
+
* The markup ships every panel visible and the tablist hidden; hiding is
|
|
246
|
+
* this function's first act. Without it — stale cache, blocked module,
|
|
247
|
+
* file:// — the reader gets all three prompts stacked instead of controls
|
|
248
|
+
* that do nothing.
|
|
244
249
|
*/
|
|
245
250
|
function initTabs() {
|
|
246
251
|
[].slice.call(document.querySelectorAll('[role="tablist"]')).forEach(function (list) {
|
|
247
252
|
var tabs = [].slice.call(list.querySelectorAll('[role="tab"]'));
|
|
248
253
|
if (!tabs.length) return;
|
|
254
|
+
list.hidden = false;
|
|
249
255
|
|
|
250
256
|
function select(tab, focus) {
|
|
251
257
|
tabs.forEach(function (other) {
|