@deeeed/metamask-harness 0.82.1 → 0.84.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.
Files changed (73) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/README.md +8 -1
  3. package/adapters/core/cleanup.sh +5 -29
  4. package/adapters/core/controller-import-probe.mjs +26 -0
  5. package/adapters/manifest.json +22 -102
  6. package/adapters/shared/session-lock.mjs +43 -0
  7. package/adapters/web-dapp/metamask-signer.mjs +212 -0
  8. package/dist/adapters/core/actions.js +0 -3
  9. package/dist/adapters/core/overlay.js +1 -5
  10. package/dist/adapters/core/readiness.js +133 -2
  11. package/dist/adapters/core/surface.js +53 -41
  12. package/dist/adapters/extension/extension-id.js +1 -0
  13. package/dist/adapters/extension/fixtures.js +60 -1
  14. package/dist/adapters/extension/network-observer.js +19 -206
  15. package/dist/adapters/extension/run.js +5 -3
  16. package/dist/adapters/extension/runtime.js +112 -20
  17. package/dist/adapters/mobile/android-gfxinfo-performance.js +3 -1
  18. package/dist/adapters/mobile/devices.js +5 -1
  19. package/dist/adapters/mobile/performance-observer.js +9 -6
  20. package/dist/adapters/mobile/run.js +1 -1
  21. package/dist/adapters/mobile/surface.js +1 -2
  22. package/dist/adapters/performance/trace-kinds.js +36 -0
  23. package/dist/adapters/plugin-surface.js +34 -34
  24. package/dist/adapters/session-lock.js +23 -0
  25. package/dist/adapters/web-dapp/console-capture.js +2 -20
  26. package/dist/adapters/web-dapp/run.js +31 -25
  27. package/dist/adapters/web-dapp/surface.js +32 -108
  28. package/dist/command-contract.js +1363 -630
  29. package/dist/commands/core-readiness.js +1 -9
  30. package/dist/commands/doctor.js +1 -1
  31. package/dist/commands/launch/device.js +1 -1
  32. package/dist/doctor.js +5 -2
  33. package/dist/mm-harness-cli.js +228 -1210
  34. package/dist/performance-observation.js +28 -5
  35. package/docs/CONTRIBUTING.md +6 -4
  36. package/docs/cli-contract.md +1 -1
  37. package/docs/feature-guard.md +1 -1
  38. package/library/actions/core/perps/_controller.mjs +10 -0
  39. package/library/actions/extension/wallet/import.mjs +21 -4
  40. package/library/actions/extension/wallet/visible-session.mjs +32 -12
  41. package/library/actions/shared/console-collector.mjs +1 -1
  42. package/library/actions/web-dapp/app/launch.mjs +1 -1
  43. package/library/actions/web-dapp/platform/adapter-web-lib.mjs +18 -0
  44. package/library/actions/web-dapp/platform/hud.mjs +1 -1
  45. package/library/actions/web-dapp/platform/page.mjs +2 -3
  46. package/library/actions/web-dapp/platform/runtime.mjs +29 -178
  47. package/package.json +7 -6
  48. package/scripts/completions.sh +1 -1
  49. package/adapters/web-dapp/cleanup.mjs +0 -69
  50. package/adapters/web-dapp/inject.mjs +0 -75
  51. package/adapters/web-dapp/launch.mjs +0 -631
  52. package/adapters/web-dapp/lib/blocked-hosts.mjs +0 -24
  53. package/adapters/web-dapp/lib/cdp-client.mjs +0 -84
  54. package/adapters/web-dapp/lib/display.mjs +0 -115
  55. package/adapters/web-dapp/lib/hud.mjs +0 -148
  56. package/adapters/web-dapp/lib/policy.mjs +0 -79
  57. package/adapters/web-dapp/lib/processes.mjs +0 -187
  58. package/adapters/web-dapp/lib/readiness.mjs +0 -272
  59. package/adapters/web-dapp/stop.mjs +0 -38
  60. package/adapters/web-dapp/verify.mjs +0 -51
  61. package/adapters/web-dapp/wallet-host.mjs +0 -520
  62. package/dist/adapters/core/live-script-env.js +0 -36
  63. package/dist/adapters/extension/browser-cdp.js +0 -174
  64. package/dist/adapters/extension/performance-observer.js +0 -75
  65. package/dist/adapters/performance/cdp-trace.js +0 -406
  66. package/dist/adapters/performance/js-task-metrics.js +0 -35
  67. package/dist/adapters/web-dapp/doctor-checks.js +0 -33
  68. package/dist/adapters/web-dapp/launch.js +0 -87
  69. package/dist/adapters/web-dapp/overlay.js +0 -21
  70. package/dist/adapters/web-dapp/readiness.js +0 -10
  71. package/dist/adapters/web-dapp/stop.js +0 -20
  72. package/dist/async.js +0 -19
  73. package/dist/cli.js +0 -202
@@ -1,33 +0,0 @@
1
- import { POLICY_ENV, webDappPolicy } from "../../../adapters/web-dapp/lib/policy.mjs";
2
- import { webDappAppPort, webDappCdpPort } from "./surface.js";
3
- async function webDappDoctorChecks(target) {
4
- if (!process.env[POLICY_ENV]) {
5
- return [{
6
- id: "web-dapp-venue-policy",
7
- status: "fail",
8
- required: true,
9
- message: "web-dapp needs a venue policy: select an adapter that extends it (e.g. --adapter terminal with its recipe library on RECIPE_LIBRARY_PATH)."
10
- }];
11
- }
12
- const policy = webDappPolicy();
13
- const { webDappReadiness } = await import("../../../adapters/web-dapp/lib/readiness.mjs");
14
- const { resolveBrowser } = await import("../../../adapters/web-dapp/launch.mjs");
15
- const report = await webDappReadiness({
16
- policy,
17
- target,
18
- appPort: webDappAppPort(),
19
- cdpPort: webDappCdpPort(),
20
- signer: process.env.TERMINAL_SIGNER || "extension",
21
- account: process.env.TERMINAL_ACCOUNT || "dev1",
22
- probeBrowser: resolveBrowser
23
- });
24
- return report.checks.map((check) => ({
25
- id: `${policy.adapterId}-${check.id}`,
26
- status: check.status === "pass" ? "pass" : "fail",
27
- required: check.required,
28
- message: check.detail
29
- }));
30
- }
31
- export {
32
- webDappDoctorChecks
33
- };
@@ -1,87 +0,0 @@
1
- import { flag, str } from "@farmslot/recipe-cli/harness";
2
- import { execFileSync } from "node:child_process";
3
- import path from "node:path";
4
- import { runnerDir } from "../../paths.js";
5
- import { webDappAdapterId } from "../../../adapters/web-dapp/lib/policy.mjs";
6
- const WEB_DAPP_ONLY_LAUNCH_OPTIONS = Object.freeze({
7
- signer: "--signer",
8
- account: "--account",
9
- freshProfile: "--fresh-profile",
10
- headless: "--headless",
11
- headful: "--headful",
12
- slowMo: "--slow-mo",
13
- network: "--network",
14
- mainnetConfirmation: "--mainnet-confirmation"
15
- });
16
- function webDappOnlyLaunchFlags(options) {
17
- return Object.entries(WEB_DAPP_ONLY_LAUNCH_OPTIONS).filter(([key]) => options[key] !== void 0 && options[key] !== false).map(([, name]) => name);
18
- }
19
- function parseWebDappLauncherOutput(output) {
20
- const text = output.trim();
21
- if (!text) return { status: "fail", error: `the ${webDappAdapterId()} launcher printed no summary` };
22
- try {
23
- return JSON.parse(text);
24
- } catch {
25
- const last = text.split("\n").filter((line) => line.trim()).pop() ?? "";
26
- try {
27
- return JSON.parse(last);
28
- } catch {
29
- return { status: "fail", error: `the ${webDappAdapterId()} launcher printed no JSON summary: ${last.slice(0, 200)}` };
30
- }
31
- }
32
- }
33
- async function launchWebDapp({ options, target, jsonOutput, stream, usage }) {
34
- const id = webDappAdapterId();
35
- const cdpPort = str(options, "cdpPort") ?? process.env.RECIPE_CDP_PORT ?? process.env.CDP_PORT;
36
- const appPort = str(options, "watcherPort") ?? process.env.TERMINAL_APP_PORT ?? process.env.RECIPE_WATCHER_PORT ?? process.env.WATCHER_PORT;
37
- if (!cdpPort || !appPort) {
38
- return usage(
39
- `${id} launch needs the slot browser CDP port and the slot dev-server port.`,
40
- `mm-harness launch --adapter ${id} --cdp-port <port> --watcher-port <app port>`
41
- );
42
- }
43
- const args = [
44
- path.join(runnerDir, "adapters", "web-dapp", "launch.mjs"),
45
- "--target",
46
- target,
47
- "--cdp-port",
48
- cdpPort,
49
- "--app-port",
50
- appPort,
51
- "--signer",
52
- str(options, "signer") ?? process.env.TERMINAL_SIGNER ?? "extension",
53
- "--account",
54
- str(options, "account") ?? process.env.TERMINAL_ACCOUNT ?? "dev1",
55
- "--json"
56
- ];
57
- if (flag(options, "freshProfile")) args.push("--fresh-profile");
58
- if (flag(options, "headless")) args.push("--headless");
59
- if (flag(options, "headful")) args.push("--headful");
60
- const slowMo = str(options, "slowMo");
61
- if (slowMo) args.push("--slow-mo", slowMo);
62
- const network = str(options, "network");
63
- if (network) args.push("--network", network);
64
- const mainnetConfirmation = str(options, "mainnetConfirmation");
65
- if (mainnetConfirmation) args.push("--mainnet-confirmation", mainnetConfirmation);
66
- stream.phase("launch", { adapter: id, cdpPort, appPort });
67
- let output = "";
68
- let status = 0;
69
- try {
70
- output = execFileSync(process.execPath, args, { encoding: "utf8", stdio: ["ignore", "pipe", "inherit"] });
71
- } catch (error) {
72
- status = 1;
73
- output = String(error.stdout ?? "");
74
- }
75
- const result = parseWebDappLauncherOutput(output);
76
- if (jsonOutput) console.log(JSON.stringify({ schemaVersion: 1, command: "launch", adapter: id, target, ...result }, null, 2));
77
- else if (result.status === "pass") console.log(`\u2713 ${id} browser ${result.reused ? "reused" : "started"} on CDP ${result.cdpPort} (${result.signer}, ${result.account?.name})`);
78
- else console.error(`\u2717 ${id} launch failed: ${result.error ?? "unknown error"}`);
79
- stream.complete(result.status === "pass" ? "pass" : "fail", status);
80
- return result.status === "pass" ? 0 : 1;
81
- }
82
- export {
83
- WEB_DAPP_ONLY_LAUNCH_OPTIONS,
84
- launchWebDapp,
85
- parseWebDappLauncherOutput,
86
- webDappOnlyLaunchFlags
87
- };
@@ -1,21 +0,0 @@
1
- import path from "node:path";
2
- import { runnerDir } from "../../paths.js";
3
- const webDappHarness = {
4
- install: { entry: "adapters/web-dapp/inject.mjs", fallback: "adapters/web-dapp/inject.mjs", node: true },
5
- cleanup: { entry: "adapters/web-dapp/cleanup.mjs", fallback: "adapters/web-dapp/cleanup.mjs", node: true },
6
- verify: () => ({
7
- command: process.execPath,
8
- prefixArgs: [path.join(runnerDir, "adapters", "web-dapp", "verify.mjs")]
9
- }),
10
- restart: "mm-harness launch"
11
- };
12
- const webDappRuntimeContext = {
13
- forbiddenFields: ["metroPort", "simulator", "simulatorUdid", "adbSerial", "extensionId"],
14
- resources: ({ existing, defaults, envPort }) => ({
15
- cdpPort: envPort("RECIPE_CDP_PORT", "CDP_PORT") ?? existing.cdpPort ?? defaults.cdpPort
16
- })
17
- };
18
- export {
19
- webDappHarness,
20
- webDappRuntimeContext
21
- };
@@ -1,10 +0,0 @@
1
- import { POLICY_ENV, webDappPolicy } from "../../../adapters/web-dapp/lib/policy.mjs";
2
- const webDappReadiness = {
3
- compatibilityMode: (target) => process.env[POLICY_ENV] && webDappPolicy().checkout.matches(target) ? "slot browser over CDP (website; no product bridge)" : "unsupported/no bridge",
4
- liveChecks: async (target) => (await import("./doctor-checks.js")).webDappDoctorChecks(target),
5
- // Screenshots come from the slot browser over CDP.
6
- captureProviders: ["cdp"]
7
- };
8
- export {
9
- webDappReadiness
10
- };
@@ -1,20 +0,0 @@
1
- import { spawnSync } from "node:child_process";
2
- import path from "node:path";
3
- import { runnerDir } from "../../paths.js";
4
- import { webDappAdapterId } from "../../../adapters/web-dapp/lib/policy.mjs";
5
- function stopWebDappBrowserSync(target) {
6
- const args = [path.join(runnerDir, "adapters", "web-dapp", "stop.mjs"), "--target", path.resolve(target)];
7
- const cdpPort = process.env.RECIPE_CDP_PORT ?? process.env.CDP_PORT;
8
- if (cdpPort) args.push("--cdp-port", cdpPort);
9
- const result = spawnSync(process.execPath, args, { encoding: "utf8" });
10
- const output = `${result.stdout ?? ""}${result.stderr ?? ""}`;
11
- const status = result.status ?? 1;
12
- return {
13
- status,
14
- summary: status === 0 ? `${webDappAdapterId()} browser and wallet host stopped` : `${webDappAdapterId()} browser stop failed`,
15
- output
16
- };
17
- }
18
- export {
19
- stopWebDappBrowserSync
20
- };
package/dist/async.js DELETED
@@ -1,19 +0,0 @@
1
- async function withTimeout(promise, options) {
2
- let timer;
3
- try {
4
- return await Promise.race([
5
- promise,
6
- new Promise((_resolve, reject) => {
7
- timer = setTimeout(() => {
8
- options.onTimeout?.();
9
- reject(new Error(options.message));
10
- }, options.timeoutMs);
11
- })
12
- ]);
13
- } finally {
14
- if (timer) clearTimeout(timer);
15
- }
16
- }
17
- export {
18
- withTimeout
19
- };
package/dist/cli.js DELETED
@@ -1,202 +0,0 @@
1
- import "./host.js";
2
- import "./adapters/surface.js";
3
- import fs from "node:fs";
4
- import {
5
- detectAdapter,
6
- handleActions,
7
- handleCall,
8
- handleHarness,
9
- handleLast,
10
- handleLaunch,
11
- handleReload,
12
- handleRun,
13
- handleStop,
14
- parseArgs,
15
- targetPath
16
- } from "@farmslot/recipe-cli/harness";
17
- import { stopProjectCollector } from "../library/actions/shared/analytics/collector.mjs";
18
- import { loadSelectedAdapter } from "./adapters/plugin-surface.js";
19
- import { adapterRegistry } from "./adapters/registry.js";
20
- import { applyDeviceTargeting } from "./commands/device-target.js";
21
- import { metaMaskEngine } from "./engine.js";
22
- import { walletFixturePath } from "./paths.js";
23
- import { handleDoctor } from "./commands/doctor.js";
24
- import { handleProvision } from "./commands/provision.js";
25
- import { handleRuntimeHealth } from "./commands/runtime-health.js";
26
- import { handleRuntimeLaunch } from "./commands/runtime-launch.js";
27
- import { handleRuntimeDecision } from "./commands/runtime-decision.js";
28
- import { handleResolveExtension } from "./commands/resolve-extension.js";
29
- import { handleEnsureReady } from "./commands/ensure-ready.js";
30
- import { handleSelfTest } from "./commands/self-test.js";
31
- import { handleCompletionCandidates, invalidateCompletionCache } from "./commands/completion-candidates.js";
32
- import { handleLogs } from "./commands/logs.js";
33
- import { handleDebug } from "./commands/debug.js";
34
- import { handleFixtures } from "./commands/fixtures.js";
35
- import { handleRecipeQuality } from "./commands/recipe-quality.js";
36
- import { handleStatus } from "./commands/status.js";
37
- import { handleCheck } from "./commands/check.js";
38
- import { handleChecklist } from "./commands/checklist.js";
39
- import { handleExecutionTemplate } from "./commands/execution-template.js";
40
- import { handlePrepare } from "./commands/prepare.js";
41
- import { handleTaskInit } from "./commands/task-init.js";
42
- import { handleReview } from "./commands/review.js";
43
- import { handleDomain } from "./commands/domain.js";
44
- import { handleConfig } from "./commands/config.js";
45
- import { handlePrBody } from "./commands/pr-body.js";
46
- import { overlayCommandOptions } from "./commands/overlay.js";
47
- const stopOptions = {
48
- companions: async (target) => {
49
- const collector = await stopProjectCollector(target);
50
- return collector.stopped && collector.pid !== null ? [{ label: "analytics collector", pid: collector.pid }] : [];
51
- }
52
- };
53
- const targetDevice = (command, adapter, options) => applyDeviceTargeting(command, adapter, options, { gate: true, rerun: "" });
54
- const runOptions = {
55
- engine: metaMaskEngine,
56
- targetDevice,
57
- plan: {
58
- // The wallet fixture `fixtures set` seeds.
59
- steps: (target) => [{
60
- step: "fixture.file",
61
- confidence: "static",
62
- status: "ok",
63
- detail: fs.existsSync(walletFixturePath(target)) ? `present: ${walletFixturePath(target)}` : `absent: ${walletFixturePath(target)} (fixtures set to seed)`
64
- }],
65
- launchDetail: "would launch/attach the app + heal transport (Metro/Chrome/CDP) before executing"
66
- }
67
- };
68
- const callOptions = {
69
- engine: metaMaskEngine,
70
- targetDevice,
71
- // A wallet action makes the most useful first example.
72
- exampleAction: (names) => names.find((name) => /wallet|unlock/u.test(name))
73
- };
74
- const COMMANDS = {
75
- actions: (parsed) => handleActions(parsed, { catalog: metaMaskEngine }),
76
- doctor: handleDoctor,
77
- "runtime-health": handleRuntimeHealth,
78
- "runtime-decision": handleRuntimeDecision,
79
- "runtime-launch": handleRuntimeLaunch,
80
- "resolve-extension": handleResolveExtension,
81
- "ensure-ready": handleEnsureReady,
82
- "self-test": handleSelfTest
83
- };
84
- const OVERLAY_COMMANDS = ["install", "verify", "cleanup"];
85
- function usage() {
86
- console.error(`mm-harness \u2014 the MetaMask recipe harness: launch the app, prove behavior, manage the runtime overlay.
87
- Run it from inside a MetaMask checkout; the platform (mobile | extension | core | terminal) is auto-detected.
88
- Grammar: mm-harness <command> [target] [flags] (target is a positional: ios | android | extension; flags add agent depth)
89
-
90
- DAILY LOOP \u2014 what a teammate runs many times a day:
91
- launch Launch the app incl. Metro/build, surface auto-detected. Mobile: ios|android required.
92
- mm-harness launch ios # quick relaunch (add --build | --verify)
93
- logs Tail Metro/webpack + app logs.
94
- mm-harness logs
95
- debug Open the debug console (extension DevTools / mobile RN).
96
- mm-harness debug
97
- reload Reload the connected Mobile or Extension runtime.
98
- mm-harness reload
99
- fixtures Sync files, set/reset the wallet, generate fixture-state, or finalize labels over CDP.
100
- mm-harness fixtures sync # or: set | reset | generate --fixture <f> --out <o> | finalize \u2026
101
-
102
- PROVE \u2014 run recipes and inspect capabilities:
103
- run Run a recipe and write evidence (summary/trace/artifacts).
104
- mm-harness run recipe.json
105
- doctor Readiness check for a checkout (no app launch).
106
- mm-harness doctor
107
- check Run bounded repo-local checks for an active diff.
108
- mm-harness check diff --profile fast --artifacts-dir artifacts/validation
109
- actions Describe the actions a manifest declares.
110
- mm-harness actions --adapter mobile
111
- RUNTIME OVERLAY \u2014 install/verify/clean the per-checkout runtime overlay:
112
- provision Install the cached Runway mobile dev client (artifact cache + sim install).
113
- mm-harness provision runway ios --adapter mobile
114
- install Install the runtime overlay into the checkout.
115
- mm-harness install # inside a checkout, auto-detected
116
- verify Check the overlay/runtime is present and healthy (no launch).
117
- mm-harness verify
118
- cleanup Remove the installed overlay and restore the checkout.
119
- mm-harness cleanup
120
-
121
- ADVANCED \u2014 internal runtime probes (rarely typed by hand):
122
- runtime-health runtime-decision runtime-launch
123
- resolve-extension ensure-ready self-test
124
- (e.g. mm-harness runtime-decision --adapter extension --target <repo> --json)
125
-
126
- ONE bin: mm-harness is the only command. No per-platform binaries \u2014 platform is
127
- auto-detected, the positional target forces it (mm-harness launch ios), and
128
- platform-specific needs are FLAGS on the same command (e.g. --sidebar, --full-build).
129
- Human happy path = the bare command; agents add depth via flags (--json, --target, ports).
130
- See README.md for the workflow and docs/RECIPES.md for proof authoring.
131
- `);
132
- }
133
- async function main(argv) {
134
- const command = argv[0];
135
- if (!command || command === "-h" || command === "--help") {
136
- usage();
137
- return command ? 0 : 2;
138
- }
139
- const refused = await loadSelectedAdapter(command, argv.slice(1));
140
- if (refused !== void 0) return refused;
141
- if (command === "status" || command === "health" || command === "home") {
142
- return handleStatus(parseArgs(argv.slice(1)));
143
- }
144
- if (command === "run") return handleRun(argv.slice(1), runOptions);
145
- if (command === "call") return handleCall(argv.slice(1), callOptions);
146
- if (command === "completion-candidates") return handleCompletionCandidates(argv.slice(1));
147
- if (command === "provision") return handleProvision(parseArgs(argv.slice(1)));
148
- if (OVERLAY_COMMANDS.includes(command)) {
149
- if (command === "verify" && process.env.MM_HARNESS_SKIP_OVERLAY_ENSURE !== "1") {
150
- try {
151
- const { options } = parseArgs(argv.slice(1));
152
- const target = targetPath(options);
153
- const adapterRaw = (typeof options["adapter"] === "string" ? options["adapter"] : void 0) ?? (typeof options["platform"] === "string" ? options["platform"] : void 0) ?? detectAdapter(target);
154
- if (adapterRaw && adapterRegistry.has(adapterRaw)) {
155
- await handleHarness(["install", "--platform", adapterRaw, "--target", target, "--quiet"]);
156
- }
157
- } catch {
158
- }
159
- }
160
- const code = await handleHarness(argv, overlayCommandOptions);
161
- if (code === 0 && command === "install") {
162
- try {
163
- invalidateCompletionCache(targetPath(parseArgs(argv.slice(1)).options));
164
- } catch {
165
- }
166
- }
167
- return code;
168
- }
169
- if (command === "launch") return handleLaunch(argv.slice(1));
170
- if (command === "stop") return handleStop(argv.slice(1), stopOptions);
171
- if (command === "logs") return handleLogs(argv.slice(1));
172
- if (command === "debug") return handleDebug(argv.slice(1));
173
- if (command === "reload") return handleReload(argv.slice(1));
174
- if (command === "fixtures") return handleFixtures(argv.slice(1));
175
- if (command === "recipe-quality") return handleRecipeQuality(argv.slice(1));
176
- if (command === "check") return handleCheck(argv.slice(1));
177
- if (command === "checklist") return handleChecklist(argv.slice(1));
178
- if (command === "execution-template") return handleExecutionTemplate(argv.slice(1));
179
- if (command === "task") return handleTaskInit(argv.slice(1));
180
- if (command === "prepare") return handlePrepare(argv.slice(1));
181
- if (command === "last") return handleLast(parseArgs(argv.slice(1)));
182
- if (command === "review") return handleReview(argv.slice(1));
183
- if (command === "domain") return handleDomain(argv.slice(1));
184
- if (command === "config") return handleConfig(argv.slice(1));
185
- if (command === "pr-body") return handlePrBody(argv.slice(1));
186
- const handler = COMMANDS[command];
187
- if (!handler) throw new Error(`Unknown command: ${command}`);
188
- return handler(parseArgs(argv.slice(1), command === "runtime-decision" ? { recordIsFlag: true } : {}));
189
- }
190
- if (!globalThis.__MM_HARNESS_WRAPPER__) {
191
- try {
192
- process.exit(await main(process.argv.slice(2)));
193
- } catch (error) {
194
- console.error(error instanceof Error ? error.message : String(error));
195
- process.exit(
196
- error !== null && typeof error === "object" && "exitCode" in error && typeof error.exitCode === "number" ? error.exitCode : 1
197
- );
198
- }
199
- }
200
- export {
201
- main
202
- };