@deeeed/metamask-harness 0.75.0 → 0.77.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 +28 -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/call.js +3 -7
- 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/list-executables.js +12 -22
- package/dist/commands/logs.js +1 -1
- package/dist/commands/manifest.js +11 -133
- 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/run.js +2 -5
- 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 +2 -2
|
@@ -17,6 +17,15 @@ import {
|
|
|
17
17
|
} from "@farmslot/protocol";
|
|
18
18
|
import { metaMaskActionExecutionCapabilities } from "../recipe-security.js";
|
|
19
19
|
import { closest } from "../command-contract.js";
|
|
20
|
+
import {
|
|
21
|
+
actionCapabilityMatrix,
|
|
22
|
+
actionCategory,
|
|
23
|
+
findRelatedActions,
|
|
24
|
+
fuzzyResolveActions,
|
|
25
|
+
resolveActionCapabilityRefusal,
|
|
26
|
+
searchActions,
|
|
27
|
+
summarizeActionCategories
|
|
28
|
+
} from "@farmslot/recipe-cli";
|
|
20
29
|
const OFFICIAL_ACTIONS = new Set(OFFICIAL_RECIPE_ACTIONS);
|
|
21
30
|
const ACTION_MATRIX_ADAPTERS = ["mobile", "extension", "core", "terminal"];
|
|
22
31
|
function actionLibraryContextArgs(librarySources) {
|
|
@@ -178,51 +187,7 @@ async function resolveActionCapabilityMatrix(librarySources) {
|
|
|
178
187
|
return { adapter, actions: describeManifestActions(manifest, actionSources) };
|
|
179
188
|
})
|
|
180
189
|
);
|
|
181
|
-
|
|
182
|
-
for (const catalog of catalogs) {
|
|
183
|
-
for (const action of catalog.actions) {
|
|
184
|
-
const entry = actions.get(action.name) ?? { entries: [], adapters: /* @__PURE__ */ new Set() };
|
|
185
|
-
entry.entries.push(action);
|
|
186
|
-
entry.adapters.add(catalog.adapter);
|
|
187
|
-
actions.set(action.name, entry);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
return [...actions.entries()].map(([name, entry]) => {
|
|
191
|
-
const representative = entry.entries[0];
|
|
192
|
-
const satisfyingAdapters = ACTION_MATRIX_ADAPTERS.filter(
|
|
193
|
-
(adapter) => entry.adapters.has(adapter)
|
|
194
|
-
);
|
|
195
|
-
return {
|
|
196
|
-
name,
|
|
197
|
-
category: representative.category,
|
|
198
|
-
description: entry.entries.map((action) => action.description).find(Boolean) ?? "",
|
|
199
|
-
fields: [...new Set(entry.entries.flatMap((action) => action.fields))].sort(),
|
|
200
|
-
support: Object.fromEntries(
|
|
201
|
-
ACTION_MATRIX_ADAPTERS.map((adapter) => [
|
|
202
|
-
adapter,
|
|
203
|
-
entry.adapters.has(adapter) ? "available" : "unavailable"
|
|
204
|
-
])
|
|
205
|
-
),
|
|
206
|
-
satisfyingAdapters
|
|
207
|
-
};
|
|
208
|
-
}).sort((left, right) => left.name.localeCompare(right.name));
|
|
209
|
-
}
|
|
210
|
-
function resolveActionCapabilityRefusal(action, adapter, matrix) {
|
|
211
|
-
const matches = fuzzyResolveActions(matrix, action);
|
|
212
|
-
if (matches.length !== 1 || matches[0].satisfyingAdapters.includes(adapter)) {
|
|
213
|
-
return void 0;
|
|
214
|
-
}
|
|
215
|
-
return {
|
|
216
|
-
capability: matches[0].name,
|
|
217
|
-
satisfyingAdapters: matches[0].satisfyingAdapters
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
function missingActionCapabilities(adapter, actionNames, matrix) {
|
|
221
|
-
return [...new Set(actionNames)].flatMap((action) => {
|
|
222
|
-
const row = matrix.find((entry) => entry.name === action);
|
|
223
|
-
if (!row || row.satisfyingAdapters.includes(adapter)) return [];
|
|
224
|
-
return [{ capability: row.name, satisfyingAdapters: row.satisfyingAdapters }];
|
|
225
|
-
});
|
|
190
|
+
return actionCapabilityMatrix(catalogs);
|
|
226
191
|
}
|
|
227
192
|
function handleActionMatrixError(json, code, message, userAction, details = {}) {
|
|
228
193
|
if (json) {
|
|
@@ -315,32 +280,6 @@ function renderHumanActionMatrix(actions) {
|
|
|
315
280
|
)
|
|
316
281
|
].join("\n");
|
|
317
282
|
}
|
|
318
|
-
function fuzzyResolveActions(entries, query) {
|
|
319
|
-
const exactFull = entries.filter((e) => e.name === query);
|
|
320
|
-
if (exactFull.length > 0) return exactFull;
|
|
321
|
-
const finalSegment2 = (name) => name.split(".").pop() ?? name;
|
|
322
|
-
const exactSegment = entries.filter((e) => finalSegment2(e.name) === query);
|
|
323
|
-
if (exactSegment.length > 0) return exactSegment;
|
|
324
|
-
return entries.filter((e) => finalSegment2(e.name).includes(query));
|
|
325
|
-
}
|
|
326
|
-
function searchActions(entries, query) {
|
|
327
|
-
const terms = searchTerms(query);
|
|
328
|
-
if (terms.length === 0) return [];
|
|
329
|
-
return entries.map((entry) => {
|
|
330
|
-
const scores = terms.map((term) => actionSearchScore(entry, term));
|
|
331
|
-
return { entry, scores, score: scores.reduce((total, value) => total + value, 0) };
|
|
332
|
-
}).filter(({ score, scores }) => score > 0 && scores.every((value) => value > 0)).sort((left, right) => right.score - left.score || left.entry.name.localeCompare(right.entry.name)).map(({ entry }) => entry);
|
|
333
|
-
}
|
|
334
|
-
function findRelatedActions(entries, selected, limit = 5) {
|
|
335
|
-
const selectedNameTerms = searchTerms(finalSegment(selected.name).replaceAll("_", " "));
|
|
336
|
-
return entries.filter((entry) => entry.name !== selected.name).map((entry) => {
|
|
337
|
-
const nameTerms = new Set(searchTerms(finalSegment(entry.name).replaceAll("_", " ")));
|
|
338
|
-
const sharedScore = selectedNameTerms.filter((term) => nameTerms.has(term)).reduce((score2, term) => score2 + (GENERIC_OPERATION_TERMS.has(term) ? 5 : 30), 0);
|
|
339
|
-
const score = (entry.category === selected.category ? 100 : 0) + sharedScore;
|
|
340
|
-
return { name: entry.name, score };
|
|
341
|
-
}).filter(({ score }) => score > 0).sort((left, right) => right.score - left.score || left.name.localeCompare(right.name)).slice(0, limit).map(({ name }) => name);
|
|
342
|
-
}
|
|
343
|
-
const GENERIC_OPERATION_TERMS = /* @__PURE__ */ new Set(["assert", "call", "close", "ensure", "place", "read", "start", "teardown"]);
|
|
344
283
|
function summarizeDescribedAction(entry) {
|
|
345
284
|
const { schema: _schema, examples: _examples, ...summary } = entry;
|
|
346
285
|
return summary;
|
|
@@ -496,11 +435,6 @@ function actionDomainGroup(entry) {
|
|
|
496
435
|
const match = /^metamask[.]([^.]+)[.]/u.exec(entry.name);
|
|
497
436
|
return match ? `metamask \xB7 ${match[1]}` : "custom";
|
|
498
437
|
}
|
|
499
|
-
function summarizeActionCategories(actions) {
|
|
500
|
-
const counts = /* @__PURE__ */ new Map();
|
|
501
|
-
for (const action of actions) counts.set(action.category, (counts.get(action.category) ?? 0) + 1);
|
|
502
|
-
return [...counts.entries()].map(([name, count]) => ({ name, count })).sort((left, right) => left.name.localeCompare(right.name));
|
|
503
|
-
}
|
|
504
438
|
function describeManifestActions(manifest, actionSources = /* @__PURE__ */ new Map()) {
|
|
505
439
|
const manifestRecord = isRecord(manifest) ? manifest : {};
|
|
506
440
|
const actions = isRecord(manifestRecord.actions) ? manifestRecord.actions : {};
|
|
@@ -521,7 +455,7 @@ function describeManifestAction(name, kind, metadata, source) {
|
|
|
521
455
|
return {
|
|
522
456
|
name,
|
|
523
457
|
kind,
|
|
524
|
-
category: actionCategory(name),
|
|
458
|
+
category: actionCategory(name, ["metamask"]),
|
|
525
459
|
description: typeof record.description === "string" ? record.description : "",
|
|
526
460
|
fields: properties,
|
|
527
461
|
schema,
|
|
@@ -539,72 +473,16 @@ function describeManifestAction(name, kind, metadata, source) {
|
|
|
539
473
|
])]
|
|
540
474
|
};
|
|
541
475
|
}
|
|
542
|
-
function actionCategory(name) {
|
|
543
|
-
const segments = name.split(".");
|
|
544
|
-
if (segments[0] === "metamask" && segments.length > 2) return segments[1] ?? "metamask";
|
|
545
|
-
if (segments[0] === "app" || segments[0] === "cdp") return "runtime";
|
|
546
|
-
if (segments[0] === "ui") return "ui";
|
|
547
|
-
if (name.startsWith("assert_")) return "assertion";
|
|
548
|
-
if (name === "watch_logs" || name === "index_artifacts") return "evidence";
|
|
549
|
-
if (name === "command" || name === "wait" || name === "call" || name === "end") return "control";
|
|
550
|
-
return segments.length > 1 ? segments[0] || "utility" : "utility";
|
|
551
|
-
}
|
|
552
|
-
function actionSearchScore(entry, term) {
|
|
553
|
-
const name = entry.name.toLowerCase();
|
|
554
|
-
const segment = finalSegment(name);
|
|
555
|
-
const nameTerms = searchTerms(name.replaceAll(".", " ").replaceAll("_", " "));
|
|
556
|
-
const fields = entry.fields.map((field) => field.toLowerCase());
|
|
557
|
-
const description = entry.description.toLowerCase();
|
|
558
|
-
if (name === term) return 120;
|
|
559
|
-
if (segment === term) return 110;
|
|
560
|
-
if (entry.category === term) return 100;
|
|
561
|
-
if (fields.includes(term)) return 90;
|
|
562
|
-
if (nameTerms.includes(term)) return 80;
|
|
563
|
-
if (name.includes(term)) return 70;
|
|
564
|
-
if (fields.some((field) => field.includes(term))) return 60;
|
|
565
|
-
if (description.includes(term)) return 50;
|
|
566
|
-
if ([...nameTerms, ...fields].some((candidate) => fuzzyTermMatch(term, candidate))) return 40;
|
|
567
|
-
return 0;
|
|
568
|
-
}
|
|
569
|
-
function fuzzyTermMatch(term, candidate) {
|
|
570
|
-
if (term.length < 4 || candidate.length < 4) return false;
|
|
571
|
-
const threshold = Math.max(1, Math.floor(Math.max(term.length, candidate.length) / 4));
|
|
572
|
-
return levenshtein(term, candidate) <= threshold;
|
|
573
|
-
}
|
|
574
|
-
function searchTerms(value) {
|
|
575
|
-
return value.toLowerCase().match(/[a-z0-9]+/gu) ?? [];
|
|
576
|
-
}
|
|
577
|
-
function finalSegment(name) {
|
|
578
|
-
return name.split(".").pop() ?? name;
|
|
579
|
-
}
|
|
580
|
-
function levenshtein(left, right) {
|
|
581
|
-
const prior = Array.from({ length: right.length + 1 }, (_, index) => index);
|
|
582
|
-
for (let leftIndex = 1; leftIndex <= left.length; leftIndex += 1) {
|
|
583
|
-
const current = [leftIndex];
|
|
584
|
-
for (let rightIndex = 1; rightIndex <= right.length; rightIndex += 1) {
|
|
585
|
-
const substitution = (prior[rightIndex - 1] ?? 0) + (left[leftIndex - 1] === right[rightIndex - 1] ? 0 : 1);
|
|
586
|
-
current[rightIndex] = Math.min((current[rightIndex - 1] ?? 0) + 1, (prior[rightIndex] ?? 0) + 1, substitution);
|
|
587
|
-
}
|
|
588
|
-
for (let index = 0; index < current.length; index += 1) prior[index] = current[index] ?? 0;
|
|
589
|
-
}
|
|
590
|
-
return prior[right.length] ?? right.length;
|
|
591
|
-
}
|
|
592
476
|
export {
|
|
593
477
|
ACTION_MATRIX_ADAPTERS,
|
|
594
478
|
actionDomain,
|
|
595
479
|
actionExampleCommand,
|
|
596
480
|
actionLibraryContextArgs,
|
|
597
481
|
describeManifestActions,
|
|
598
|
-
findRelatedActions,
|
|
599
|
-
fuzzyResolveActions,
|
|
600
482
|
handleActions,
|
|
601
|
-
missingActionCapabilities,
|
|
602
483
|
renderHumanActionCatalog,
|
|
603
484
|
renderHumanActionExample,
|
|
604
485
|
renderHumanActionMatrix,
|
|
605
486
|
resolveActionCapabilityMatrix,
|
|
606
|
-
resolveActionCapabilityRefusal,
|
|
607
|
-
searchActions,
|
|
608
|
-
summarizeActionCategories,
|
|
609
487
|
summarizeDescribedAction
|
|
610
488
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { detectAdapter } from "../harness.js";
|
|
3
|
-
import {
|
|
3
|
+
import { adapterRegistry } from "../adapters/registry.js";
|
|
4
|
+
import { assertAdapter } from "../paths.js";
|
|
4
5
|
import { explicitRecipeTrustOptions } from "../recipe-security.js";
|
|
5
6
|
import { ADAPTER_DETECT_NEXT } from "./shared.js";
|
|
6
7
|
import { EXIT } from "./shared.js";
|
|
@@ -179,12 +180,16 @@ function requiredOption(options, key, message) {
|
|
|
179
180
|
if (!value) throw usageError(message);
|
|
180
181
|
return value;
|
|
181
182
|
}
|
|
183
|
+
function adapterUsageError(error) {
|
|
184
|
+
if (error instanceof Error && error.message.startsWith("internal:")) return error;
|
|
185
|
+
return usageError(error instanceof Error ? error.message : String(error));
|
|
186
|
+
}
|
|
182
187
|
function adapterOption(options) {
|
|
183
188
|
const adapter = optionString(options, "adapter");
|
|
184
189
|
try {
|
|
185
190
|
assertAdapter(adapter);
|
|
186
191
|
} catch (error) {
|
|
187
|
-
throw
|
|
192
|
+
throw adapterUsageError(error);
|
|
188
193
|
}
|
|
189
194
|
return adapter;
|
|
190
195
|
}
|
|
@@ -193,7 +198,7 @@ function targetPath(options) {
|
|
|
193
198
|
}
|
|
194
199
|
function actionManifestPathOption(options, adapter) {
|
|
195
200
|
const configured = optionString(options, "actionManifest");
|
|
196
|
-
return configured ? path.resolve(configured) :
|
|
201
|
+
return configured ? path.resolve(configured) : adapterRegistry.get(adapter).actions.manifestPath();
|
|
197
202
|
}
|
|
198
203
|
function parsePort(value, errorMessage) {
|
|
199
204
|
const port = Number(value);
|
|
@@ -238,7 +243,7 @@ function resolveAdapter(options) {
|
|
|
238
243
|
try {
|
|
239
244
|
assertAdapter(adapter);
|
|
240
245
|
} catch (error) {
|
|
241
|
-
throw
|
|
246
|
+
throw adapterUsageError(error);
|
|
242
247
|
}
|
|
243
248
|
return { adapter, target };
|
|
244
249
|
}
|
package/dist/commands/prepare.js
CHANGED
|
@@ -10,7 +10,9 @@ import {
|
|
|
10
10
|
import { color, stripAnsi } from "../cli-color.js";
|
|
11
11
|
import { runnerProvenance } from "../doctor.js";
|
|
12
12
|
import { detectAdapter } from "../harness.js";
|
|
13
|
-
import {
|
|
13
|
+
import { adapterRegistry } from "../adapters/registry.js";
|
|
14
|
+
import { getAdapterSurface } from "../adapters/surface.js";
|
|
15
|
+
import { harnessExecutable, PREPARE_PROGRESS_ARTIFACT, recipeRuntimeDir } from "../paths.js";
|
|
14
16
|
import { optionFlag, optionString, parseArgs, targetPath, usageError } from "./parse-args.js";
|
|
15
17
|
import { EXIT } from "./shared.js";
|
|
16
18
|
import { formatDuration } from "./status-watch.js";
|
|
@@ -52,8 +54,8 @@ const STEP_HINT = {
|
|
|
52
54
|
launch: "can take minutes on Mobile: Metro bundle and app start",
|
|
53
55
|
fixtures: "under a minute"
|
|
54
56
|
};
|
|
55
|
-
function stepChain(
|
|
56
|
-
return
|
|
57
|
+
function stepChain(surface) {
|
|
58
|
+
return surface.headless ? ["doctor", "status", "verify"] : ["doctor", "status", "launch", "fixtures", "verify"];
|
|
57
59
|
}
|
|
58
60
|
function lastRunDurations(recordPath) {
|
|
59
61
|
let parsed;
|
|
@@ -225,36 +227,6 @@ function readDevices(artifactsDir) {
|
|
|
225
227
|
const devices = parsed?.devices;
|
|
226
228
|
return Array.isArray(devices) ? devices : [];
|
|
227
229
|
}
|
|
228
|
-
function readRuntimeContextTarget(target) {
|
|
229
|
-
const file = path.join(target, recipeRuntimeDir(), "agentic-runtime.json");
|
|
230
|
-
let parsed;
|
|
231
|
-
try {
|
|
232
|
-
parsed = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
233
|
-
} catch {
|
|
234
|
-
return {};
|
|
235
|
-
}
|
|
236
|
-
const record = parsed ?? {};
|
|
237
|
-
const str = (key) => typeof record[key] === "string" ? record[key] : void 0;
|
|
238
|
-
return { simulator: str("simulator"), simulatorUdid: str("simulatorUdid"), adbSerial: str("adbSerial") };
|
|
239
|
-
}
|
|
240
|
-
function mobilePlatformFromConfiguredTarget(env, context) {
|
|
241
|
-
const ios = Boolean(env.IOS_SIMULATOR || env.SIM_UDID || context.simulator || context.simulatorUdid);
|
|
242
|
-
const android = Boolean(
|
|
243
|
-
env.ADB_SERIAL || env.ANDROID_SERIAL || env.ANDROID_TARGET_DEVICE_NAME || env.ANDROID_DEVICE || context.adbSerial
|
|
244
|
-
);
|
|
245
|
-
if (ios === android) return void 0;
|
|
246
|
-
return ios ? "ios" : "android";
|
|
247
|
-
}
|
|
248
|
-
function mobilePlatformFromDevices(devices) {
|
|
249
|
-
const selected = devices.filter((device) => device?.selected === true);
|
|
250
|
-
const candidates = selected.length > 0 ? selected : devices;
|
|
251
|
-
const platforms = [
|
|
252
|
-
...new Set(
|
|
253
|
-
candidates.map((device) => device?.platform).filter((value) => value === "ios" || value === "android")
|
|
254
|
-
)
|
|
255
|
-
];
|
|
256
|
-
return platforms.length === 1 ? platforms[0] : void 0;
|
|
257
|
-
}
|
|
258
230
|
function mobileTargetAmbiguityError() {
|
|
259
231
|
const choices = [
|
|
260
232
|
"1. iOS mm-harness prepare --platform mobile --mobile-platform ios",
|
|
@@ -300,8 +272,9 @@ function mobilePlatformOption(value) {
|
|
|
300
272
|
}
|
|
301
273
|
function resolvePlatform(requested, target) {
|
|
302
274
|
const platform = requested ?? detectAdapter(target);
|
|
303
|
-
|
|
304
|
-
|
|
275
|
+
const surface = platform && adapterRegistry.has(platform) ? getAdapterSurface(platform) : void 0;
|
|
276
|
+
if (surface?.prepare) {
|
|
277
|
+
return surface;
|
|
305
278
|
}
|
|
306
279
|
throw usageError(
|
|
307
280
|
`could not detect the platform of ${target}
|
|
@@ -328,21 +301,22 @@ async function handlePrepareLocked(argv) {
|
|
|
328
301
|
const { options } = parseArgs(argv, "prepare");
|
|
329
302
|
const json = optionFlag(options, "json");
|
|
330
303
|
const target = targetPath(options);
|
|
331
|
-
const
|
|
304
|
+
const surface = resolvePlatform(optionString(options, "platform"), target);
|
|
305
|
+
const platform = surface.adapter;
|
|
332
306
|
const artifactsDir = path.resolve(
|
|
333
307
|
optionString(options, "artifactsDir") ?? path.join(target, recipeRuntimeDir())
|
|
334
308
|
);
|
|
335
309
|
const executable = harnessExecutable();
|
|
336
310
|
const targetArgs = ["--target", target];
|
|
337
311
|
const clearMetro = optionFlag(options, "clearMetro");
|
|
338
|
-
if (clearMetro &&
|
|
312
|
+
if (clearMetro && !surface.prepare.clearMetro) {
|
|
339
313
|
throw usageError(`--clear-metro is mobile only; this checkout is ${platform}
|
|
340
314
|
Next: ${USAGE}`);
|
|
341
315
|
}
|
|
342
316
|
const remoteFlag = optionString(options, "remoteFlag");
|
|
343
317
|
if (remoteFlag !== void 0) {
|
|
344
|
-
if (
|
|
345
|
-
throw usageError(`--remote-flag needs an app runtime; this checkout is
|
|
318
|
+
if (surface.headless) {
|
|
319
|
+
throw usageError(`--remote-flag needs an app runtime; this checkout is ${platform}
|
|
346
320
|
Next: ${USAGE}`);
|
|
347
321
|
}
|
|
348
322
|
try {
|
|
@@ -357,7 +331,7 @@ async function handlePrepareLocked(argv) {
|
|
|
357
331
|
let mobilePlatform = mobilePlatformOption(optionString(options, "mobilePlatform"));
|
|
358
332
|
const recordPath = path.join(artifactsDir, READINESS_RECORD);
|
|
359
333
|
const progressPath = path.join(artifactsDir, PREPARE_PROGRESS_ARTIFACT);
|
|
360
|
-
const chain = stepChain(
|
|
334
|
+
const chain = stepChain(surface);
|
|
361
335
|
const view = json ? void 0 : createStepView(chain, stepHints(chain, lastRunDurations(recordPath)));
|
|
362
336
|
view?.begin();
|
|
363
337
|
const steps = [];
|
|
@@ -406,35 +380,33 @@ async function handlePrepareLocked(argv) {
|
|
|
406
380
|
try {
|
|
407
381
|
await run("doctor", ["doctor", "--fix", "--json", "--adapter", platform, ...targetArgs]);
|
|
408
382
|
await run("status", ["status", "--json", ...targetArgs]);
|
|
409
|
-
|
|
410
|
-
|
|
383
|
+
const { devicePlatform } = surface.prepare;
|
|
384
|
+
if (devicePlatform && !mobilePlatform && !failed()) {
|
|
385
|
+
mobilePlatform = devicePlatform(target, readDevices(artifactsDir));
|
|
411
386
|
if (!mobilePlatform) return ambiguousMobileTarget(json);
|
|
412
387
|
}
|
|
413
|
-
if (
|
|
414
|
-
const reason =
|
|
388
|
+
if (surface.headless) {
|
|
389
|
+
const reason = `${platform} is headless: no app surface to launch or seed`;
|
|
415
390
|
addStep(skippedStep("launch", `${executable} launch`, reason));
|
|
416
391
|
addStep(skippedStep("fixtures", `${executable} fixtures set`, reason));
|
|
417
|
-
} else
|
|
418
|
-
const device = mobilePlatform ? [mobilePlatform] : [];
|
|
392
|
+
} else {
|
|
393
|
+
const device = devicePlatform && mobilePlatform ? [mobilePlatform] : [];
|
|
419
394
|
await run("launch", ["launch", ...device, ...clearMetro ? ["--clear-metro"] : [], ...remoteFlagArgs, "--verify", "--json", ...targetArgs]);
|
|
420
395
|
await run("fixtures", [
|
|
421
396
|
"fixtures",
|
|
422
397
|
"set",
|
|
423
|
-
...
|
|
398
|
+
...device.flatMap((platformName) => ["--platform", platformName]),
|
|
424
399
|
...remoteFlagArgs,
|
|
425
400
|
"--json",
|
|
426
401
|
...targetArgs
|
|
427
402
|
]);
|
|
428
|
-
} else {
|
|
429
|
-
await run("launch", ["launch", ...remoteFlagArgs, "--verify", "--json", ...targetArgs]);
|
|
430
|
-
await run("fixtures", ["fixtures", "set", ...remoteFlagArgs, "--json", ...targetArgs]);
|
|
431
403
|
}
|
|
432
404
|
await run("verify", ["verify", "--json", "--adapter", platform, ...targetArgs]);
|
|
433
405
|
} finally {
|
|
434
406
|
clearInterval(heartbeat);
|
|
435
407
|
view?.end();
|
|
436
408
|
}
|
|
437
|
-
const provenance = runnerProvenance(manifestPath(
|
|
409
|
+
const provenance = runnerProvenance(surface.actions.manifestPath());
|
|
438
410
|
const record = {
|
|
439
411
|
schemaVersion: 1,
|
|
440
412
|
harness: {
|
|
@@ -462,7 +434,5 @@ async function handlePrepareLocked(argv) {
|
|
|
462
434
|
}
|
|
463
435
|
export {
|
|
464
436
|
handlePrepare,
|
|
465
|
-
mobilePlatformFromConfiguredTarget,
|
|
466
|
-
mobilePlatformFromDevices,
|
|
467
437
|
mobileTargetAmbiguityError
|
|
468
438
|
};
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { detectAdapter } from "../harness.js";
|
|
2
2
|
import { assertAdapter } from "../paths.js";
|
|
3
3
|
import { getAdapterSurface } from "../adapters/surface.js";
|
|
4
|
-
import {
|
|
5
|
-
import { ADAPTER_DETECT_NEXT, usageOut, writeInteractiveProgress } from "./shared.js";
|
|
4
|
+
import { ADAPTER_DETECT_NEXT, usageOut } from "./shared.js";
|
|
6
5
|
import {
|
|
7
6
|
applyWatcherPortOption,
|
|
8
7
|
optionFlag,
|
|
@@ -27,10 +26,7 @@ async function handleProvision({ positional, options, rawArgv }) {
|
|
|
27
26
|
const surface = getAdapterSurface(adapter);
|
|
28
27
|
const rerunCommand = provisionRerunCommand(rawArgv, options, adapter, target);
|
|
29
28
|
const platform = optionString(options, "platform") ?? optionString(options, "devicePlatform") ?? (positional[0] === "runway" ? positional[1] : positional[0]) ?? "ios";
|
|
30
|
-
|
|
31
|
-
writeInteractiveProgress(json, `\u2192 provision mobile ${platform} \u2014 resolving simulator and Runway artifact`);
|
|
32
|
-
if (!optionFlag(options, "resolveOnly")) clearMobileReleaseArtifactState(target);
|
|
33
|
-
}
|
|
29
|
+
surface.runwayProvision.before?.(target, { json, platform, resolveOnly: optionFlag(options, "resolveOnly") });
|
|
34
30
|
const result = await surface.runwayProvision.run(target, {
|
|
35
31
|
json,
|
|
36
32
|
platform,
|
|
@@ -50,7 +46,7 @@ async function handleProvision({ positional, options, rawArgv }) {
|
|
|
50
46
|
});
|
|
51
47
|
const provisionedSimulator = typeof result.simulator === "object" && result.simulator ? result.simulator : void 0;
|
|
52
48
|
const provisionedDevice = typeof provisionedSimulator?.udid === "string" ? provisionedSimulator.udid : typeof provisionedSimulator?.name === "string" ? provisionedSimulator.name : void 0;
|
|
53
|
-
const next = result.status === "pass" &&
|
|
49
|
+
const next = result.status === "pass" && surface.runwayProvision.launchNext && result.resolveOnly !== true ? `mm-harness launch ${String(result.platform ?? platform)} --adapter ${adapter} --target ${shellQuote(target)}` + (provisionedDevice ? ` --device ${shellQuoteArg(provisionedDevice)}` : "") : void 0;
|
|
54
50
|
const output = next ? { ...result, next } : result;
|
|
55
51
|
if (json) {
|
|
56
52
|
console.log(JSON.stringify(output, null, 2));
|
package/dist/commands/reload.js
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import { runnerDir } from "../paths.js";
|
|
3
1
|
import { getAdapterSurface } from "../adapters/surface.js";
|
|
4
2
|
import {
|
|
5
3
|
ADAPTER_DETECT_NEXT,
|
|
6
|
-
EXIT,
|
|
7
4
|
flag,
|
|
8
5
|
parseFlags,
|
|
9
6
|
resolveAdapter,
|
|
10
|
-
spawnScript,
|
|
11
7
|
targetOf,
|
|
12
8
|
usageOut
|
|
13
9
|
} from "./shared.js";
|
|
@@ -25,55 +21,20 @@ async function handleReload(argv) {
|
|
|
25
21
|
ADAPTER_DETECT_NEXT
|
|
26
22
|
);
|
|
27
23
|
}
|
|
28
|
-
|
|
24
|
+
const surface = getAdapterSurface(adapter);
|
|
25
|
+
if (surface.headless) {
|
|
29
26
|
return usageOut(
|
|
30
27
|
json,
|
|
31
28
|
"reload",
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
`${adapter} is headless; there is no app runtime to reload.`,
|
|
30
|
+
`mm-harness run <${adapter}-recipe>`
|
|
34
31
|
);
|
|
35
32
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (!process.env.CDP_PORT) {
|
|
39
|
-
return usageOut(
|
|
40
|
-
json,
|
|
41
|
-
"reload",
|
|
42
|
-
"the Extension CDP port could not be resolved.",
|
|
43
|
-
"mm-harness launch"
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
const script2 = path.join(runnerDir, "adapters/extension/reattach.sh");
|
|
47
|
-
const args2 = ["--target", target, "--cdp-port", process.env.CDP_PORT];
|
|
48
|
-
if (process.env.WATCHER_PORT) {
|
|
49
|
-
args2.push("--watcher-port", process.env.WATCHER_PORT);
|
|
50
|
-
}
|
|
51
|
-
const result2 = spawnScript(script2, args2, target, json);
|
|
52
|
-
if (json) {
|
|
53
|
-
console.log(
|
|
54
|
-
JSON.stringify(
|
|
55
|
-
{
|
|
56
|
-
ok: result2.status === 0,
|
|
57
|
-
adapter,
|
|
58
|
-
method: "cdp-reattach",
|
|
59
|
-
command: "reload",
|
|
60
|
-
cdpPort: Number(process.env.CDP_PORT),
|
|
61
|
-
...result2.status === 0 ? {} : { error: result2.output.slice(-4e3) || "Extension reload failed." }
|
|
62
|
-
},
|
|
63
|
-
null,
|
|
64
|
-
2
|
|
65
|
-
)
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
return result2.status === 0 ? EXIT.ok : EXIT.runtime;
|
|
33
|
+
if (!surface.reload) {
|
|
34
|
+
return usageOut(json, "reload", `reload does not support the ${adapter} adapter.`, surface.hints.launch);
|
|
69
35
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (process.env.WATCHER_PORT) args.push("--port", process.env.WATCHER_PORT);
|
|
73
|
-
if (json) args.push("--json");
|
|
74
|
-
const result = spawnScript(process.execPath, [script, ...args], target, json);
|
|
75
|
-
if (json) process.stdout.write(result.output);
|
|
76
|
-
return result.status === 0 ? EXIT.ok : EXIT.runtime;
|
|
36
|
+
surface.resolveSlotPorts(target);
|
|
37
|
+
return surface.reload(target, json);
|
|
77
38
|
}
|
|
78
39
|
export {
|
|
79
40
|
handleReload
|
package/dist/commands/run.js
CHANGED
|
@@ -38,11 +38,8 @@ import { formatRunDiagnosticsForHuman, readRunDiagnosticsDocument } from "../run
|
|
|
38
38
|
import { recipeTrustFailure } from "../recipe-security.js";
|
|
39
39
|
import { ProvenanceDriftError } from "../execution-provenance.js";
|
|
40
40
|
import { recordCommandEvidence } from "../command-journal.js";
|
|
41
|
-
import {
|
|
42
|
-
|
|
43
|
-
missingActionCapabilities,
|
|
44
|
-
resolveActionCapabilityMatrix
|
|
45
|
-
} from "./manifest.js";
|
|
41
|
+
import { actionLibraryContextArgs, resolveActionCapabilityMatrix } from "./manifest.js";
|
|
42
|
+
import { missingActionCapabilities } from "@farmslot/recipe-cli";
|
|
46
43
|
import {
|
|
47
44
|
startRunNetworkObservation
|
|
48
45
|
} from "../network-observation.js";
|
|
@@ -1,32 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { decideMobileReadiness } from "../adapters/mobile/runtime-decision.js";
|
|
1
|
+
import { getAdapterSurface } from "../adapters/surface.js";
|
|
3
2
|
import {
|
|
4
3
|
adapterOption,
|
|
5
4
|
applyRuntimeDirOption,
|
|
6
5
|
optionFlag,
|
|
7
|
-
optionString,
|
|
8
|
-
parsePort,
|
|
9
6
|
targetPath
|
|
10
7
|
} from "./parse-args.js";
|
|
11
8
|
async function handleRuntimeDecision({ options }) {
|
|
12
9
|
applyRuntimeDirOption(options);
|
|
13
10
|
const adapter = adapterOption(options);
|
|
14
11
|
const target = targetPath(options);
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const watcherPort = watcherPortRaw === void 0 ? void 0 : parsePort(watcherPortRaw, "runtime-decision --watcher-port must be a port.");
|
|
18
|
-
const report2 = await decideMobileReadiness(target, {
|
|
19
|
-
watcherPort,
|
|
20
|
-
metroLog: optionString(options, "metroLog"),
|
|
21
|
-
platform: optionString(options, "platform") ?? process.env.PLATFORM ?? process.env.RECIPE_HARNESS_PLATFORM,
|
|
22
|
-
record: optionFlag(options, "record"),
|
|
23
|
-
preflightMode: optionString(options, "preflightMode")
|
|
24
|
-
});
|
|
25
|
-
if (optionFlag(options, "json")) console.log(JSON.stringify(report2, null, 2));
|
|
26
|
-
else console.log(`${report2.decision} ${report2.reasonCode} \u2014 ${report2.reasons[0] ?? ""}`);
|
|
27
|
-
return 0;
|
|
28
|
-
}
|
|
29
|
-
if (adapter !== "extension") {
|
|
12
|
+
const decide = getAdapterSurface(adapter).runtimeDecision;
|
|
13
|
+
if (!decide) {
|
|
30
14
|
const report2 = {
|
|
31
15
|
schemaVersion: 1,
|
|
32
16
|
adapter,
|
|
@@ -42,15 +26,9 @@ async function handleRuntimeDecision({ options }) {
|
|
|
42
26
|
else console.log(`unknown adapter-unsupported \u2014 ${report2.reasons[0]}`);
|
|
43
27
|
return 0;
|
|
44
28
|
}
|
|
45
|
-
const
|
|
46
|
-
const cdpPort = cdpPortRaw === void 0 ? void 0 : parsePort(cdpPortRaw, "runtime-decision --cdp-port must be a port.");
|
|
47
|
-
const report = await decideExtensionReadiness(target, {
|
|
48
|
-
cdpPort,
|
|
49
|
-
watchLog: optionString(options, "watchLog"),
|
|
50
|
-
record: optionFlag(options, "record")
|
|
51
|
-
});
|
|
29
|
+
const { report, line } = await decide(target, options);
|
|
52
30
|
if (optionFlag(options, "json")) console.log(JSON.stringify(report, null, 2));
|
|
53
|
-
else console.log(
|
|
31
|
+
else console.log(line);
|
|
54
32
|
return 0;
|
|
55
33
|
}
|
|
56
34
|
export {
|
|
@@ -25,9 +25,7 @@ async function runSelfTest(options) {
|
|
|
25
25
|
for (const adapter of ["mobile", "extension"]) {
|
|
26
26
|
const manifest = loadActionManifest(adapter);
|
|
27
27
|
const manifestValidation = await validateManifest(manifest);
|
|
28
|
-
const smokeRecipe = recipePath(
|
|
29
|
-
adapter === "mobile" ? "mobile/runner/smoke.recipe.json" : "extension/runner/smoke.recipe.json"
|
|
30
|
-
);
|
|
28
|
+
const smokeRecipe = recipePath(`${adapter}/runner/smoke.recipe.json`);
|
|
31
29
|
const artifactsDir = path.join(root, adapter);
|
|
32
30
|
const result = await runRecipe(adapter, smokeRecipe, artifactsDir, runnerDir, void 0, {
|
|
33
31
|
skipExtensionRuntimePrepare: true
|