@deeeed/metamask-harness 0.3.6 → 0.3.8
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 +20 -0
- package/adapters/mobile/inject.sh +5 -2
- package/package.json +1 -1
- package/src/adapters/mobile/prepare.ts +20 -14
- package/src/commands/shared.ts +62 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.8 - 2026-07-04
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- **Mobile launch streams progress in mode** — mobile prepare leaves now run through , which tees the child's stdout+stderr to the parent's stderr live while stdout stays reserved for the JSON envelope. A multi-minute {
|
|
7
|
+
"schemaVersion": 1,
|
|
8
|
+
"command": "launch",
|
|
9
|
+
"status": "fail",
|
|
10
|
+
"exitCode": 2,
|
|
11
|
+
"error": {
|
|
12
|
+
"code": "USAGE",
|
|
13
|
+
"message": "could not detect the MetaMask repo type for /private/tmp/claude-501/-Users-deeeed-dev-metamask-principal-pitch-2025/4645d206-3e7f-482d-a5c1-2274f26a7411/scratchpad/emh-review",
|
|
14
|
+
"userAction": "cd into a MetaMask checkout or pass --target <path>, or force it with --adapter <mobile|extension|core>"
|
|
15
|
+
}
|
|
16
|
+
} is no longer silent (previously buffered leaf output and suppressed it entirely under ). Contract test asserts leaf progress reaches stderr, the envelope stays clean, and the streaming conversion preserves the exit-code contract.
|
|
17
|
+
|
|
18
|
+
## 0.3.7 - 2026-07-04
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- **Overlay install manifest no longer advertises the retired `launch.sh`/`live.sh`** — mobile launch/live orchestration is in-process now (`mm-harness launch`), and `install_v1_runner_assets` deletes `scripts/launch.sh` and `scripts/live.sh` on both install paths. The product-owned/metadata-only manifest already advertised `verify` only, but the overlay manifest's `runtimeHelpers` still listed `launch: scripts/launch.sh` and `live: scripts/live.sh` — paths the same install had just removed, a manifest that pointed at nonexistent files. The overlay manifest now advertises `verify` only, matching the product-owned path. A new manifest-honesty contract check in `tests/contract/mobile-inject.test.sh` asserts every advertised `runtimeHelpers` path exists on disk and that `launch`/`live` are not advertised, for both the product-owned and a full overlay install, so this cannot recur in either mode.
|
|
22
|
+
|
|
3
23
|
## 0.3.6 - 2026-07-04
|
|
4
24
|
|
|
5
25
|
### Fixed
|
|
@@ -669,9 +669,12 @@ node -e '
|
|
|
669
669
|
protocolVersion: "v1",
|
|
670
670
|
actionManifestPath: harnessRel + "/action-manifest.json",
|
|
671
671
|
runnerEntrypoint: harnessRel + "/runner/bin/mm-harness",
|
|
672
|
+
// launch/live orchestration is in-process (mm-harness launch); only verify
|
|
673
|
+
// remains a shell helper. install_v1_runner_assets deletes scripts/launch.sh
|
|
674
|
+
// and scripts/live.sh, so advertising them here would be a manifest that
|
|
675
|
+
// points at files this same install removed. Keep this in sync with the
|
|
676
|
+
// product-owned block above (verify only).
|
|
672
677
|
runtimeHelpers: {
|
|
673
|
-
launch: harnessRel + "/scripts/launch.sh",
|
|
674
|
-
live: harnessRel + "/scripts/live.sh",
|
|
675
678
|
verify: harnessRel + "/scripts/verify.sh"
|
|
676
679
|
},
|
|
677
680
|
installedPaths: [harnessRel + "/runner", harnessRel + "/scripts", harnessRel + "/action-manifest.json", "app/dev-tools/AgenticService"],
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import path from 'node:path';
|
|
9
9
|
|
|
10
|
-
import { EXIT,
|
|
10
|
+
import { EXIT, spawnScriptStreaming } from '../../commands/shared.ts';
|
|
11
11
|
import type { ScriptResult } from '../../commands/shared.ts';
|
|
12
12
|
import { runnerDir } from '../../paths.ts';
|
|
13
13
|
import {
|
|
@@ -27,7 +27,11 @@ export { type MobileRuntimeDecisionReport };
|
|
|
27
27
|
const POD_PROBE_ENV: Record<string, string> = { FORCE_COLOR: '0', NO_COLOR: '1' };
|
|
28
28
|
|
|
29
29
|
export interface PrepareMobileOptions extends MobileRuntimeDecisionOptions {
|
|
30
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* Whether the caller emits a --json envelope on stdout. Leaf progress always
|
|
32
|
+
* streams to stderr (see dispatchAction/spawnScriptStreaming); this only gates
|
|
33
|
+
* the in-process teaching messages so they never pollute the stdout envelope.
|
|
34
|
+
*/
|
|
31
35
|
json?: boolean;
|
|
32
36
|
/**
|
|
33
37
|
* Passed to open-device.sh --preflight-mode.
|
|
@@ -106,7 +110,7 @@ export async function prepareMobile(
|
|
|
106
110
|
)
|
|
107
111
|
: report.actions;
|
|
108
112
|
for (const action of actions) {
|
|
109
|
-
const result = dispatchAction(action, target, platform, json, preflightMode);
|
|
113
|
+
const result = await dispatchAction(action, target, platform, json, preflightMode);
|
|
110
114
|
if (result.status !== 0) return result;
|
|
111
115
|
}
|
|
112
116
|
|
|
@@ -132,13 +136,13 @@ export async function prepareMobile(
|
|
|
132
136
|
}
|
|
133
137
|
case 'ready': {
|
|
134
138
|
// Deps resolved and runtime appears ready — still confirm bridge is live.
|
|
135
|
-
const bridge = dispatchAction({ id: 'wait-for-bridge', cwd: target }, target, platform, json, preflightMode);
|
|
139
|
+
const bridge = await dispatchAction({ id: 'wait-for-bridge', cwd: target }, target, platform, json, preflightMode);
|
|
136
140
|
if (bridge.status !== 0) return bridge;
|
|
137
141
|
break;
|
|
138
142
|
}
|
|
139
143
|
case 'launch': {
|
|
140
144
|
for (const action of postInstall.actions) {
|
|
141
|
-
const result = dispatchAction(action, target, platform, json, preflightMode);
|
|
145
|
+
const result = await dispatchAction(action, target, platform, json, preflightMode);
|
|
142
146
|
if (result.status !== 0) return result;
|
|
143
147
|
}
|
|
144
148
|
break;
|
|
@@ -157,51 +161,53 @@ export async function prepareMobile(
|
|
|
157
161
|
return { status: 0, output: '' };
|
|
158
162
|
}
|
|
159
163
|
|
|
160
|
-
|
|
164
|
+
// Long-running leaves stream their output to stderr live (spawnScriptStreaming),
|
|
165
|
+
// so the multi-minute prepare phase is not silent; stdout stays reserved for the
|
|
166
|
+
// caller's --json envelope. Async so the parent can await each leaf in sequence.
|
|
167
|
+
async function dispatchAction(
|
|
161
168
|
action: { id: string; argv?: string[]; cwd?: string },
|
|
162
169
|
target: string,
|
|
163
170
|
platform: string,
|
|
164
171
|
json: boolean,
|
|
165
172
|
preflightMode = 'fast',
|
|
166
|
-
): ScriptResult {
|
|
173
|
+
): Promise<ScriptResult> {
|
|
167
174
|
const cwd = action.cwd ?? target;
|
|
168
175
|
switch (action.id) {
|
|
169
176
|
case 'yarn-setup': {
|
|
170
177
|
// Leaf: install node_modules (deps missing or stale); `yarn setup` runs pods.
|
|
171
178
|
const leaf = path.join(runnerDir, 'adapters/mobile/yarn-setup.sh');
|
|
172
|
-
return
|
|
179
|
+
return spawnScriptStreaming(leaf, ['--target', cwd], target, POD_PROBE_ENV);
|
|
173
180
|
}
|
|
174
181
|
case 'start-metro': {
|
|
175
182
|
// Leaf: ensure Metro is running; argv may carry --clear for cache reset.
|
|
176
183
|
const leaf = path.join(runnerDir, 'adapters/mobile/start-metro.sh');
|
|
177
184
|
const extra: string[] = action.argv ?? [];
|
|
178
|
-
return
|
|
185
|
+
return spawnScriptStreaming(leaf, ['--target', cwd, ...extra], target);
|
|
179
186
|
}
|
|
180
187
|
case 'prewarm-bundle': {
|
|
181
188
|
// Leaf: curl the bundle URL before opening the dev client.
|
|
182
189
|
const leaf = path.join(runnerDir, 'adapters/mobile/prewarm-bundle.sh');
|
|
183
|
-
return
|
|
190
|
+
return spawnScriptStreaming(leaf, ['--platform', platform, '--target', cwd], target);
|
|
184
191
|
}
|
|
185
192
|
case 'wait-for-bridge': {
|
|
186
193
|
// Leaf: poll CDP bridge until a route target registers.
|
|
187
194
|
const leaf = path.join(runnerDir, 'adapters/mobile/wait-for-bridge.sh');
|
|
188
|
-
return
|
|
195
|
+
return spawnScriptStreaming(leaf, ['--target', cwd], target);
|
|
189
196
|
}
|
|
190
197
|
case 'clear-metro-cache': {
|
|
191
198
|
// Legacy alias for start-metro --clear (kept for backward compat).
|
|
192
199
|
const leaf = path.join(runnerDir, 'adapters/mobile/start-metro.sh');
|
|
193
|
-
return
|
|
200
|
+
return spawnScriptStreaming(leaf, ['--target', cwd, '--clear'], target);
|
|
194
201
|
}
|
|
195
202
|
case 'launch-mobile-runtime': {
|
|
196
203
|
// Leaf: open the MetaMask Mobile dev client on the simulator/device. In
|
|
197
204
|
// auto/rebuild modes this runs the native build (`yarn start:*`), which
|
|
198
205
|
// triggers pod install — pass the pod-probe env so it stays FORCE_COLOR-safe.
|
|
199
206
|
const leaf = path.join(runnerDir, 'adapters/mobile/open-device.sh');
|
|
200
|
-
return
|
|
207
|
+
return spawnScriptStreaming(
|
|
201
208
|
leaf,
|
|
202
209
|
['--platform', platform, '--target', cwd, '--preflight-mode', preflightMode],
|
|
203
210
|
target,
|
|
204
|
-
json,
|
|
205
211
|
POD_PROBE_ENV,
|
|
206
212
|
);
|
|
207
213
|
}
|
package/src/commands/shared.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// For node invocations (bin === process.execPath) the stem is derived from the
|
|
8
8
|
// script path in args[0], e.g. MM_HARNESS_SCRIPT_BIN_OPEN_DEBUG_MJS.
|
|
9
9
|
|
|
10
|
-
import { spawnSync } from 'node:child_process';
|
|
10
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
11
11
|
import path from 'node:path';
|
|
12
12
|
|
|
13
13
|
import { detectAdapter } from '../harness.ts';
|
|
@@ -151,6 +151,67 @@ export function spawnScript(
|
|
|
151
151
|
return { status: result.status ?? 1, output };
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
+
// Streaming variant of spawnScript for long-running leaves (mobile prepare:
|
|
155
|
+
// native build + Metro + health-bridge poll, minutes long). spawnScript buffers
|
|
156
|
+
// via spawnSync and, in --json mode, suppresses output entirely — so those leaves
|
|
157
|
+
// run with zero feedback until exit. This tees the child's stdout+stderr to the
|
|
158
|
+
// parent's STDERR live (so the --json envelope on stdout stays clean) while still
|
|
159
|
+
// capturing the combined output for heal classification. Same seam, leaf-invoke
|
|
160
|
+
// resolution, missing-leaf pre-check, and spawn-error contract as spawnScript.
|
|
161
|
+
export function spawnScriptStreaming(
|
|
162
|
+
script: string,
|
|
163
|
+
args: string[],
|
|
164
|
+
cwd: string,
|
|
165
|
+
env?: Record<string, string>,
|
|
166
|
+
): Promise<ScriptResult> {
|
|
167
|
+
const isNodeScript = script === process.execPath && args.length > 0;
|
|
168
|
+
const stem = isNodeScript
|
|
169
|
+
? path.basename(args[0]).replace(/[^A-Za-z0-9]/gu, '_').toUpperCase()
|
|
170
|
+
: path.basename(script).replace(/[^A-Za-z0-9]/gu, '_').toUpperCase();
|
|
171
|
+
const override = process.env[`MM_HARNESS_SCRIPT_BIN_${stem}`];
|
|
172
|
+
const bin = override ?? script;
|
|
173
|
+
const directArgs = override !== undefined && isNodeScript ? args.slice(1) : args;
|
|
174
|
+
|
|
175
|
+
if (shellLeafMissing(bin)) {
|
|
176
|
+
const message =
|
|
177
|
+
`leaf could not start: ${path.basename(bin)} (ENOENT)\n` +
|
|
178
|
+
` Next: reinstall mm-harness (npm i -g @deeeed/metamask-harness) — the shell leaf is missing or not executable`;
|
|
179
|
+
process.stderr.write(`${message}\n`);
|
|
180
|
+
return Promise.resolve({ status: 1, output: message });
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const { bin: invokeBin, args: spawnArgs } = resolveLeafInvoke(bin, directArgs);
|
|
184
|
+
return new Promise<ScriptResult>((resolve) => {
|
|
185
|
+
const child = spawn(invokeBin, spawnArgs, {
|
|
186
|
+
cwd,
|
|
187
|
+
env: env ? { ...process.env, ...env } : process.env,
|
|
188
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
189
|
+
});
|
|
190
|
+
let output = '';
|
|
191
|
+
// Route BOTH child streams to the parent's stderr, live: stdout is reserved
|
|
192
|
+
// for the harness --json envelope, so all human/leaf progress goes to stderr.
|
|
193
|
+
const tee = (chunk: Buffer): void => {
|
|
194
|
+
const text = chunk.toString('utf8');
|
|
195
|
+
output += text;
|
|
196
|
+
process.stderr.write(text);
|
|
197
|
+
};
|
|
198
|
+
child.stdout?.on('data', tee);
|
|
199
|
+
child.stderr?.on('data', tee);
|
|
200
|
+
child.on('error', (error: NodeJS.ErrnoException) => {
|
|
201
|
+
const leaf = isNodeScript ? path.basename(args[0]) : path.basename(script);
|
|
202
|
+
const code = error.code ?? 'ESPAWN';
|
|
203
|
+
const message =
|
|
204
|
+
`leaf could not start: ${leaf} (${code})\n` +
|
|
205
|
+
` Next: reinstall mm-harness (npm i -g @deeeed/metamask-harness) — the shell leaf is missing or not executable`;
|
|
206
|
+
process.stderr.write(`${message}\n`);
|
|
207
|
+
resolve({ status: 1, output: message });
|
|
208
|
+
});
|
|
209
|
+
child.on('close', (status) => {
|
|
210
|
+
resolve({ status: status ?? 1, output });
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
154
215
|
// Both escapes from a failed repo-type detection (defined at the detection source
|
|
155
216
|
// in harness.ts): where to run it (checkout/target) and how to force it (adapter).
|
|
156
217
|
export { ADAPTER_DETECT_NEXT } from '../harness.ts';
|