@deeeed/metamask-harness 0.75.0 → 0.76.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 +22 -0
- package/adapters/shared/reap-checkout-metros.sh +9 -0
- package/dist/adapters/core/overlay.js +34 -0
- package/dist/adapters/core/readiness.js +8 -0
- package/dist/adapters/core/surface.js +7 -0
- package/dist/adapters/extension/doctor-checks.js +135 -0
- package/dist/adapters/extension/fixtures.js +103 -0
- package/dist/adapters/extension/overlay.js +55 -0
- package/dist/adapters/extension/readiness.js +32 -0
- package/dist/adapters/extension/reload.js +40 -0
- package/dist/adapters/extension/surface.js +22 -1
- package/dist/adapters/harness-freshness.js +4 -2
- package/dist/adapters/mobile/devices.js +21 -0
- package/dist/adapters/mobile/doctor-checks.js +40 -0
- package/dist/adapters/mobile/fixtures.js +141 -0
- package/dist/adapters/mobile/overlay.js +27 -0
- package/dist/adapters/mobile/prepare-target.js +38 -0
- package/dist/adapters/mobile/readiness.js +39 -0
- package/dist/adapters/mobile/reload.js +15 -0
- package/dist/adapters/mobile/surface.js +28 -1
- package/dist/adapters/terminal/doctor-checks.js +22 -0
- package/dist/adapters/terminal/overlay.js +18 -0
- package/dist/adapters/terminal/readiness.js +11 -0
- package/dist/adapters/terminal/surface.js +15 -2
- package/dist/cli.js +3 -1
- package/dist/commands/debug.js +6 -25
- package/dist/commands/device-target.js +2 -1
- package/dist/commands/doctor.js +26 -137
- package/dist/commands/fixtures.js +13 -217
- package/dist/commands/logs.js +1 -1
- package/dist/commands/parse-args.js +9 -4
- package/dist/commands/prepare.js +23 -53
- package/dist/commands/provision.js +3 -7
- package/dist/commands/reload.js +8 -47
- package/dist/commands/runtime-decision.js +5 -27
- package/dist/commands/self-test.js +1 -3
- package/dist/commands/status.js +20 -28
- package/dist/commands/stop.js +1 -1
- package/dist/doctor.js +9 -119
- package/dist/harness.js +31 -100
- package/dist/mm-harness-cli.js +3 -2
- package/dist/paths.js +8 -2
- package/dist/runtime-context.js +12 -17
- package/docs/CONTRIBUTING.md +27 -17
- package/package.json +1 -1
package/dist/harness.js
CHANGED
|
@@ -3,7 +3,7 @@ import { execFileSync } from "node:child_process";
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { shellLeafMissing, missingShellLeafMessage } from "./leaf-invoke.js";
|
|
6
|
-
import {
|
|
6
|
+
import { adapterRegistry } from "./adapters/registry.js";
|
|
7
7
|
import { recipeHarnessPath, recipeRuntimeDir, runnerDir } from "./paths.js";
|
|
8
8
|
import { colorHumanMessage } from "./cli-color.js";
|
|
9
9
|
const HARNESS_ACTIONS = ["install", "verify", "cleanup"];
|
|
@@ -37,15 +37,15 @@ Examples:
|
|
|
37
37
|
|
|
38
38
|
core supports install, verify, and cleanup (headless; no live).`);
|
|
39
39
|
}
|
|
40
|
-
function failureHint(
|
|
41
|
-
|
|
40
|
+
function failureHint(surface, action) {
|
|
41
|
+
const restart = surface.harness.restart;
|
|
42
|
+
if (!restart) {
|
|
42
43
|
if (action === "verify") {
|
|
43
|
-
return `Install the
|
|
44
|
+
return `Install the ${surface.adapter} harness first: mm-harness install --platform ${surface.adapter}`;
|
|
44
45
|
}
|
|
45
46
|
return "Read the error above for the specific cause, then re-run this command.";
|
|
46
47
|
}
|
|
47
|
-
|
|
48
|
-
return `Read the error above for the specific cause. To (re)start the runtime, run: ${helper}`;
|
|
48
|
+
return `Read the error above for the specific cause. To (re)start the runtime, run: ${restart}`;
|
|
49
49
|
}
|
|
50
50
|
function successNext(adapter, action, target) {
|
|
51
51
|
const base = `--adapter ${adapter} --target ${shellQuote(target)}`;
|
|
@@ -67,7 +67,7 @@ function shellQuote(value) {
|
|
|
67
67
|
return /^[A-Za-z0-9_./:=@+-]+$/u.test(value) ? value : `'${value.replace(/'/gu, `'\\''`)}'`;
|
|
68
68
|
}
|
|
69
69
|
function isAdapter(value) {
|
|
70
|
-
return value
|
|
70
|
+
return value !== void 0 && adapterRegistry.has(value);
|
|
71
71
|
}
|
|
72
72
|
function parseHarnessArgs(args) {
|
|
73
73
|
const forward = [];
|
|
@@ -170,37 +170,9 @@ function readRuntimeContextField(contextPath, field) {
|
|
|
170
170
|
if (node === void 0 || node === null || node === "") return void 0;
|
|
171
171
|
return String(node);
|
|
172
172
|
}
|
|
173
|
-
function applyExtensionRuntimeEnv(target, args) {
|
|
174
|
-
const contextPath = resolveRuntimeContextPath(target);
|
|
175
|
-
const contextExists = fs.existsSync(contextPath);
|
|
176
|
-
if (contextExists) {
|
|
177
|
-
process.env.RECIPE_RUNTIME_CONTEXT = contextPath;
|
|
178
|
-
if (!process.env.RECIPE_SLOT_ID) {
|
|
179
|
-
const slotId = readRuntimeContextField(contextPath, "slotId");
|
|
180
|
-
if (slotId) process.env.RECIPE_SLOT_ID = slotId;
|
|
181
|
-
}
|
|
182
|
-
if (!process.env.RECIPE_HARNESS_EXTENSION_ID) {
|
|
183
|
-
const extensionId = readRuntimeContextField(contextPath, "extensionId");
|
|
184
|
-
if (extensionId) process.env.RECIPE_HARNESS_EXTENSION_ID = extensionId;
|
|
185
|
-
}
|
|
186
|
-
} else {
|
|
187
|
-
delete process.env.RECIPE_RUNTIME_CONTEXT;
|
|
188
|
-
}
|
|
189
|
-
resolveExtensionSlotPorts(target);
|
|
190
|
-
let result = [...args];
|
|
191
|
-
if (!hasArg(result, "--cdp-port")) {
|
|
192
|
-
const cdpPort = process.env.RECIPE_CDP_PORT ?? process.env.CDP_PORT;
|
|
193
|
-
if (cdpPort) {
|
|
194
|
-
process.env.RECIPE_CDP_PORT = cdpPort;
|
|
195
|
-
process.env.CDP_PORT = cdpPort;
|
|
196
|
-
result = [...result, "--cdp-port", cdpPort];
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
return result;
|
|
200
|
-
}
|
|
201
173
|
function resolveEntry(base, candidates, mode) {
|
|
202
174
|
for (const candidate of candidates) {
|
|
203
|
-
const full = path.
|
|
175
|
+
const full = path.resolve(base, candidate);
|
|
204
176
|
try {
|
|
205
177
|
const stat = fs.statSync(full);
|
|
206
178
|
if (!stat.isFile()) continue;
|
|
@@ -208,7 +180,7 @@ function resolveEntry(base, candidates, mode) {
|
|
|
208
180
|
} catch {
|
|
209
181
|
}
|
|
210
182
|
}
|
|
211
|
-
return path.
|
|
183
|
+
return path.resolve(base, candidates[0]);
|
|
212
184
|
}
|
|
213
185
|
function installedRunnerSource(target, adapter) {
|
|
214
186
|
const pointer = path.join(recipeHarnessPath(target, adapter), "runner", ".runner-source");
|
|
@@ -217,56 +189,12 @@ function installedRunnerSource(target, adapter) {
|
|
|
217
189
|
if (!value || !fs.existsSync(value)) return void 0;
|
|
218
190
|
return value;
|
|
219
191
|
}
|
|
220
|
-
function
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
const INJECT_CANDIDATES = {
|
|
229
|
-
mobile: { entry: "adapters/mobile/inject.sh", fallback: "scripts/inject-mobile-harness.sh" },
|
|
230
|
-
extension: { entry: "adapters/extension/inject.mjs", fallback: "scripts/inject-extension-harness.mjs" },
|
|
231
|
-
core: { entry: "adapters/core/inject.sh", fallback: "scripts/inject-core-harness.sh" },
|
|
232
|
-
terminal: { entry: "adapters/terminal/inject.mjs", fallback: "adapters/terminal/inject.mjs" }
|
|
233
|
-
};
|
|
234
|
-
const CLEANUP_CANDIDATES = {
|
|
235
|
-
mobile: { entry: "adapters/mobile/cleanup.sh", fallback: "scripts/cleanup-mobile-harness.sh" },
|
|
236
|
-
extension: { entry: "adapters/extension/cleanup.mjs", fallback: "scripts/cleanup-extension-harness.mjs" },
|
|
237
|
-
core: { entry: "adapters/core/cleanup.sh", fallback: "scripts/cleanup-core-harness.sh" },
|
|
238
|
-
terminal: { entry: "adapters/terminal/cleanup.mjs", fallback: "adapters/terminal/cleanup.mjs" }
|
|
239
|
-
};
|
|
240
|
-
function resolveHarnessDispatch(adapter, action, target) {
|
|
241
|
-
if (action === "install") {
|
|
242
|
-
const { entry, fallback } = INJECT_CANDIDATES[adapter];
|
|
243
|
-
if (adapter === "extension" || adapter === "terminal") {
|
|
244
|
-
return { command: process.execPath, prefixArgs: [resolveEntry(runnerDir, [entry, fallback], "file")] };
|
|
245
|
-
}
|
|
246
|
-
return { command: resolveEntry(runnerDir, [entry, fallback], "file"), prefixArgs: [] };
|
|
247
|
-
}
|
|
248
|
-
if (action === "cleanup") {
|
|
249
|
-
const { entry, fallback } = CLEANUP_CANDIDATES[adapter];
|
|
250
|
-
const base = adapter === "terminal" ? runnerDir : installedRunnerSource(target, adapter) ?? runnerDir;
|
|
251
|
-
if (adapter === "extension" || adapter === "terminal") {
|
|
252
|
-
return { command: process.execPath, prefixArgs: [resolveEntry(base, [entry, fallback], "file")] };
|
|
253
|
-
}
|
|
254
|
-
return { command: resolveEntry(base, [entry, fallback], "file"), prefixArgs: [] };
|
|
255
|
-
}
|
|
256
|
-
if (adapter === "terminal" && action === "verify") {
|
|
257
|
-
return { command: process.execPath, prefixArgs: [path.join(runnerDir, "adapters", "terminal", "verify.mjs")] };
|
|
258
|
-
}
|
|
259
|
-
if (adapter === "core" && action === "verify") {
|
|
260
|
-
const delegate = path.join(recipeHarnessPath(target, "core"), "runner", "bin", "mm-harness");
|
|
261
|
-
if (!isExecutable(delegate)) {
|
|
262
|
-
return {
|
|
263
|
-
error: `core recipe harness not installed: missing delegate ${delegate}.
|
|
264
|
-
Next: mm-harness install --platform core --target ${target}`
|
|
265
|
-
};
|
|
266
|
-
}
|
|
267
|
-
return { command: delegate, prefixArgs: ["doctor", "--adapter", "core"] };
|
|
268
|
-
}
|
|
269
|
-
return { inProcess: adapter === "mobile" ? "mobile-verify" : "extension-verify" };
|
|
192
|
+
function resolveHarnessDispatch(surface, action, target) {
|
|
193
|
+
if (action === "verify") return surface.harness.verify(target);
|
|
194
|
+
const leaf = action === "install" ? surface.harness.install : surface.harness.cleanup;
|
|
195
|
+
const base = action === "cleanup" && surface.harness.cleanup.fromInstalledRunner ? installedRunnerSource(target, surface.adapter) ?? runnerDir : runnerDir;
|
|
196
|
+
const entry = resolveEntry(base, [leaf.entry, leaf.fallback], "file");
|
|
197
|
+
return leaf.node ? { command: process.execPath, prefixArgs: [entry] } : { command: entry, prefixArgs: [] };
|
|
270
198
|
}
|
|
271
199
|
async function handleHarness(argv) {
|
|
272
200
|
const action = argv[0];
|
|
@@ -280,6 +208,7 @@ async function handleHarness(argv) {
|
|
|
280
208
|
return 2;
|
|
281
209
|
}
|
|
282
210
|
const harnessAction = action;
|
|
211
|
+
const { getAdapterSurface } = await import("./adapters/surface.js");
|
|
283
212
|
const { adapter: parsedAdapter, json, quiet, forward } = parseHarnessArgs(argv.slice(1));
|
|
284
213
|
const rawTarget = argValue(forward, "--target");
|
|
285
214
|
const target = path.resolve(rawTarget ?? process.cwd());
|
|
@@ -306,10 +235,11 @@ async function handleHarness(argv) {
|
|
|
306
235
|
if (harnessAction === "install" && hasArg(forward, "--runway")) {
|
|
307
236
|
return handleRunwayInstall(adapter, target, forward, json);
|
|
308
237
|
}
|
|
309
|
-
|
|
310
|
-
|
|
238
|
+
const surface = getAdapterSurface(adapter);
|
|
239
|
+
if (harnessAction === "verify" && surface.harness.verifyArgs) {
|
|
240
|
+
forwardArgs = surface.harness.verifyArgs(target, forwardArgs);
|
|
311
241
|
}
|
|
312
|
-
const dispatch = resolveHarnessDispatch(
|
|
242
|
+
const dispatch = resolveHarnessDispatch(surface, harnessAction, target);
|
|
313
243
|
if ("error" in dispatch) {
|
|
314
244
|
if (quiet) {
|
|
315
245
|
return 1;
|
|
@@ -335,7 +265,7 @@ async function handleHarness(argv) {
|
|
|
335
265
|
}
|
|
336
266
|
const start = Date.now();
|
|
337
267
|
if ("inProcess" in dispatch) {
|
|
338
|
-
const runVerify = dispatch.inProcess
|
|
268
|
+
const runVerify = await dispatch.inProcess();
|
|
339
269
|
let exitCode;
|
|
340
270
|
try {
|
|
341
271
|
exitCode = await runVerify(forwardArgs, {
|
|
@@ -355,7 +285,7 @@ async function handleHarness(argv) {
|
|
|
355
285
|
`);
|
|
356
286
|
exitCode = 1;
|
|
357
287
|
}
|
|
358
|
-
return reportHarnessOutcome(harnessAction,
|
|
288
|
+
return reportHarnessOutcome(harnessAction, surface, target, exitCode, autoDetected, json, quiet, start);
|
|
359
289
|
}
|
|
360
290
|
if (shellLeafMissing(dispatch.command)) {
|
|
361
291
|
const message = missingShellLeafMessage(dispatch.command);
|
|
@@ -366,7 +296,7 @@ async function handleHarness(argv) {
|
|
|
366
296
|
harnessSummary(harnessAction, adapter, target, "fail", 1, autoDetected, {
|
|
367
297
|
code: "HARNESS_SPAWN_FAILED",
|
|
368
298
|
message,
|
|
369
|
-
userAction: failureHint(
|
|
299
|
+
userAction: failureHint(surface, harnessAction)
|
|
370
300
|
})
|
|
371
301
|
);
|
|
372
302
|
} else {
|
|
@@ -387,19 +317,20 @@ async function handleHarness(argv) {
|
|
|
387
317
|
harnessSummary(harnessAction, adapter, target, "fail", 1, autoDetected, {
|
|
388
318
|
code: "HARNESS_SPAWN_FAILED",
|
|
389
319
|
message: `${harnessAction} ${adapter} could not start: ${result.error.message}`,
|
|
390
|
-
userAction: failureHint(
|
|
320
|
+
userAction: failureHint(surface, harnessAction)
|
|
391
321
|
})
|
|
392
322
|
);
|
|
393
323
|
} else {
|
|
394
324
|
console.error(`\u2717 ${harnessAction} ${adapter} could not start: ${result.error.message}
|
|
395
|
-
${failureHint(
|
|
325
|
+
${failureHint(surface, harnessAction)}`);
|
|
396
326
|
}
|
|
397
327
|
return 1;
|
|
398
328
|
}
|
|
399
|
-
return reportHarnessOutcome(harnessAction,
|
|
329
|
+
return reportHarnessOutcome(harnessAction, surface, target, result.status ?? 1, autoDetected, json, quiet, start);
|
|
400
330
|
}
|
|
401
|
-
function reportHarnessOutcome(action,
|
|
331
|
+
function reportHarnessOutcome(action, surface, target, exitCode, autoDetected, json, quiet, start) {
|
|
402
332
|
const seconds = ((Date.now() - start) / 1e3).toFixed(1);
|
|
333
|
+
const adapter = surface.adapter;
|
|
403
334
|
if (quiet) {
|
|
404
335
|
return exitCode;
|
|
405
336
|
} else if (json) {
|
|
@@ -411,7 +342,7 @@ function reportHarnessOutcome(action, adapter, target, exitCode, autoDetected, j
|
|
|
411
342
|
exitCode === 0 ? "pass" : "fail",
|
|
412
343
|
exitCode,
|
|
413
344
|
autoDetected,
|
|
414
|
-
exitCode === 0 ? void 0 : { code: "HARNESS_FAILED", message: `${action} ${adapter} failed (exit ${exitCode})`, userAction: failureHint(
|
|
345
|
+
exitCode === 0 ? void 0 : { code: "HARNESS_FAILED", message: `${action} ${adapter} failed (exit ${exitCode})`, userAction: failureHint(surface, action) }
|
|
415
346
|
)
|
|
416
347
|
);
|
|
417
348
|
} else if (exitCode === 0) {
|
|
@@ -419,7 +350,7 @@ function reportHarnessOutcome(action, adapter, target, exitCode, autoDetected, j
|
|
|
419
350
|
Next: ${successNext(adapter, action, target)}`));
|
|
420
351
|
} else {
|
|
421
352
|
console.error(colorHumanMessage(`\u2717 ${action} ${adapter} failed (exit ${exitCode}, ${seconds}s)
|
|
422
|
-
${failureHint(
|
|
353
|
+
${failureHint(surface, action)}`));
|
|
423
354
|
}
|
|
424
355
|
return exitCode;
|
|
425
356
|
}
|
|
@@ -448,7 +379,7 @@ async function handleRunwayInstall(adapter, target, forward, json) {
|
|
|
448
379
|
});
|
|
449
380
|
const provisionedSimulator = typeof result.simulator === "object" && result.simulator ? result.simulator : void 0;
|
|
450
381
|
const provisionedDevice = typeof provisionedSimulator?.udid === "string" ? provisionedSimulator.udid : typeof provisionedSimulator?.name === "string" ? provisionedSimulator.name : void 0;
|
|
451
|
-
const next = result.status === "pass" &&
|
|
382
|
+
const next = result.status === "pass" && surface.runwayProvision.launchNext && result.resolveOnly !== true ? `mm-harness launch ${String(result.platform ?? "ios")} --adapter ${adapter} --target ${shellQuote(target)}` + (provisionedDevice ? ` --device ${shellQuote(provisionedDevice)}` : "") : void 0;
|
|
452
383
|
const output = next ? { ...result, next } : result;
|
|
453
384
|
if (json) {
|
|
454
385
|
console.log(JSON.stringify(output, null, 2));
|
package/dist/mm-harness-cli.js
CHANGED
|
@@ -1032,8 +1032,9 @@ function detectedSlotLine(out) {
|
|
|
1032
1032
|
if (ctx.slotId) parts.push(`slot ${out("ok", String(ctx.slotId))}`);
|
|
1033
1033
|
if (ctx.simulator) parts.push(`device ${out("ok", String(ctx.simulator))}`);
|
|
1034
1034
|
const devServerPort = ctx.watcherPort ?? ctx.devServerPort ?? ctx.metroPort;
|
|
1035
|
-
|
|
1036
|
-
|
|
1035
|
+
const surface = adapter ? getAdapterSurface(adapter) : void 0;
|
|
1036
|
+
if (surface && !surface.headless && devServerPort) {
|
|
1037
|
+
parts.push(`${surface.devServer.label} :${out("ok", String(devServerPort))}`);
|
|
1037
1038
|
}
|
|
1038
1039
|
if (ctx.gitBranch) parts.push(`branch ${out("info", String(ctx.gitBranch))}`);
|
|
1039
1040
|
if (parts.length === 0) return null;
|
package/dist/paths.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
4
|
+
import { adapterRegistry } from "./adapters/registry.js";
|
|
4
5
|
const runnerDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
5
6
|
const pathDefaults = readPathDefaults();
|
|
6
7
|
const DEFAULT_RECIPE_RUNTIME_DIR = pathDefaults.recipeRuntimeDir;
|
|
@@ -88,8 +89,13 @@ function isProtocolRoot(candidate) {
|
|
|
88
89
|
return fs.existsSync(path.join(candidate, "packages/recipe-harness/package.json")) && fs.existsSync(path.join(candidate, "packages/protocol/package.json"));
|
|
89
90
|
}
|
|
90
91
|
function assertAdapter(adapter) {
|
|
91
|
-
|
|
92
|
-
|
|
92
|
+
const ids = adapterRegistry.list();
|
|
93
|
+
if (ids.length === 0) {
|
|
94
|
+
throw new Error("internal: no adapters are registered yet; import src/adapters/surface.ts before validating an adapter");
|
|
95
|
+
}
|
|
96
|
+
if (typeof adapter !== "string" || !adapterRegistry.has(adapter)) {
|
|
97
|
+
const choices = ids.length > 1 ? `${ids.slice(0, -1).join(", ")}, or ${ids.at(-1)}` : ids.join("");
|
|
98
|
+
throw new Error(`Adapter must be ${choices}.`);
|
|
93
99
|
}
|
|
94
100
|
}
|
|
95
101
|
function manifestPath(adapter) {
|
package/dist/runtime-context.js
CHANGED
|
@@ -3,6 +3,7 @@ import { execFileSync } from "node:child_process";
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
6
|
+
import { getAdapterSurface } from "./adapters/surface.js";
|
|
6
7
|
import { recipeRuntimeDir } from "./paths.js";
|
|
7
8
|
function ensureRuntimeContext(target, adapter) {
|
|
8
9
|
const repoRoot = fs.realpathSync(target);
|
|
@@ -91,7 +92,7 @@ function validExistingContext(context, repoRoot, runtimeDir, adapter) {
|
|
|
91
92
|
if (runtimeStart[field] !== void 0 && (typeof runtimeStart[field] !== "string" || runtimeStart[field].length === 0)) return false;
|
|
92
93
|
}
|
|
93
94
|
}
|
|
94
|
-
const forbiddenResources = adapter
|
|
95
|
+
const forbiddenResources = getAdapterSurface(adapter).runtimeContext.forbiddenFields;
|
|
95
96
|
if (forbiddenResources.some((field) => context[field] !== void 0)) return false;
|
|
96
97
|
return true;
|
|
97
98
|
}
|
|
@@ -104,11 +105,12 @@ function localDefaults(repoRoot, adapter, existing) {
|
|
|
104
105
|
const digest = createHash("sha256").update(repoRoot).digest("hex");
|
|
105
106
|
const offset = Number.parseInt(digest.slice(0, 8), 16) % 1e4;
|
|
106
107
|
const defaults = { slotId: `local-${adapter}-${digest.slice(0, 8)}` };
|
|
107
|
-
|
|
108
|
-
|
|
108
|
+
const localPorts = getAdapterSurface(adapter).runtimeContext.localPorts ?? {};
|
|
109
|
+
if (localPorts.cdp !== void 0 && !numericEnv("RECIPE_CDP_PORT", "CDP_PORT") && !existing.cdpPort) {
|
|
110
|
+
defaults.cdpPort = claimLocalPort(repoRoot, "cdp", localPorts.cdp + offset);
|
|
109
111
|
}
|
|
110
|
-
if (
|
|
111
|
-
defaults.watcherPort = claimLocalPort(repoRoot, "watcher",
|
|
112
|
+
if (localPorts.watcher !== void 0 && !numericEnv("WATCHER_PORT", "METRO_PORT", "RECIPE_WATCHER_PORT") && !existing.watcherPort && !existing.metroPort && !existing.devServerPort) {
|
|
113
|
+
defaults.watcherPort = claimLocalPort(repoRoot, "watcher", localPorts.watcher + offset);
|
|
112
114
|
}
|
|
113
115
|
return defaults;
|
|
114
116
|
}
|
|
@@ -158,21 +160,14 @@ function portClaimIsLive(claim, port) {
|
|
|
158
160
|
}
|
|
159
161
|
}
|
|
160
162
|
function runtimeResources(adapter, existing, defaults) {
|
|
161
|
-
|
|
163
|
+
const { resources } = getAdapterSurface(adapter).runtimeContext;
|
|
164
|
+
if (!resources) return {};
|
|
162
165
|
const watcherPort = numericEnv("WATCHER_PORT", "METRO_PORT", "RECIPE_WATCHER_PORT") ?? existing.watcherPort ?? existing.metroPort ?? existing.devServerPort ?? defaults.watcherPort;
|
|
163
|
-
|
|
166
|
+
return {
|
|
164
167
|
watcherPort,
|
|
165
|
-
devServerPort: watcherPort
|
|
168
|
+
devServerPort: watcherPort,
|
|
169
|
+
...resources({ watcherPort, existing, defaults, envPort: numericEnv })
|
|
166
170
|
};
|
|
167
|
-
if (adapter === "extension" || adapter === "terminal") {
|
|
168
|
-
shared.cdpPort = numericEnv("RECIPE_CDP_PORT", "CDP_PORT") ?? existing.cdpPort ?? defaults.cdpPort;
|
|
169
|
-
} else {
|
|
170
|
-
shared.metroPort = watcherPort;
|
|
171
|
-
shared.simulator = process.env.IOS_SIMULATOR || existing.simulator;
|
|
172
|
-
shared.simulatorUdid = process.env.SIM_UDID || existing.simulatorUdid;
|
|
173
|
-
shared.adbSerial = process.env.ADB_SERIAL || process.env.ANDROID_SERIAL || existing.adbSerial;
|
|
174
|
-
}
|
|
175
|
-
return shared;
|
|
176
171
|
}
|
|
177
172
|
function numericEnv(...names) {
|
|
178
173
|
for (const name of names) {
|
package/docs/CONTRIBUTING.md
CHANGED
|
@@ -62,25 +62,35 @@ exits.
|
|
|
62
62
|
|
|
63
63
|
Commands resolve shared platform behavior through
|
|
64
64
|
`src/adapters/surface.ts`. Each platform's surface registers itself in
|
|
65
|
-
`src/adapters/registry.ts` when its module loads
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
plugin loader
|
|
65
|
+
`src/adapters/registry.ts` when its module loads, and commands look adapters up
|
|
66
|
+
in the registry, so a platform registered the same way is reached by the
|
|
67
|
+
commands that take `--adapter` with no edit to shared code
|
|
68
|
+
(`tests/fixtures/example-adapter.ts`); `status` detects the adapter from the
|
|
69
|
+
checkout.
|
|
70
|
+
The CLI contract (`src/command-contract.ts`) still lists the four built-ins for
|
|
71
|
+
`--adapter`, so a new platform reaches `bin/mm-harness` once a plugin loader
|
|
72
|
+
registers it and extends that list.
|
|
72
73
|
|
|
73
74
|
Extend `AdapterSurface` when a platform owns the behavior; members a platform
|
|
74
|
-
does not need are optional
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
`
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
`
|
|
82
|
-
|
|
83
|
-
`
|
|
75
|
+
does not need are optional, and Core reports headless/N/A explicitly. Put each
|
|
76
|
+
platform's implementation under `src/adapters/<adapter>/`: `launch.ts`,
|
|
77
|
+
`run.ts`, `actions.ts`, `overlay.ts` (install/verify/cleanup, the runtime
|
|
78
|
+
context, prepare) and `readiness.ts` (doctor and status). Do not add
|
|
79
|
+
command-local platform ladders for surface-owned behavior:
|
|
80
|
+
- `launch` dispatches through `AdapterSurface.launch` (the device platforms
|
|
81
|
+
share `src/commands/launch/device.ts` through a `DeviceLaunchProfile`);
|
|
82
|
+
- `run`/`call` read `run`, `recording`, `diagnostics`, `observation` and
|
|
83
|
+
`sourceFingerprint`;
|
|
84
|
+
- the runner reads `actions` (manifest, bundled semantic actions, live adapter
|
|
85
|
+
scripts, `cdp.target`, the `ui.*` transport, adapter wrappers, input checks);
|
|
86
|
+
- `install`/`verify`/`cleanup` read `harness`, `agentic-runtime.json` reads
|
|
87
|
+
`runtimeContext`, `doctor`/`status` read `readiness`, and `fixtures`,
|
|
88
|
+
`prepare`, `debug`, `reload` and `runtime-decision` read their own members.
|
|
89
|
+
|
|
90
|
+
A few comparisons remain where a command exists for one platform
|
|
91
|
+
(`runtime-health`, `resolve-extension`, `ensure-ready`, `runtime-launch`) or
|
|
92
|
+
code compares a platform's own data (`review/knowledge.ts`, the Core jest
|
|
93
|
+
grouping in `check.ts`, the Mobile release-artifact state file).
|
|
84
94
|
|
|
85
95
|
Each checkout owns:
|
|
86
96
|
|