@deeeed/metamask-harness 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/README.md +47 -17
- package/{orchestration → adapters}/core/cleanup.sh +2 -2
- package/{orchestration → adapters}/core/inject.sh +8 -8
- package/{orchestration → adapters}/extension/cleanup.mjs +1 -1
- package/{orchestration → adapters}/extension/console-tail.mjs +3 -0
- package/{orchestration → adapters}/extension/ensure-browser.sh +4 -4
- package/{orchestration → adapters}/extension/inject.mjs +27 -31
- package/{orchestration → adapters}/extension/launch.sh +4 -4
- package/{orchestration → adapters}/extension/live.sh +5 -5
- package/{orchestration → adapters}/extension/readiness.mjs +12 -2
- package/{orchestration → adapters}/extension/refresh-build.sh +1 -1
- package/{orchestration → adapters}/extension/seed-fixture.sh +1 -1
- package/{orchestration → adapters}/extension/start-watch.sh +7 -7
- package/{runner → adapters}/extension/verify.sh +11 -11
- package/{orchestration → adapters}/extension/wallet-fixture-state.cjs +2 -2
- package/{orchestration → adapters}/manifest.json +96 -96
- package/{library/actions → adapters}/mobile/bridge-runtime/cdp-bridge.cjs +5 -5
- package/{orchestration → adapters}/mobile/cleanup.sh +3 -3
- package/{orchestration → adapters}/mobile/inject.sh +22 -24
- package/adapters/mobile/open-device.sh +260 -0
- package/{orchestration/compat-overlays → adapters/mobile/overlay/compat}/README.md +13 -4
- package/adapters/mobile/prewarm-bundle.sh +95 -0
- package/adapters/mobile/start-metro.sh +228 -0
- package/{runner → adapters}/mobile/verify.sh +6 -6
- package/adapters/mobile/wait-for-bridge.sh +108 -0
- package/adapters/mobile/yarn-setup.sh +20 -0
- package/{orchestration/lib → adapters/shared}/cli-ux.sh +5 -9
- package/{orchestration/lib → adapters/shared}/log-tui.mjs +57 -1
- package/{orchestration/lib → adapters/shared}/open-debug.mjs +32 -10
- package/{orchestration/lib → adapters/shared}/resolve-farmslot-ports.sh +47 -0
- package/adapters/shared/sync-wallet-fixture.sh +78 -0
- package/bin/mm-harness +37 -6
- package/docs/CLI-SPEC.md +41 -2
- package/docs/architecture.md +69 -38
- package/docs/runtime-file-conventions.md +3 -3
- package/library/actions/core/perps/_controller.mjs +1 -1
- package/library/actions/extension/platform/cdp.mjs +2 -2
- package/library/actions/extension/wallet/ensure_unlocked.mjs +1 -1
- package/library/actions/mobile/platform/bridge.mjs +1 -1
- package/library/actions/mobile/wallet/ensure_unlocked.mjs +1 -1
- package/library/actions/mobile/wallet/setup.mjs +2 -2
- package/library/library.json +1 -1
- package/package.json +8 -8
- package/{orchestration → src/adapters}/extension/runtime-decision.ts +5 -5
- package/{orchestration → src/adapters}/extension/runtime.ts +1 -1
- package/src/adapters/mobile/prepare.ts +210 -0
- package/{orchestration → src/adapters}/mobile/runtime-decision.ts +105 -5
- package/{runner/src → src}/adapters.ts +3 -3
- package/{orchestration/lib/cli-color.mjs → src/cli-color.ts} +21 -13
- package/{orchestration/lib/cli-commands.mjs → src/cli-commands.ts} +52 -45
- package/{orchestration/lib/cli-version.mjs → src/cli-version.ts} +35 -17
- package/{runner/src → src}/cli.ts +48 -40
- package/src/commands/debug.ts +63 -0
- package/src/commands/fixtures.ts +193 -0
- package/{runner/src → src}/commands/launch.ts +200 -54
- package/src/commands/logs.ts +83 -0
- package/{runner/src → src}/commands/shared.ts +40 -30
- package/src/commands/update.ts +316 -0
- package/{runner/src → src}/harness.ts +119 -16
- package/{runner/src → src}/heal-bounds.ts +20 -1
- package/src/index.ts +15 -0
- package/{runner/src → src}/live-adapter-contract.ts +1 -1
- package/{runner/src → src}/mm-harness-cli.ts +64 -3
- package/{runner/src → src}/paths.ts +2 -2
- package/{orchestration/lib/progress.mjs → src/progress.ts} +31 -21
- package/docs/extension-runtime-commands.md +0 -60
- package/docs/harness-cli.md +0 -43
- package/orchestration/compat-overlays/mobile/README.md +0 -13
- package/orchestration/doctor.mjs +0 -72
- package/orchestration/lib/cli-home.mjs +0 -354
- package/orchestration/lib/recipe-paths.mjs +0 -26
- package/orchestration/mobile/launch.sh +0 -137
- package/orchestration/mobile/live.sh +0 -125
- package/orchestration/porcelain/metamask-recipe +0 -99
- package/orchestration/porcelain/mm-recipe +0 -1591
- package/orchestration/porcelain/mme-recipe +0 -1181
- package/runner/src/commands/debug.ts +0 -44
- package/runner/src/commands/fixtures.ts +0 -99
- package/runner/src/commands/logs.ts +0 -60
- package/runner/src/index.ts +0 -6
- /package/{orchestration → adapters}/extension/launch-browser.cjs +0 -0
- /package/{orchestration → adapters}/extension/pin-remote-flags.cjs +0 -0
- /package/{orchestration → adapters}/extension/sidepanel-toggle.sh +0 -0
- /package/{orchestration → adapters}/extension/snapshot-dist.sh +0 -0
- /package/{library/actions → adapters}/mobile/bridge-runtime/lib/cdp-eval.cjs +0 -0
- /package/{library/actions → adapters}/mobile/bridge-runtime/lib/config.cjs +0 -0
- /package/{library/actions → adapters}/mobile/bridge-runtime/lib/issue-capture.cjs +0 -0
- /package/{library/actions → adapters}/mobile/bridge-runtime/lib/target-discovery.cjs +0 -0
- /package/{library/actions → adapters}/mobile/bridge-runtime/lib/ws-client.cjs +0 -0
- /package/{library/actions → adapters}/mobile/bridge-runtime/setup-wallet.sh +0 -0
- /package/{library/actions/mobile/app-overlay → adapters/mobile/overlay}/app/dev-tools/AgenticService/AgentStepHud.tsx.patch +0 -0
- /package/{library/actions/mobile/app-overlay → adapters/mobile/overlay}/app/dev-tools/AgenticService/AgenticService.ts.patch +0 -0
- /package/{orchestration/compat-overlays/mobile → adapters/mobile/overlay/compat}/rn81-message-event-source.patch +0 -0
- /package/{orchestration/lib → adapters/shared}/activate-repo-node.sh +0 -0
- /package/{orchestration/lib → adapters/shared}/ensure-runner-deps.sh +0 -0
- /package/{orchestration/lib → adapters/shared}/harness-path.sh +0 -0
- /package/{orchestration/lib → adapters/shared}/hash-helpers.sh +0 -0
- /package/{orchestration/lib → adapters/shared}/json-field.sh +0 -0
- /package/{orchestration/lib → adapters/shared}/path-defaults.json +0 -0
- /package/{orchestration → src/adapters}/extension/ensure-ready.ts +0 -0
- /package/{orchestration → src/adapters}/extension/extension-id.ts +0 -0
- /package/{orchestration → src/adapters}/mobile/deps-markers.ts +0 -0
- /package/{runner/src → src}/completions-cache.ts +0 -0
- /package/{runner/src → src}/doctor.ts +0 -0
- /package/{runner/src → src}/manifest.ts +0 -0
- /package/{runner/src → src}/recording-target.ts +0 -0
- /package/{runner/src → src}/run-recording.ts +0 -0
- /package/{runner/src → src}/runner.ts +0 -0
- /package/{runner/src → src}/types.ts +0 -0
package/docs/harness-cli.md
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
# `mm-harness` — Command Reference
|
|
2
|
-
|
|
3
|
-
> This page is a pointer. The full, authoritative command contract lives in
|
|
4
|
-
> [CLI-SPEC.md](CLI-SPEC.md); the before→after overview is in
|
|
5
|
-
> [MENTAL-MODEL.md](MENTAL-MODEL.md). This file exists only so links to
|
|
6
|
-
> `harness-cli.md` keep resolving.
|
|
7
|
-
|
|
8
|
-
`mm-harness` is the MetaMask recipe harness — the single front door for launching
|
|
9
|
-
the app, proving behavior, and managing the per-checkout **runtime overlay**. It
|
|
10
|
-
is the only bin: platform (`mobile | extension | core`) is auto-detected from the
|
|
11
|
-
checkout, a positional target forces it (`mm-harness launch ios`), and depth lives
|
|
12
|
-
in flags (`--json`, ports, build tiers), never in extra command names.
|
|
13
|
-
|
|
14
|
-
Naming: **"harness" is the tool** (`mm-harness`) and the generic engine package
|
|
15
|
-
(`@farmslot/recipe-harness`). The files the tool installs into a target checkout
|
|
16
|
-
are the **runtime overlay** — never "the harness".
|
|
17
|
-
|
|
18
|
-
## Command surface (all shipped)
|
|
19
|
-
|
|
20
|
-
```
|
|
21
|
-
DAILY LOOP launch · logs · debug · fixtures what a teammate runs many times a day
|
|
22
|
-
DISCOVER actions · call · flows compose recipes from the vocabulary + flow library
|
|
23
|
-
PROVE run · doctor run recipes, check readiness
|
|
24
|
-
RUNTIME OVERLAY install · verify · cleanup install/verify/clean the per-checkout overlay
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
Grammar: `mm-harness <command> [target] [flags]`. `--json` is the agent contract on
|
|
28
|
-
every command; the bare command is the human happy path.
|
|
29
|
-
|
|
30
|
-
- `run` validates first, then executes; `run --plan` validates and prints the plan
|
|
31
|
-
without touching a device.
|
|
32
|
-
- `call <action>` runs one action from the adapter vocabulary through the same
|
|
33
|
-
engine path `run` uses.
|
|
34
|
-
- `doctor` is read-only; `doctor --fix` runs the self-healing steps (overlay
|
|
35
|
-
auto-ensure; never a fixture reseed) without launching the app and reports
|
|
36
|
-
`fixed[]` / `failed[]`.
|
|
37
|
-
- `flows` lists the recipe library (bare = `flows list`); `flows promote` keeps a
|
|
38
|
-
proven flow.
|
|
39
|
-
- `install` / `verify` / `cleanup` manage the runtime overlay; the DAILY LOOP
|
|
40
|
-
commands auto-ensure it.
|
|
41
|
-
|
|
42
|
-
For flags, exit-code taxonomy, `--json` shapes, and per-command detail, see
|
|
43
|
-
[CLI-SPEC.md](CLI-SPEC.md).
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
# Mobile historical compatibility overlays
|
|
2
|
-
|
|
3
|
-
These patch files document temporary source overlays used by Recipe v1 historical
|
|
4
|
-
injection evals when an older MetaMask Mobile checkout cannot boot under the
|
|
5
|
-
current local Xcode/Hermes toolchain.
|
|
6
|
-
|
|
7
|
-
They are intentionally kept in the external runner repository, not Farmslot root
|
|
8
|
-
scripts, so a `/recipe-harness` skill can apply the same reversible overlay before
|
|
9
|
-
a historical rebuild and record the overlay path in validation evidence.
|
|
10
|
-
|
|
11
|
-
- `rn81-message-event-source.patch`: adds a read-only `MessageEvent.prototype.source`
|
|
12
|
-
getter for React Native 0.81 historical checkouts whose bundled polyfill omits
|
|
13
|
-
it while CDP websocket clients expect browser-compatible `MessageEvent` shape.
|
package/orchestration/doctor.mjs
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// doctor.mjs — orchestration feature-surface doctor
|
|
3
|
-
//
|
|
4
|
-
// Purpose:
|
|
5
|
-
// Verifies orchestration/manifest.json against reality: every entry point
|
|
6
|
-
// exists; every script entry answers --help with exit 0; and every script
|
|
7
|
-
// under orchestration/{mobile,extension,core} is listed (no doc drift).
|
|
8
|
-
//
|
|
9
|
-
// Inputs (flags): --json (machine-readable report)
|
|
10
|
-
// Outputs: per-feature lines (or JSON report) on stdout.
|
|
11
|
-
// Exit 0 — surface healthy; 1 — missing entry, failing --help, or
|
|
12
|
-
// unlisted script; 2 — bad args.
|
|
13
|
-
// Never touches: anything (read-only + --help subprocesses).
|
|
14
|
-
import fs from 'node:fs';
|
|
15
|
-
import path from 'node:path';
|
|
16
|
-
import { spawnSync } from 'node:child_process';
|
|
17
|
-
import { fileURLToPath } from 'node:url';
|
|
18
|
-
|
|
19
|
-
let json = false;
|
|
20
|
-
for (const arg of process.argv.slice(2)) {
|
|
21
|
-
if (arg === '--json') json = true;
|
|
22
|
-
else if (arg === '-h' || arg === '--help') {
|
|
23
|
-
console.log('Usage: doctor.mjs [--json]');
|
|
24
|
-
process.exit(0);
|
|
25
|
-
} else {
|
|
26
|
-
console.error(`Unknown arg: ${arg}`);
|
|
27
|
-
process.exit(2);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const orchestrationDir = path.dirname(fileURLToPath(import.meta.url));
|
|
32
|
-
const runnerRoot = path.resolve(orchestrationDir, '..');
|
|
33
|
-
const manifest = JSON.parse(fs.readFileSync(path.join(orchestrationDir, 'manifest.json'), 'utf8'));
|
|
34
|
-
|
|
35
|
-
const results = [];
|
|
36
|
-
for (const feature of manifest.features) {
|
|
37
|
-
const entryAbs = path.join(runnerRoot, feature.entry);
|
|
38
|
-
const checks = { exists: fs.existsSync(entryAbs), help: null };
|
|
39
|
-
if (checks.exists && (feature.kind === 'bash' || feature.kind === 'node')) {
|
|
40
|
-
const runner = feature.kind === 'bash' ? 'bash' : process.execPath;
|
|
41
|
-
const run = spawnSync(runner, [entryAbs, '--help'], { encoding: 'utf8', timeout: 30000 });
|
|
42
|
-
checks.help = run.status === 0;
|
|
43
|
-
}
|
|
44
|
-
const ok = checks.exists && (checks.help === null || checks.help === true);
|
|
45
|
-
results.push({ id: feature.id, entry: feature.entry, kind: feature.kind, ...checks, ok });
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// drift guard: every script in the orchestration trees must be listed
|
|
49
|
-
const listed = new Set(manifest.features.map((feature) => feature.entry));
|
|
50
|
-
const unlisted = [];
|
|
51
|
-
for (const platform of ['mobile', 'extension', 'core', 'lib']) {
|
|
52
|
-
const dir = path.join(orchestrationDir, platform);
|
|
53
|
-
if (!fs.existsSync(dir)) continue;
|
|
54
|
-
for (const entry of fs.readdirSync(dir)) {
|
|
55
|
-
const rel = `orchestration/${platform}/${entry}`;
|
|
56
|
-
if (!/\.(?:sh|mjs|cjs|json)$/u.test(entry)) continue;
|
|
57
|
-
if (!listed.has(rel)) unlisted.push(rel);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const failed = results.filter((result) => !result.ok);
|
|
62
|
-
const status = failed.length === 0 && unlisted.length === 0 ? 'pass' : 'fail';
|
|
63
|
-
if (json) {
|
|
64
|
-
console.log(JSON.stringify({ status, features: results, unlisted }, null, 2));
|
|
65
|
-
} else {
|
|
66
|
-
for (const result of results) {
|
|
67
|
-
console.log(`${result.ok ? 'ok ' : 'FAIL'} ${result.id} (${result.entry})${result.help === false ? ' [--help failed]' : ''}${result.exists ? '' : ' [missing]'}`);
|
|
68
|
-
}
|
|
69
|
-
for (const rel of unlisted) console.log(`FAIL unlisted script not in manifest: ${rel}`);
|
|
70
|
-
console.log(`orchestration surface: ${status} (${results.length - failed.length}/${results.length} features, ${unlisted.length} unlisted)`);
|
|
71
|
-
}
|
|
72
|
-
process.exit(status === 'pass' ? 0 : 1);
|
|
@@ -1,354 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* cli-home.mjs — recipe CLI home screen and ports output.
|
|
4
|
-
*/
|
|
5
|
-
import fs from 'node:fs';
|
|
6
|
-
import http from 'node:http';
|
|
7
|
-
import path from 'node:path';
|
|
8
|
-
import { parseArgs } from 'node:util';
|
|
9
|
-
import {
|
|
10
|
-
color,
|
|
11
|
-
colorKv,
|
|
12
|
-
colorStatusWord,
|
|
13
|
-
} from './cli-color.mjs';
|
|
14
|
-
|
|
15
|
-
const OUT = process.stdout;
|
|
16
|
-
|
|
17
|
-
function readJson(file) {
|
|
18
|
-
if (!file || !fs.existsSync(file)) return null;
|
|
19
|
-
try {
|
|
20
|
-
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
21
|
-
} catch {
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function httpProbe(url, timeoutMs = 1500) {
|
|
27
|
-
return new Promise((resolve) => {
|
|
28
|
-
const req = http.get(url, { timeout: timeoutMs }, (res) => {
|
|
29
|
-
let body = '';
|
|
30
|
-
res.on('data', (chunk) => { body += chunk; });
|
|
31
|
-
res.on('end', () => resolve({ ok: res.statusCode >= 200 && res.statusCode < 300, body }));
|
|
32
|
-
});
|
|
33
|
-
req.on('timeout', () => { req.destroy(); resolve({ ok: false, body: '' }); });
|
|
34
|
-
req.on('error', () => resolve({ ok: false, body: '' }));
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function watcherPhase(logPath) {
|
|
39
|
-
if (!logPath || !fs.existsSync(logPath)) return 'stopped';
|
|
40
|
-
const text = fs.readFileSync(logPath, 'utf8');
|
|
41
|
-
if (/Module build failed|^ERROR in /m.test(text)) return 'failed';
|
|
42
|
-
if (/compiled successfully|MetaMask .* compiled|Bundle end:/m.test(text)) return 'ready';
|
|
43
|
-
if (/webpack.Progress|Building|Compiling|yarn start/m.test(text)) return 'compiling';
|
|
44
|
-
return 'unknown';
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function metroPhase(logPath) {
|
|
48
|
-
if (!logPath || !fs.existsSync(logPath)) return 'stopped';
|
|
49
|
-
const text = fs.readFileSync(logPath, 'utf8');
|
|
50
|
-
if (/Bundl(ed|ing)|Finished/m.test(text)) {
|
|
51
|
-
const match = text.match(/(iOS|Android).*?([0-9]+(?:\.[0-9]+)?)%/);
|
|
52
|
-
if (match && Number(match[2]) < 100) return 'bundling';
|
|
53
|
-
return 'ready';
|
|
54
|
-
}
|
|
55
|
-
return 'starting';
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function mobileBridgeSnapshot(runtimeStatus) {
|
|
59
|
-
const raw = runtimeStatus?.bridge?.status;
|
|
60
|
-
const targets = Array.isArray(raw) ? raw : raw ? [raw] : [];
|
|
61
|
-
const ready = targets.find((item) => item && typeof item === 'object' && item.route);
|
|
62
|
-
if (!ready) return { route: null, account: null, deviceName: null };
|
|
63
|
-
return {
|
|
64
|
-
route: ready.route?.name || null,
|
|
65
|
-
account: ready.account?.name || ready.account?.address || null,
|
|
66
|
-
deviceName: ready.deviceName || null,
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function watcherPhaseLabel(phase) {
|
|
71
|
-
switch (phase) {
|
|
72
|
-
case 'ready': return 'ready';
|
|
73
|
-
case 'compiling': return 'compiling';
|
|
74
|
-
case 'failed': return 'failed';
|
|
75
|
-
case 'stopped': return 'stopped';
|
|
76
|
-
default: return phase || 'unknown';
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function metroPhaseLabel(phase) {
|
|
81
|
-
switch (phase) {
|
|
82
|
-
case 'running':
|
|
83
|
-
case 'ready': return 'ready';
|
|
84
|
-
case 'bundling': return 'bundling';
|
|
85
|
-
case 'starting': return 'starting';
|
|
86
|
-
case 'down': return 'down';
|
|
87
|
-
default: return phase || 'unknown';
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function nextActions(adapter, state) {
|
|
92
|
-
const actions = [];
|
|
93
|
-
if (state.harnessStale) {
|
|
94
|
-
actions.push({ command: 'recipe sync', reason: 'refresh harness scripts + fixture context' });
|
|
95
|
-
}
|
|
96
|
-
if (adapter === 'extension') {
|
|
97
|
-
if (state.ready) {
|
|
98
|
-
actions.push({ command: 'recipe refresh', reason: 'fast incremental update when watcher is active' });
|
|
99
|
-
actions.push({ command: 'recipe debug', reason: 'open Chrome DevTools' });
|
|
100
|
-
actions.push({ command: 'recipe logs', reason: 'compact last build events (recipe logs --full for raw tail)' });
|
|
101
|
-
} else if (state.watcher === 'ready' && state.decision === 'build') {
|
|
102
|
-
actions.push({ command: 'recipe refresh', reason: 'watcher is up; wait for dist to catch up' });
|
|
103
|
-
actions.push({ command: 'recipe rebuild', reason: 'heavy clean rebuild if refresh stalls' });
|
|
104
|
-
} else if (state.watcher === 'compiling') {
|
|
105
|
-
actions.push({ command: 'recipe logs', reason: 'compact webpack progress' });
|
|
106
|
-
actions.push({ command: 'recipe status', reason: 're-check readiness' });
|
|
107
|
-
} else {
|
|
108
|
-
actions.push({ command: 'recipe up', reason: 'decide and run the minimum path to ready' });
|
|
109
|
-
actions.push({ command: 'recipe rebuild', reason: 'clean webpack + browser launch (2-8 min)' });
|
|
110
|
-
actions.push({ command: 'recipe watch', reason: 'start watcher only' });
|
|
111
|
-
}
|
|
112
|
-
if (!state.fixturePresent) actions.unshift({ command: 'recipe sync', reason: 'install fixture + harness context' });
|
|
113
|
-
} else {
|
|
114
|
-
if (state.ready) {
|
|
115
|
-
actions.push({ command: 'recipe debug', reason: 'open React Native DevTools' });
|
|
116
|
-
actions.push({ command: 'recipe logs', reason: 'compact Metro events (recipe logs --full for raw tail)' });
|
|
117
|
-
} else if (state.metro === 'ready' && !state.bridgeReady) {
|
|
118
|
-
actions.push({ command: 'recipe up', reason: 'launch dev client and wait for bridge' });
|
|
119
|
-
actions.push({ command: 'recipe logs', reason: 'compact bundle progress' });
|
|
120
|
-
} else {
|
|
121
|
-
actions.push({ command: 'recipe up', reason: 'start Metro + launch dev client as needed' });
|
|
122
|
-
actions.push({ command: 'recipe ios', reason: 'launch iOS dev client' });
|
|
123
|
-
actions.push({ command: 'recipe android', reason: 'launch Android dev client' });
|
|
124
|
-
}
|
|
125
|
-
if (!state.fixturePresent) actions.unshift({ command: 'recipe sync', reason: 'install fixture + harness context' });
|
|
126
|
-
}
|
|
127
|
-
return actions.slice(0, 5);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function renderHuman(payload) {
|
|
131
|
-
const lines = [];
|
|
132
|
-
const head = payload.adapter === 'extension' ? 'Extension recipe' : 'Mobile recipe';
|
|
133
|
-
lines.push(`${color('bold', head, { stream: OUT })} ${color('dim', '|', { stream: OUT })} ${color('path', payload.root, { stream: OUT })}`);
|
|
134
|
-
lines.push(`${colorKv('slot', payload.slotId || '?', 'info', { stream: OUT })} ${colorKv('runtime', payload.runtimeDir, 'path', { stream: OUT })}`);
|
|
135
|
-
if (payload.harnessStale) {
|
|
136
|
-
lines.push(color('warn', 'harness: stale - run: recipe sync', { stream: OUT }));
|
|
137
|
-
}
|
|
138
|
-
if (payload.adapter === 'extension') {
|
|
139
|
-
lines.push(
|
|
140
|
-
`${colorKv('cdp', String(payload.ports.cdp ?? '?'), 'info', { stream: OUT })} ${colorKv('watcher-port', String(payload.ports.watcher ?? '?'), 'info', { stream: OUT })}`,
|
|
141
|
-
);
|
|
142
|
-
lines.push([
|
|
143
|
-
`${color('label', 'browser:', { stream: OUT })} ${colorStatusWord(payload.browser, { stream: OUT })}`,
|
|
144
|
-
`${color('label', 'webpack:', { stream: OUT })} ${colorStatusWord(watcherPhaseLabel(payload.watcher), { stream: OUT })}`,
|
|
145
|
-
`${color('label', 'ready:', { stream: OUT })} ${colorStatusWord(payload.ready ? 'yes' : 'no', { stream: OUT })}`,
|
|
146
|
-
payload.reason ? color('comment', ` (${payload.reason})`, { stream: OUT }) : '',
|
|
147
|
-
].join(' '));
|
|
148
|
-
lines.push(`${color('label', 'fixture:', { stream: OUT })} ${colorStatusWord(payload.fixturePresent ? 'present' : 'missing', { stream: OUT })}`);
|
|
149
|
-
} else {
|
|
150
|
-
const bridgeBits = [];
|
|
151
|
-
if (payload.appRoute) bridgeBits.push(`route=${payload.appRoute}`);
|
|
152
|
-
if (payload.appAccount) bridgeBits.push(`account=${payload.appAccount}`);
|
|
153
|
-
const bridgeDetail = bridgeBits.length > 0 ? color('comment', ` (${bridgeBits.join(', ')})`, { stream: OUT }) : '';
|
|
154
|
-
lines.push(
|
|
155
|
-
`${colorKv('metro-port', String(payload.ports.watcher ?? '?'), 'info', { stream: OUT })} ${colorKv('device', payload.device || payload.appDevice || '?', 'info', { stream: OUT })} ${color('label', 'metro:', { stream: OUT })} ${colorStatusWord(metroPhaseLabel(payload.metro), { stream: OUT })} ${color('label', 'bridge:', { stream: OUT })} ${colorStatusWord(payload.bridge, { stream: OUT })}${bridgeDetail}`,
|
|
156
|
-
);
|
|
157
|
-
lines.push([
|
|
158
|
-
`${color('label', 'fixture:', { stream: OUT })} ${colorStatusWord(payload.fixturePresent ? 'present' : 'missing', { stream: OUT })}`,
|
|
159
|
-
`${color('label', 'ready:', { stream: OUT })} ${colorStatusWord(payload.ready ? 'yes' : 'no', { stream: OUT })}`,
|
|
160
|
-
payload.reason ? color('comment', ` (${payload.reason})`, { stream: OUT }) : '',
|
|
161
|
-
].join(' '));
|
|
162
|
-
}
|
|
163
|
-
if (payload.decision) {
|
|
164
|
-
lines.push(`${colorKv('decision', payload.decision, 'ok', { stream: OUT })}${payload.reasonCode ? color('comment', ` - ${payload.reasonCode}`, { stream: OUT }) : ''}`);
|
|
165
|
-
}
|
|
166
|
-
if (payload.tmux) {
|
|
167
|
-
lines.push(`${colorKv('tmux', payload.tmux, 'accent', { stream: OUT })}${color('comment', ' (webpack window may still stream full logs)', { stream: OUT })}`);
|
|
168
|
-
}
|
|
169
|
-
lines.push('');
|
|
170
|
-
lines.push(color('label', 'next:', { stream: OUT }));
|
|
171
|
-
for (const item of payload.nextActions) {
|
|
172
|
-
lines.push(` ${color('cmd', item.command, { stream: OUT })} ${color('comment', `# ${item.reason}`, { stream: OUT })}`);
|
|
173
|
-
}
|
|
174
|
-
lines.push('');
|
|
175
|
-
lines.push(color('comment', 'speed: fast -> watch -> refresh slow -> rebuild inspect -> status | logs | logs --full | debug', { stream: OUT }));
|
|
176
|
-
return `${lines.join('\n')}\n`;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
function renderPorts(payload) {
|
|
180
|
-
const lines = [
|
|
181
|
-
colorKv('slot', payload.slotId || '?', 'info', { stream: OUT }),
|
|
182
|
-
`${colorKv('cdp', String(payload.ports.cdp ?? '?'), 'info', { stream: OUT })} ${colorKv('watcher', String(payload.ports.watcher ?? '?'), 'info', { stream: OUT })}`,
|
|
183
|
-
`${colorKv('runtime', payload.runtimeDir, 'path', { stream: OUT })} ${colorKv('harness', payload.harnessDir, 'path', { stream: OUT })}`,
|
|
184
|
-
];
|
|
185
|
-
if (payload.tmux) lines.push(colorKv('tmux', payload.tmux, 'accent', { stream: OUT }));
|
|
186
|
-
return `${lines.join('\n')}\n`;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
async function buildHome({ adapter, target, runtimeDir, ports, runtimeStatus, decision, harnessStale = false }) {
|
|
190
|
-
const runtimeAbs = path.join(target, runtimeDir);
|
|
191
|
-
const context = readJson(path.join(runtimeAbs, 'agentic-runtime.json'))
|
|
192
|
-
|| readJson(path.join(runtimeAbs, 'recipe-runtime.json'))
|
|
193
|
-
|| {};
|
|
194
|
-
const slotId = context.slotId || null;
|
|
195
|
-
const fixturePresent = Boolean(
|
|
196
|
-
runtimeStatus?.fixture?.present
|
|
197
|
-
|| fs.existsSync(path.join(runtimeAbs, 'wallet-fixture.json')),
|
|
198
|
-
);
|
|
199
|
-
|
|
200
|
-
let browser = 'down';
|
|
201
|
-
let watcher = 'stopped';
|
|
202
|
-
let metro = 'stopped';
|
|
203
|
-
let bridge = 'down';
|
|
204
|
-
let bridgeReady = false;
|
|
205
|
-
let device = process.env.IOS_SIMULATOR || process.env.ANDROID_DEVICE || process.env.ADB_SERIAL || null;
|
|
206
|
-
let tmux = null;
|
|
207
|
-
|
|
208
|
-
if (adapter === 'extension') {
|
|
209
|
-
const cdp = ports.cdp;
|
|
210
|
-
if (cdp) {
|
|
211
|
-
const probe = await httpProbe(`http://127.0.0.1:${cdp}/json/version`);
|
|
212
|
-
browser = probe.ok ? 'up' : 'down';
|
|
213
|
-
}
|
|
214
|
-
const logPath = path.join(runtimeAbs, 'webpack.log');
|
|
215
|
-
watcher = watcherPhase(logPath);
|
|
216
|
-
const slot = (path.basename(target).match(/-(\d+)$/) || [])[1];
|
|
217
|
-
if (slot) tmux = `mme-${slot}:webpack-${ports.watcher || '?'}`;
|
|
218
|
-
} else {
|
|
219
|
-
const port = ports.watcher;
|
|
220
|
-
if (port) {
|
|
221
|
-
const status = await httpProbe(`http://127.0.0.1:${port}/status`);
|
|
222
|
-
metro = status.ok && status.body.includes('packager-status:running') ? 'running' : 'down';
|
|
223
|
-
if (metro === 'running') metro = metroPhase(path.join(runtimeAbs, 'metro.log'));
|
|
224
|
-
}
|
|
225
|
-
bridgeReady = Boolean(runtimeStatus?.bridge?.ready);
|
|
226
|
-
bridge = bridgeReady ? 'ready' : (runtimeStatus?.debugTarget?.present ? 'waiting' : 'down');
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
const ready = Boolean(runtimeStatus?.ready);
|
|
230
|
-
const decisionValue = decision?.decision || runtimeStatus?.decision?.decision || null;
|
|
231
|
-
const reasonCode = decision?.reasonCode || runtimeStatus?.decision?.reasonCode || runtimeStatus?.reason || null;
|
|
232
|
-
const bridgeSnap = adapter === 'mobile' ? mobileBridgeSnapshot(runtimeStatus) : null;
|
|
233
|
-
|
|
234
|
-
const payload = {
|
|
235
|
-
schemaVersion: 1,
|
|
236
|
-
kind: 'home',
|
|
237
|
-
adapter,
|
|
238
|
-
root: target,
|
|
239
|
-
runtimeDir,
|
|
240
|
-
slotId,
|
|
241
|
-
ports,
|
|
242
|
-
fixturePresent,
|
|
243
|
-
harnessStale,
|
|
244
|
-
ready,
|
|
245
|
-
reason: runtimeStatus?.reason || null,
|
|
246
|
-
appRoute: bridgeSnap?.route || null,
|
|
247
|
-
appAccount: bridgeSnap?.account || null,
|
|
248
|
-
appDevice: bridgeSnap?.deviceName || null,
|
|
249
|
-
decision: decisionValue,
|
|
250
|
-
reasonCode,
|
|
251
|
-
browser: adapter === 'extension' ? browser : undefined,
|
|
252
|
-
watcher: adapter === 'extension' ? watcher : undefined,
|
|
253
|
-
metro: adapter === 'mobile' ? metro : undefined,
|
|
254
|
-
bridge: adapter === 'mobile' ? bridge : undefined,
|
|
255
|
-
bridgeReady: adapter === 'mobile' ? bridgeReady : undefined,
|
|
256
|
-
device: adapter === 'mobile' ? device : undefined,
|
|
257
|
-
tmux: adapter === 'extension' ? tmux : undefined,
|
|
258
|
-
runtimeStatus,
|
|
259
|
-
nextActions: [],
|
|
260
|
-
};
|
|
261
|
-
payload.nextActions = nextActions(adapter, {
|
|
262
|
-
ready,
|
|
263
|
-
watcher,
|
|
264
|
-
metro,
|
|
265
|
-
bridgeReady,
|
|
266
|
-
fixturePresent,
|
|
267
|
-
harnessStale,
|
|
268
|
-
decision: decisionValue,
|
|
269
|
-
});
|
|
270
|
-
return payload;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
function buildPorts({ adapter, target, runtimeDir, ports, slotId }) {
|
|
274
|
-
return {
|
|
275
|
-
schemaVersion: 1,
|
|
276
|
-
kind: 'ports',
|
|
277
|
-
adapter,
|
|
278
|
-
root: target,
|
|
279
|
-
runtimeDir,
|
|
280
|
-
slotId: slotId || null,
|
|
281
|
-
ports,
|
|
282
|
-
tmux: adapter === 'extension' && ports.watcher
|
|
283
|
-
? `mme-${(path.basename(target).match(/-(\d+)$/) || ['', ''])[1]}:webpack-${ports.watcher}`
|
|
284
|
-
: null,
|
|
285
|
-
harnessDir: process.env.RECIPE_HARNESS_ROOT || 'temp/recipe/harness',
|
|
286
|
-
};
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
async function main() {
|
|
290
|
-
const { values } = parseArgs({
|
|
291
|
-
args: process.argv.slice(2),
|
|
292
|
-
options: {
|
|
293
|
-
adapter: { type: 'string' },
|
|
294
|
-
target: { type: 'string' },
|
|
295
|
-
'runtime-dir': { type: 'string' },
|
|
296
|
-
'cdp-port': { type: 'string' },
|
|
297
|
-
'watcher-port': { type: 'string' },
|
|
298
|
-
'slot-id': { type: 'string' },
|
|
299
|
-
mode: { type: 'string', default: 'home' },
|
|
300
|
-
'runtime-status-file': { type: 'string' },
|
|
301
|
-
'decision-file': { type: 'string' },
|
|
302
|
-
'harness-stale': { type: 'boolean', default: false },
|
|
303
|
-
json: { type: 'boolean', default: false },
|
|
304
|
-
},
|
|
305
|
-
allowPositionals: false,
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
if (!values.adapter || !values.target) {
|
|
309
|
-
console.error('cli-home.mjs requires --adapter and --target');
|
|
310
|
-
process.exit(2);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
const ports = {
|
|
314
|
-
cdp: values['cdp-port'] ? Number(values['cdp-port']) : null,
|
|
315
|
-
watcher: values['watcher-port'] ? Number(values['watcher-port']) : null,
|
|
316
|
-
};
|
|
317
|
-
const runtimeStatus = readJson(values['runtime-status-file']);
|
|
318
|
-
const decision = readJson(values['decision-file']);
|
|
319
|
-
|
|
320
|
-
const payload = values.mode === 'ports'
|
|
321
|
-
? buildPorts({
|
|
322
|
-
adapter: values.adapter,
|
|
323
|
-
target: values.target,
|
|
324
|
-
runtimeDir: values['runtime-dir'] || 'temp/recipe/runtime',
|
|
325
|
-
ports,
|
|
326
|
-
slotId: values['slot-id'],
|
|
327
|
-
})
|
|
328
|
-
: await buildHome({
|
|
329
|
-
adapter: values.adapter,
|
|
330
|
-
target: values.target,
|
|
331
|
-
runtimeDir: values['runtime-dir'] || 'temp/recipe/runtime',
|
|
332
|
-
ports,
|
|
333
|
-
runtimeStatus,
|
|
334
|
-
decision,
|
|
335
|
-
harnessStale: values['harness-stale'],
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
if (values.json) {
|
|
339
|
-
process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
340
|
-
return;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
if (payload.kind === 'ports') {
|
|
344
|
-
process.stdout.write(renderPorts(payload));
|
|
345
|
-
return;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
process.stdout.write(renderHuman(payload));
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
main().catch((error) => {
|
|
352
|
-
console.error(error instanceof Error ? error.message : String(error));
|
|
353
|
-
process.exit(1);
|
|
354
|
-
});
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { fileURLToPath } from 'node:url';
|
|
4
|
-
|
|
5
|
-
const defaultsPath = path.join(path.dirname(fileURLToPath(import.meta.url)), 'path-defaults.json');
|
|
6
|
-
const defaults = JSON.parse(fs.readFileSync(defaultsPath, 'utf8'));
|
|
7
|
-
|
|
8
|
-
export const DEFAULT_RECIPE_HARNESS_ROOT = defaults.recipeHarnessRoot;
|
|
9
|
-
export const DEFAULT_RECIPE_RUNTIME_DIR = defaults.recipeRuntimeDir;
|
|
10
|
-
|
|
11
|
-
export function validateRelativeRecipePath(name, value) {
|
|
12
|
-
if (!value || path.isAbsolute(value)) throw new Error(`${name} must be a non-empty relative path: ${value}`);
|
|
13
|
-
if (!/^[A-Za-z0-9._/-]+$/u.test(value)) throw new Error(`${name} contains unsupported characters: ${value}`);
|
|
14
|
-
for (const part of value.split('/')) {
|
|
15
|
-
if (!part || part === '.' || part === '..') throw new Error(`${name} contains unsafe path component: ${value}`);
|
|
16
|
-
}
|
|
17
|
-
return value;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function recipeHarnessRoot(env = process.env) {
|
|
21
|
-
return validateRelativeRecipePath('RECIPE_HARNESS_ROOT', env.RECIPE_HARNESS_ROOT || DEFAULT_RECIPE_HARNESS_ROOT);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function recipeRuntimeDir(env = process.env) {
|
|
25
|
-
return validateRelativeRecipePath('RECIPE_RUNTIME_DIR', env.RECIPE_RUNTIME_DIR || DEFAULT_RECIPE_RUNTIME_DIR);
|
|
26
|
-
}
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# launch.sh — mobile instance identity intake + prepare/preflight launch
|
|
3
|
-
#
|
|
4
|
-
# Purpose:
|
|
5
|
-
# Resolves the per-instance tuple (port, simulator/adb-serial, runtime dir,
|
|
6
|
-
# platform, preflight mode) and runs the installed runner's mobile prepare.
|
|
7
|
-
#
|
|
8
|
-
# Inputs (flags / env precedence):
|
|
9
|
-
# --target <metamask-mobile> (default $PWD)
|
|
10
|
-
# --platform ios|android (default ios)
|
|
11
|
-
# --preflight-mode|--mode fast|auto|default|rebuild-native|clean
|
|
12
|
-
# (env RECIPE_HARNESS_MOBILE_PREFLIGHT_MODE, default fast)
|
|
13
|
-
# --port|--watcher-port|--cdp-port <port> (env WATCHER_PORT > METRO_PORT > CDP_PORT)
|
|
14
|
-
# --simulator <sim> (env IOS_SIMULATOR > SIM_UDID)
|
|
15
|
-
# --adb-serial <serial> (env ADB_SERIAL > ANDROID_SERIAL)
|
|
16
|
-
# --runtime-dir <dir> (env RECIPE_RUNTIME_DIR)
|
|
17
|
-
# --wallet-setup / --no-wallet-setup, --wallet-fixture <json>
|
|
18
|
-
# --artifacts-dir <dir>
|
|
19
|
-
#
|
|
20
|
-
# Outputs:
|
|
21
|
-
# <artifacts>/summary.json (adapter/action/status/runtime/generatedAt),
|
|
22
|
-
# <artifacts>/runtime-status.json, <artifacts>/logs/prepare.log
|
|
23
|
-
# Exit 0 — launch pass; 1 — runner missing or prepare failed; 2 — bad args.
|
|
24
|
-
#
|
|
25
|
-
# Never touches: product source files; anything outside the target's harness
|
|
26
|
-
# dir and the artifacts dir.
|
|
27
|
-
set -euo pipefail
|
|
28
|
-
|
|
29
|
-
TARGET="$PWD"
|
|
30
|
-
PLATFORM="ios"
|
|
31
|
-
PREFLIGHT_MODE="${RECIPE_HARNESS_MOBILE_PREFLIGHT_MODE:-fast}"
|
|
32
|
-
ARTIFACTS=""
|
|
33
|
-
PORT="${WATCHER_PORT:-${METRO_PORT:-${CDP_PORT:-}}}"
|
|
34
|
-
SIMULATOR="${IOS_SIMULATOR:-${SIM_UDID:-}}"
|
|
35
|
-
ADB_SERIAL_ARG="${ADB_SERIAL:-${ANDROID_SERIAL:-}}"
|
|
36
|
-
RUNTIME_DIR_ARG="${RECIPE_RUNTIME_DIR:-}"
|
|
37
|
-
WALLET_SETUP=false
|
|
38
|
-
WALLET_FIXTURE_ARG=""
|
|
39
|
-
require_value() { [ "$#" -ge 2 ] || { echo "Missing value for $1" >&2; exit 2; }; }
|
|
40
|
-
while [ "$#" -gt 0 ]; do
|
|
41
|
-
case "$1" in
|
|
42
|
-
--target) require_value "$@"; TARGET="$2"; shift 2 ;;
|
|
43
|
-
--platform) require_value "$@"; PLATFORM="$2"; shift 2 ;;
|
|
44
|
-
--preflight-mode|--mode) require_value "$@"; PREFLIGHT_MODE="$2"; shift 2 ;;
|
|
45
|
-
--artifacts-dir) require_value "$@"; ARTIFACTS="$2"; shift 2 ;;
|
|
46
|
-
--port|--watcher-port|--cdp-port) require_value "$@"; PORT="$2"; shift 2 ;;
|
|
47
|
-
--simulator) require_value "$@"; SIMULATOR="$2"; shift 2 ;;
|
|
48
|
-
--adb-serial) require_value "$@"; ADB_SERIAL_ARG="$2"; shift 2 ;;
|
|
49
|
-
--runtime-dir) require_value "$@"; RUNTIME_DIR_ARG="$2"; shift 2 ;;
|
|
50
|
-
--wallet-setup) WALLET_SETUP=true; shift ;;
|
|
51
|
-
--no-wallet-setup) WALLET_SETUP=false; shift ;;
|
|
52
|
-
--wallet-fixture) require_value "$@"; WALLET_FIXTURE_ARG="$2"; shift 2 ;;
|
|
53
|
-
-h|--help)
|
|
54
|
-
echo "Usage: launch.sh [--target <metamask-mobile>] [--platform ios|android] [--preflight-mode fast|auto|default|rebuild-native|clean] [--port <port>] [--simulator <sim>] [--runtime-dir <dir>] [--wallet-setup] [--wallet-fixture <json>]"
|
|
55
|
-
exit 0
|
|
56
|
-
;;
|
|
57
|
-
*) echo "Unknown arg: $1" >&2; exit 2 ;;
|
|
58
|
-
esac
|
|
59
|
-
done
|
|
60
|
-
|
|
61
|
-
case "$PLATFORM" in ios|android) ;; *) echo "Unknown --platform: $PLATFORM" >&2; exit 2 ;; esac
|
|
62
|
-
case "$PREFLIGHT_MODE" in fast|auto|default|rebuild-native|clean) ;; *) echo "Unknown --preflight-mode: $PREFLIGHT_MODE" >&2; exit 2 ;; esac
|
|
63
|
-
|
|
64
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
65
|
-
# shellcheck disable=SC1091
|
|
66
|
-
for _hp in "$SCRIPT_DIR/lib/harness-path.sh" "$SCRIPT_DIR/../lib/harness-path.sh"; do
|
|
67
|
-
[ -f "$_hp" ] && { . "$_hp"; break; }
|
|
68
|
-
done
|
|
69
|
-
unset _hp
|
|
70
|
-
if ! command -v harness_root >/dev/null 2>&1; then
|
|
71
|
-
echo "mm-harness: shared lib orchestration/lib/harness-path.sh not found; reinstall the runner." >&2
|
|
72
|
-
exit 1
|
|
73
|
-
fi
|
|
74
|
-
TARGET="$(cd "$TARGET" && pwd)"
|
|
75
|
-
HARNESS_DIR="$(harness_dir "$TARGET" mobile)"
|
|
76
|
-
RUNNER_BIN="$HARNESS_DIR/runner/bin/metamask-recipe"
|
|
77
|
-
ARTIFACTS="${ARTIFACTS:-$HARNESS_DIR/launch/$(date -u +%Y%m%dT%H%M%SZ)}"
|
|
78
|
-
mkdir -p "$ARTIFACTS/logs"
|
|
79
|
-
|
|
80
|
-
if [ ! -x "$RUNNER_BIN" ]; then
|
|
81
|
-
echo "Mobile recipe runner is not installed in $TARGET. Run metamask-recipe mobile install --target $TARGET first." >&2
|
|
82
|
-
exit 1
|
|
83
|
-
fi
|
|
84
|
-
|
|
85
|
-
prepare_args=(mobile prepare --target "$TARGET" --platform "$PLATFORM" --preflight-mode "$PREFLIGHT_MODE")
|
|
86
|
-
[ -n "$PORT" ] && prepare_args+=(--port "$PORT")
|
|
87
|
-
[ -n "$SIMULATOR" ] && prepare_args+=(--simulator "$SIMULATOR")
|
|
88
|
-
[ -n "$ADB_SERIAL_ARG" ] && prepare_args+=(--adb-serial "$ADB_SERIAL_ARG")
|
|
89
|
-
[ -n "$RUNTIME_DIR_ARG" ] && prepare_args+=(--runtime-dir "$RUNTIME_DIR_ARG")
|
|
90
|
-
$WALLET_SETUP && prepare_args+=(--wallet-setup)
|
|
91
|
-
[ -n "$WALLET_FIXTURE_ARG" ] && prepare_args+=(--wallet-fixture "$WALLET_FIXTURE_ARG")
|
|
92
|
-
|
|
93
|
-
status="pass"
|
|
94
|
-
if (cd "$TARGET" && "$RUNNER_BIN" "${prepare_args[@]}") > "$ARTIFACTS/logs/prepare.log" 2>&1; then
|
|
95
|
-
runtime_status="pass"
|
|
96
|
-
else
|
|
97
|
-
runtime_status="fail"
|
|
98
|
-
status="fail"
|
|
99
|
-
fi
|
|
100
|
-
|
|
101
|
-
status_args=(mobile runtime-status --target "$TARGET" --json)
|
|
102
|
-
[ -n "$PORT" ] && status_args+=(--port "$PORT")
|
|
103
|
-
[ -n "$RUNTIME_DIR_ARG" ] && status_args+=(--runtime-dir "$RUNTIME_DIR_ARG")
|
|
104
|
-
(cd "$TARGET" && "$RUNNER_BIN" "${status_args[@]}") > "$ARTIFACTS/runtime-status.json" 2> "$ARTIFACTS/logs/runtime-status.err" || true
|
|
105
|
-
|
|
106
|
-
TARGET_FOR_SUMMARY="$TARGET" ARTIFACTS_FOR_SUMMARY="$ARTIFACTS" STATUS_FOR_SUMMARY="$status" RUNTIME_STATUS="$runtime_status" PLATFORM_FOR_SUMMARY="$PLATFORM" MODE_FOR_SUMMARY="$PREFLIGHT_MODE" node <<'NODE'
|
|
107
|
-
const fs = require('fs');
|
|
108
|
-
const path = require('path');
|
|
109
|
-
const artifacts = process.env.ARTIFACTS_FOR_SUMMARY;
|
|
110
|
-
let runtime = null;
|
|
111
|
-
try { runtime = JSON.parse(fs.readFileSync(path.join(artifacts, 'runtime-status.json'), 'utf8')); } catch {}
|
|
112
|
-
fs.writeFileSync(path.join(artifacts, 'summary.json'), `${JSON.stringify({
|
|
113
|
-
adapter: 'mobile',
|
|
114
|
-
action: 'launch',
|
|
115
|
-
status: process.env.STATUS_FOR_SUMMARY,
|
|
116
|
-
platform: process.env.PLATFORM_FOR_SUMMARY,
|
|
117
|
-
preflightMode: process.env.MODE_FOR_SUMMARY,
|
|
118
|
-
target: process.env.TARGET_FOR_SUMMARY,
|
|
119
|
-
runtime: {
|
|
120
|
-
status: process.env.RUNTIME_STATUS,
|
|
121
|
-
ready: runtime?.ready === true,
|
|
122
|
-
reason: runtime?.reason || null,
|
|
123
|
-
port: runtime?.metro?.port || null,
|
|
124
|
-
prepareLogPath: path.join(artifacts, 'logs/prepare.log'),
|
|
125
|
-
runtimeStatusPath: path.join(artifacts, 'runtime-status.json'),
|
|
126
|
-
},
|
|
127
|
-
runtimePolicy: {
|
|
128
|
-
nativeBuildPolicy: process.env.MODE_FOR_SUMMARY === 'fast'
|
|
129
|
-
? 'fast mode never runs a native build; it requires an installed dev client, starts/reuses Metro, prewarms the JS bundle, opens the dev client, and waits for the bridge'
|
|
130
|
-
: 'non-fast modes may install or rebuild the native dev client when needed; then start/reuse Metro, prewarm the JS bundle, open the dev client, and wait for the bridge',
|
|
131
|
-
},
|
|
132
|
-
generatedAt: new Date().toISOString(),
|
|
133
|
-
}, null, 2)}\n`);
|
|
134
|
-
NODE
|
|
135
|
-
|
|
136
|
-
echo "Mobile harness launch $status: $ARTIFACTS/summary.json"
|
|
137
|
-
[ "$status" = "pass" ]
|