appduct 0.10.0 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +122 -52
- package/dist/artifact-inspect.d.ts +0 -1
- package/dist/bin.d.ts +0 -1
- package/dist/bin.js +1250 -2
- package/dist/cli/command-options.d.ts +8 -1
- package/dist/cli/create-cli.d.ts +0 -1
- package/dist/cli/dispatch.d.ts +10 -1
- package/dist/cli/envelope.d.ts +19 -0
- package/dist/cli/global-flags.d.ts +30 -0
- package/dist/cli/open-target.d.ts +25 -8
- package/dist/cli/result-types.d.ts +31 -5
- package/dist/cli/router.d.ts +78 -0
- package/dist/cli/routes/daemon/index.d.ts +9 -0
- package/dist/cli/routes/daemon/index.js +848 -0
- package/dist/cli/routes/daemon/run.d.ts +3 -0
- package/dist/cli/routes/daemon/run.js +3246 -0
- package/dist/cli/routes/daemon/start.d.ts +3 -0
- package/dist/cli/routes/daemon/start.js +1677 -0
- package/dist/cli/routes/daemon/status.d.ts +4 -0
- package/dist/cli/routes/daemon/status.js +1685 -0
- package/dist/cli/routes/daemon/stop.d.ts +4 -0
- package/dist/cli/routes/daemon/stop.js +1655 -0
- package/dist/cli/routes/doctor.d.ts +3 -0
- package/dist/cli/routes/doctor.js +1075 -0
- package/dist/cli/routes/events.d.ts +3 -0
- package/dist/cli/routes/events.js +1915 -0
- package/dist/cli/routes/init.d.ts +3 -0
- package/dist/cli/routes/init.js +1486 -0
- package/dist/cli/routes/invoke.d.ts +3 -0
- package/dist/cli/routes/invoke.js +1893 -0
- package/dist/cli/routes/keygen.d.ts +3 -0
- package/dist/cli/routes/keygen.js +928 -0
- package/dist/cli/routes/link.d.ts +3 -0
- package/dist/cli/routes/link.js +2714 -0
- package/dist/cli/routes/ls.d.ts +3 -0
- package/dist/cli/routes/ls.js +1668 -0
- package/dist/cli/routes/mcp.d.ts +5 -0
- package/dist/cli/routes/mcp.js +3929 -0
- package/dist/cli/routes/revoke.d.ts +3 -0
- package/dist/cli/routes/revoke.js +1684 -0
- package/dist/cli/routes/tools.d.ts +3 -0
- package/dist/cli/routes/tools.js +1860 -0
- package/dist/cli/runner.d.ts +3 -11
- package/dist/cli/types.d.ts +9 -6
- package/dist/cli/version-guard.d.ts +20 -0
- package/dist/cli.d.ts +0 -1
- package/dist/client/app-client.d.ts +4 -4
- package/dist/client/bootstrap.d.ts +4 -4
- package/dist/client/connect.d.ts +0 -1
- package/dist/client/errors.d.ts +0 -1
- package/dist/client/index.d.ts +1 -2
- package/dist/client/index.js +2389 -16
- package/dist/commands/daemon/context.d.ts +14 -0
- package/dist/commands/daemon/run.d.ts +11 -0
- package/dist/commands/daemon/start.d.ts +5 -0
- package/dist/commands/daemon/status.d.ts +5 -0
- package/dist/commands/daemon/stop.d.ts +5 -0
- package/dist/commands/doctor.d.ts +0 -1
- package/dist/commands/events.d.ts +0 -1
- package/dist/commands/init.d.ts +17 -4
- package/dist/commands/invoke.d.ts +0 -1
- package/dist/commands/keygen.d.ts +0 -1
- package/dist/commands/link.d.ts +1 -2
- package/dist/commands/ls.d.ts +0 -1
- package/dist/commands/mcp.d.ts +2 -4
- package/dist/commands/revoke.d.ts +0 -1
- package/dist/commands/tools.d.ts +19 -3
- package/dist/daemon/address.d.ts +0 -1
- package/dist/daemon/audit.d.ts +6 -11
- package/dist/daemon/calls.d.ts +0 -1
- package/dist/daemon/config.d.ts +13 -16
- package/dist/daemon/daemon.d.ts +0 -1
- package/dist/daemon/event-bus.d.ts +0 -1
- package/dist/daemon/links.d.ts +0 -1
- package/dist/daemon/listener.d.ts +0 -1
- package/dist/daemon/log-rotation.d.ts +0 -1
- package/dist/daemon/pidfile.d.ts +14 -8
- package/dist/daemon/policy.d.ts +0 -1
- package/dist/daemon/registry.d.ts +0 -1
- package/dist/daemon/rpc-errors.d.ts +16 -0
- package/dist/daemon/rpc-server.d.ts +0 -11
- package/dist/daemon/sessions.d.ts +0 -1
- package/dist/daemon/socket-probe.d.ts +0 -1
- package/dist/daemon/state-dir.d.ts +0 -1
- package/dist/daemon/timers.d.ts +0 -1
- package/dist/daemon/tls.d.ts +0 -1
- package/dist/errors.d.ts +0 -1
- package/dist/host-certificate.d.ts +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +1307 -3
- package/dist/key-material.d.ts +0 -1
- package/dist/link.d.ts +8 -7
- package/dist/mcp/app-tools.d.ts +153 -0
- package/dist/mcp/connect-tool.d.ts +14 -8
- package/dist/mcp/daemon-tools.d.ts +2 -11
- package/dist/mcp/events-tool.d.ts +0 -1
- package/dist/mcp/server.d.ts +33 -14
- package/dist/native-scheme.d.ts +0 -1
- package/dist/output.d.ts +8 -11
- package/dist/package-root.d.ts +11 -0
- package/dist/package-version.d.ts +0 -1
- package/dist/qr-terminal.d.ts +0 -1
- package/dist/rpc/client.d.ts +3 -3
- package/dist/scheme.d.ts +60 -2
- package/dist/spki-pin.d.ts +0 -1
- package/package.json +6 -3
- package/dist/artifact-inspect.d.ts.map +0 -1
- package/dist/artifact-inspect.js +0 -334
- package/dist/bin.d.ts.map +0 -1
- package/dist/cli/command-options.d.ts.map +0 -1
- package/dist/cli/command-options.js +0 -95
- package/dist/cli/create-cli.d.ts.map +0 -1
- package/dist/cli/create-cli.js +0 -56
- package/dist/cli/dispatch.d.ts.map +0 -1
- package/dist/cli/dispatch.js +0 -275
- package/dist/cli/open-target.d.ts.map +0 -1
- package/dist/cli/open-target.js +0 -438
- package/dist/cli/result-types.d.ts.map +0 -1
- package/dist/cli/result-types.js +0 -6
- package/dist/cli/runner.d.ts.map +0 -1
- package/dist/cli/runner.js +0 -111
- package/dist/cli/types.d.ts.map +0 -1
- package/dist/cli/types.js +0 -10
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -2
- package/dist/client/app-client.d.ts.map +0 -1
- package/dist/client/app-client.js +0 -223
- package/dist/client/bootstrap.d.ts.map +0 -1
- package/dist/client/bootstrap.js +0 -108
- package/dist/client/connect.d.ts.map +0 -1
- package/dist/client/connect.js +0 -38
- package/dist/client/errors.d.ts.map +0 -1
- package/dist/client/errors.js +0 -40
- package/dist/client/index.d.ts.map +0 -1
- package/dist/commands/daemon.d.ts +0 -22
- package/dist/commands/daemon.d.ts.map +0 -1
- package/dist/commands/daemon.js +0 -128
- package/dist/commands/doctor.d.ts.map +0 -1
- package/dist/commands/doctor.js +0 -42
- package/dist/commands/events.d.ts.map +0 -1
- package/dist/commands/events.js +0 -76
- package/dist/commands/init.d.ts.map +0 -1
- package/dist/commands/init.js +0 -231
- package/dist/commands/invoke.d.ts.map +0 -1
- package/dist/commands/invoke.js +0 -54
- package/dist/commands/keygen.d.ts.map +0 -1
- package/dist/commands/keygen.js +0 -46
- package/dist/commands/link.d.ts.map +0 -1
- package/dist/commands/link.js +0 -43
- package/dist/commands/ls.d.ts.map +0 -1
- package/dist/commands/ls.js +0 -7
- package/dist/commands/mcp.d.ts.map +0 -1
- package/dist/commands/mcp.js +0 -98
- package/dist/commands/revoke.d.ts.map +0 -1
- package/dist/commands/revoke.js +0 -7
- package/dist/commands/tools.d.ts.map +0 -1
- package/dist/commands/tools.js +0 -52
- package/dist/daemon/address.d.ts.map +0 -1
- package/dist/daemon/address.js +0 -71
- package/dist/daemon/audit.d.ts.map +0 -1
- package/dist/daemon/audit.js +0 -237
- package/dist/daemon/calls.d.ts.map +0 -1
- package/dist/daemon/calls.js +0 -158
- package/dist/daemon/config.d.ts.map +0 -1
- package/dist/daemon/config.js +0 -170
- package/dist/daemon/daemon.d.ts.map +0 -1
- package/dist/daemon/daemon.js +0 -560
- package/dist/daemon/event-bus.d.ts.map +0 -1
- package/dist/daemon/event-bus.js +0 -111
- package/dist/daemon/links.d.ts.map +0 -1
- package/dist/daemon/links.js +0 -118
- package/dist/daemon/listener.d.ts.map +0 -1
- package/dist/daemon/listener.js +0 -132
- package/dist/daemon/log-rotation.d.ts.map +0 -1
- package/dist/daemon/log-rotation.js +0 -136
- package/dist/daemon/pidfile.d.ts.map +0 -1
- package/dist/daemon/pidfile.js +0 -90
- package/dist/daemon/policy.d.ts.map +0 -1
- package/dist/daemon/policy.js +0 -24
- package/dist/daemon/registry.d.ts.map +0 -1
- package/dist/daemon/registry.js +0 -31
- package/dist/daemon/rpc-server.d.ts.map +0 -1
- package/dist/daemon/rpc-server.js +0 -203
- package/dist/daemon/sessions.d.ts.map +0 -1
- package/dist/daemon/sessions.js +0 -429
- package/dist/daemon/socket-probe.d.ts.map +0 -1
- package/dist/daemon/socket-probe.js +0 -28
- package/dist/daemon/state-dir.d.ts.map +0 -1
- package/dist/daemon/state-dir.js +0 -43
- package/dist/daemon/timers.d.ts.map +0 -1
- package/dist/daemon/timers.js +0 -12
- package/dist/daemon/tls.d.ts.map +0 -1
- package/dist/daemon/tls.js +0 -79
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js +0 -162
- package/dist/host-certificate.d.ts.map +0 -1
- package/dist/host-certificate.js +0 -111
- package/dist/index.d.ts.map +0 -1
- package/dist/key-material.d.ts.map +0 -1
- package/dist/key-material.js +0 -42
- package/dist/link.d.ts.map +0 -1
- package/dist/link.js +0 -117
- package/dist/mcp/connect-tool.d.ts.map +0 -1
- package/dist/mcp/connect-tool.js +0 -396
- package/dist/mcp/daemon-tools.d.ts.map +0 -1
- package/dist/mcp/daemon-tools.js +0 -29
- package/dist/mcp/events-tool.d.ts.map +0 -1
- package/dist/mcp/events-tool.js +0 -322
- package/dist/mcp/server.d.ts.map +0 -1
- package/dist/mcp/server.js +0 -538
- package/dist/mcp/tool-mapping.d.ts +0 -58
- package/dist/mcp/tool-mapping.d.ts.map +0 -1
- package/dist/mcp/tool-mapping.js +0 -143
- package/dist/mcp/tool-namespace.d.ts +0 -40
- package/dist/mcp/tool-namespace.d.ts.map +0 -1
- package/dist/mcp/tool-namespace.js +0 -65
- package/dist/native-scheme.d.ts.map +0 -1
- package/dist/native-scheme.js +0 -465
- package/dist/output.d.ts.map +0 -1
- package/dist/output.js +0 -342
- package/dist/package-version.d.ts.map +0 -1
- package/dist/package-version.js +0 -47
- package/dist/qr-terminal.d.ts.map +0 -1
- package/dist/qr-terminal.js +0 -45
- package/dist/rpc/client.d.ts.map +0 -1
- package/dist/rpc/client.js +0 -819
- package/dist/scheme.d.ts.map +0 -1
- package/dist/scheme.js +0 -321
- package/dist/spki-pin.d.ts.map +0 -1
- package/dist/spki-pin.js +0 -14
package/dist/cli/open-target.js
DELETED
|
@@ -1,438 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Emulator/simulator fast path (ARCHITECTURE.md §8 delivery path 1, §10 `link --open`): delivers a
|
|
3
|
-
* deep link to a booted Android emulator/device or iOS simulator without any human handling a QR
|
|
4
|
-
* code — this is the CI/agent path. All subprocess execution goes through the injectable
|
|
5
|
-
* {@link ExecFn} seam so every branch here is testable without `adb`/`xcrun` installed.
|
|
6
|
-
*
|
|
7
|
-
* {@link detectBootedTargets} is the same enumeration used *before* a target is chosen, so callers
|
|
8
|
-
* that were given no explicit target (notably `appduct_connect` over MCP) can deliver to the one
|
|
9
|
-
* obvious device instead of falling back to a QR code nobody is there to scan.
|
|
10
|
-
*
|
|
11
|
-
* A third, experimental target — `ios-device`, a paired physical iPhone/iPad via `xcrun devicectl`
|
|
12
|
-
* (issue #31) — is explicit opt-in only: it is never auto-detected, it needs the app's bundle id,
|
|
13
|
-
* and its link must carry the LAN address rather than `127.0.0.1` (see {@link usesLoopbackAddress}).
|
|
14
|
-
*/
|
|
15
|
-
import { execFile } from "node:child_process";
|
|
16
|
-
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
|
17
|
-
import { tmpdir } from "node:os";
|
|
18
|
-
import path from "node:path";
|
|
19
|
-
import { connectionError, usageError } from "../errors.js";
|
|
20
|
-
export const isOpenTarget = (value) => {
|
|
21
|
-
return value === "android" || value === "ios-sim" || value === "ios-device";
|
|
22
|
-
};
|
|
23
|
-
/** Human-readable list of every accepted target, for the "must be one of" validation messages the
|
|
24
|
-
* CLI, the shared `mintLink` core and `appduct_connect` each render in their own wording. */
|
|
25
|
-
export const OPEN_TARGETS = ["android", "ios-sim", "ios-device"];
|
|
26
|
-
/**
|
|
27
|
-
* Whether a link delivered to this target reaches the daemon over loopback, and so must be minted
|
|
28
|
-
* with the `127.0.0.1` address override (`link.ts`, `mcp/connect-tool.ts`). True for `android`
|
|
29
|
-
* (`adb reverse` forwards the port onto the device) and `ios-sim` (the simulator shares the host's
|
|
30
|
-
* network stack); false for `ios-device` — a physical iPhone reaches the daemon only over the LAN,
|
|
31
|
-
* so such a link must carry the machine's real advertised address (issue #31).
|
|
32
|
-
*/
|
|
33
|
-
export const usesLoopbackAddress = (target) => {
|
|
34
|
-
return target === "android" || target === "ios-sim";
|
|
35
|
-
};
|
|
36
|
-
/**
|
|
37
|
-
* Whether an advertised address is one that only resolves back to the machine that minted the link.
|
|
38
|
-
* `daemon/address.ts` falls back to `127.0.0.1` when it cannot find a routable interface, and such
|
|
39
|
-
* a link handed to a physical phone points the phone at *itself*: the app connects to nothing, the
|
|
40
|
-
* session is never claimed, and `wait_for_session` blocks for its whole timeout with no clue why.
|
|
41
|
-
* The `ios-device` path checks this after minting and refuses rather than delivering.
|
|
42
|
-
*/
|
|
43
|
-
export const isLoopbackAddress = (address) => {
|
|
44
|
-
const normalized = address.trim().toLowerCase().replace(/^\[|\]$/gu, "");
|
|
45
|
-
return (normalized === "localhost" ||
|
|
46
|
-
normalized === "::1" ||
|
|
47
|
-
normalized === "0.0.0.0" ||
|
|
48
|
-
normalized === "::" ||
|
|
49
|
-
/^127\./u.test(normalized));
|
|
50
|
-
};
|
|
51
|
-
/** The one wording for that refusal, shared by the CLI and MCP paths. */
|
|
52
|
-
export const loopbackAddressMessage = (address) => `The daemon advertised "${address}", which a physical device cannot reach — it would point the ` +
|
|
53
|
-
"phone at itself and the session would never be claimed. No routable LAN address was detected; " +
|
|
54
|
-
'set "advertisedIp" in config.json to this machine\'s address on the network the device is on, ' +
|
|
55
|
-
"and retry.";
|
|
56
|
-
/** Real subprocess execution via `execFile` (no shell involved — argv is passed verbatim). */
|
|
57
|
-
export const defaultExec = (command, args) => {
|
|
58
|
-
return new Promise((resolve, reject) => {
|
|
59
|
-
execFile(command, args, { encoding: "utf8" }, (error, stdout, stderr) => {
|
|
60
|
-
if (error) {
|
|
61
|
-
reject(Object.assign(error, { stdout, stderr }));
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
resolve({ stdout, stderr });
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
};
|
|
68
|
-
const TOOL_INSTALL_HINT = {
|
|
69
|
-
adb: "the Android platform-tools (adb)",
|
|
70
|
-
xcrun: "the Xcode command line tools (xcrun)",
|
|
71
|
-
};
|
|
72
|
-
const run = async (exec, command, args) => {
|
|
73
|
-
try {
|
|
74
|
-
return await exec(command, args);
|
|
75
|
-
}
|
|
76
|
-
catch (error) {
|
|
77
|
-
const err = error;
|
|
78
|
-
if (err.code === "ENOENT") {
|
|
79
|
-
throw connectionError(`"${command}" was not found on PATH. Install ${TOOL_INSTALL_HINT[command] ?? command} and retry.`);
|
|
80
|
-
}
|
|
81
|
-
const stderr = (err.stderr ?? "").trim();
|
|
82
|
-
throw connectionError(`"${command} ${args.join(" ")}" failed${stderr ? `: ${stderr}` : `: ${err.message}`}`);
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
const parseBootedSimulators = (stdout) => {
|
|
86
|
-
let parsed;
|
|
87
|
-
try {
|
|
88
|
-
parsed = JSON.parse(stdout);
|
|
89
|
-
}
|
|
90
|
-
catch {
|
|
91
|
-
return [];
|
|
92
|
-
}
|
|
93
|
-
return Object.values(parsed.devices ?? {})
|
|
94
|
-
.flat()
|
|
95
|
-
.filter((entry) => entry.state === "Booted" && typeof entry.udid === "string" && entry.udid.length > 0)
|
|
96
|
-
.map((entry) => ({ udid: entry.udid, name: entry.name ?? entry.udid }));
|
|
97
|
-
};
|
|
98
|
-
const listBootedSimulators = async (exec) => {
|
|
99
|
-
const preflight = await run(exec, "xcrun", ["simctl", "list", "devices", "booted", "--json"]);
|
|
100
|
-
return parseBootedSimulators(preflight.stdout);
|
|
101
|
-
};
|
|
102
|
-
const describeSimulator = (simulator) => `${simulator.name} (${simulator.udid})`;
|
|
103
|
-
const deliverIosSim = async (exec, deepLink, device) => {
|
|
104
|
-
// An explicit udid is honored without a preflight, mirroring android's `--device` passthrough:
|
|
105
|
-
// the caller has already named the device, so re-listing only adds a failure mode.
|
|
106
|
-
if (device) {
|
|
107
|
-
await run(exec, "xcrun", ["simctl", "openurl", device, deepLink]);
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
const booted = await listBootedSimulators(exec);
|
|
111
|
-
if (booted.length === 0) {
|
|
112
|
-
throw usageError("No booted iOS simulator found. Boot a simulator (e.g. via Xcode) and retry.");
|
|
113
|
-
}
|
|
114
|
-
if (booted.length > 1) {
|
|
115
|
-
// Previously this delivered to simctl's `booted` alias regardless, which silently picks one of
|
|
116
|
-
// several booted simulators — the link would land on an arbitrary device and the session would
|
|
117
|
-
// never be claimed, looking exactly like a hung `wait_for_session`. Errors like android does.
|
|
118
|
-
throw usageError(`Multiple iOS simulators are booted (${booted
|
|
119
|
-
.map(describeSimulator)
|
|
120
|
-
.join(", ")}); specify --device <udid>.`);
|
|
121
|
-
}
|
|
122
|
-
// Target the resolved udid rather than the `booted` alias: `booted` is only unambiguous while
|
|
123
|
-
// exactly one simulator is up, and a second one booting between the preflight and this call would
|
|
124
|
-
// otherwise redirect the link to an arbitrary device.
|
|
125
|
-
await run(exec, "xcrun", ["simctl", "openurl", booted[0].udid, deepLink]);
|
|
126
|
-
};
|
|
127
|
-
// --- ios-device (experimental, issue #31) ---
|
|
128
|
-
/**
|
|
129
|
-
* `devicectl` writes its JSON to a *file* (`--json-output <path>`), never to stdout, so this path
|
|
130
|
-
* cannot reuse the stdout-parsing shape `simctl` uses. The file lives in a private `mkdtemp`
|
|
131
|
-
* directory removed in a `finally`, so a failing `devicectl` (or a malformed payload) never leaks
|
|
132
|
-
* a temp file.
|
|
133
|
-
*/
|
|
134
|
-
const runDevicectlJson = async (exec, buildArgs) => {
|
|
135
|
-
let dir;
|
|
136
|
-
try {
|
|
137
|
-
dir = await mkdtemp(path.join(tmpdir(), "appduct-devicectl-"));
|
|
138
|
-
}
|
|
139
|
-
catch (error) {
|
|
140
|
-
// A read-only or missing TMPDIR would otherwise surface as a bare `EACCES`/`ENOENT` against a
|
|
141
|
-
// path the user never chose, with nothing saying which knob to turn.
|
|
142
|
-
throw connectionError(`A temporary directory for devicectl's --json-output could not be created in "${tmpdir()}" ` +
|
|
143
|
-
`(${error.message}). Set TMPDIR to a writable directory and retry.`);
|
|
144
|
-
}
|
|
145
|
-
const file = path.join(dir, "devicectl.json");
|
|
146
|
-
try {
|
|
147
|
-
await run(exec, "xcrun", buildArgs(file));
|
|
148
|
-
try {
|
|
149
|
-
return JSON.parse(await readFile(file, "utf8"));
|
|
150
|
-
}
|
|
151
|
-
catch {
|
|
152
|
-
// `devicectl` exited 0 but wrote nothing readable. Treated as an empty listing (the caller
|
|
153
|
-
// renders "no device found") rather than surfacing a JSON syntax error, matching how
|
|
154
|
-
// `parseBootedSimulators` handles unparseable `simctl` output.
|
|
155
|
-
return undefined;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
finally {
|
|
159
|
-
// Swallowed deliberately: a failure to clean up a temp directory must not replace the
|
|
160
|
-
// devicectl error the caller actually needs to read (a `finally` that throws wins).
|
|
161
|
-
await rm(dir, { force: true, recursive: true }).catch(() => { });
|
|
162
|
-
}
|
|
163
|
-
};
|
|
164
|
-
/** `hardwareProperties.platform` values `devicectl` reports for things that are not an iPhone or
|
|
165
|
-
* iPad. A paired Apple Watch or Vision Pro is a real CoreDevice and shows up in the same listing,
|
|
166
|
-
* but cannot run the app and must never make the device choice look ambiguous. */
|
|
167
|
-
const DELIVERABLE_PLATFORM = "ios";
|
|
168
|
-
/**
|
|
169
|
-
* The only `tunnelState` that means "not reachable at all". Deliberately *not* including
|
|
170
|
-
* `disconnected`: a wired, trusted iPhone routinely reports `disconnected` (the tunnel is brought
|
|
171
|
-
* up on demand), so excluding it would drop exactly the device this target exists to reach. This
|
|
172
|
-
* matches the vendored Expo CLI's own `devicectl` integration
|
|
173
|
-
* (`@expo/cli`'s `AppleDevice.js:102`), which keeps `disconnected` and excludes only `unavailable`.
|
|
174
|
-
*/
|
|
175
|
-
const UNREACHABLE_TUNNEL_STATE = "unavailable";
|
|
176
|
-
/** Expo's companion condition: a usable device is affirmatively `paired`. */
|
|
177
|
-
const USABLE_PAIRING_STATE = "paired";
|
|
178
|
-
/**
|
|
179
|
-
* `devicectl list devices` lists **every CoreDevice the Mac has ever known**, across platforms and
|
|
180
|
-
* regardless of whether it is plugged in — so the listing has to be filtered, not trusted.
|
|
181
|
-
*
|
|
182
|
-
* Filtering happens here rather than through `devicectl --filter` (an NSPredicate over undocumented
|
|
183
|
-
* keys) so that every rule is exercised by the `ExecFn` tests instead of by a predicate string no
|
|
184
|
-
* test can evaluate.
|
|
185
|
-
*
|
|
186
|
-
* Each rule drops an entry only when the field is **present and disqualifying**; a missing field
|
|
187
|
-
* keeps the device. The field names are undocumented, so a `devicectl` that stops emitting one must
|
|
188
|
-
* degrade to "offer the device and let the launch fail loudly", never to "silently find nothing".
|
|
189
|
-
* That is the one deliberate departure from Expo, which tests `pairingState === "paired"`
|
|
190
|
-
* positively and so would find nothing at all if the key were ever renamed.
|
|
191
|
-
*/
|
|
192
|
-
const parsePairedIosDevices = (payload) => {
|
|
193
|
-
const devices = payload?.result?.devices;
|
|
194
|
-
if (!Array.isArray(devices)) {
|
|
195
|
-
return [];
|
|
196
|
-
}
|
|
197
|
-
return devices
|
|
198
|
-
.map((entry) => {
|
|
199
|
-
const platform = entry?.hardwareProperties?.platform;
|
|
200
|
-
if (typeof platform === "string" && platform.toLowerCase() !== DELIVERABLE_PLATFORM) {
|
|
201
|
-
return undefined;
|
|
202
|
-
}
|
|
203
|
-
const tunnelState = entry?.connectionProperties?.tunnelState;
|
|
204
|
-
if (typeof tunnelState === "string" && tunnelState.toLowerCase() === UNREACHABLE_TUNNEL_STATE) {
|
|
205
|
-
return undefined;
|
|
206
|
-
}
|
|
207
|
-
const pairingState = entry?.connectionProperties?.pairingState;
|
|
208
|
-
if (typeof pairingState === "string" && pairingState.toLowerCase() !== USABLE_PAIRING_STATE) {
|
|
209
|
-
return undefined;
|
|
210
|
-
}
|
|
211
|
-
// `hardwareProperties.udid` is what `--device` wants; `identifier` (the CoreDevice UUID) is
|
|
212
|
-
// also accepted by `devicectl`, so a listing that omits the udid is still usable rather than
|
|
213
|
-
// silently dropped. Checked for emptiness rather than `??`, which would keep a `""` udid and
|
|
214
|
-
// never reach the documented fallback.
|
|
215
|
-
const hardwareUdid = entry?.hardwareProperties?.udid;
|
|
216
|
-
const udid = typeof hardwareUdid === "string" && hardwareUdid.length > 0
|
|
217
|
-
? hardwareUdid
|
|
218
|
-
: entry?.identifier;
|
|
219
|
-
return typeof udid === "string" && udid.length > 0
|
|
220
|
-
? { udid, name: entry?.deviceProperties?.name ?? udid }
|
|
221
|
-
: undefined;
|
|
222
|
-
})
|
|
223
|
-
.filter((device) => device !== undefined);
|
|
224
|
-
};
|
|
225
|
-
const listPairedIosDevices = async (exec) => {
|
|
226
|
-
const payload = await runDevicectlJson(exec, (file) => [
|
|
227
|
-
"devicectl",
|
|
228
|
-
"list",
|
|
229
|
-
"devices",
|
|
230
|
-
"--timeout",
|
|
231
|
-
"5",
|
|
232
|
-
"--json-output",
|
|
233
|
-
file,
|
|
234
|
-
]);
|
|
235
|
-
return parsePairedIosDevices(payload);
|
|
236
|
-
};
|
|
237
|
-
const describeIosDevice = (device) => `${device.name} (${device.udid})`;
|
|
238
|
-
/** The single wording naming both ways to supply a bundle id, so the CLI and MCP paths agree. */
|
|
239
|
-
export const MISSING_BUNDLE_ID_MESSAGE = "Delivering to a physical iPhone needs the app's bundle id: pass \"--bundle-id <id>\" (or " +
|
|
240
|
-
'"bundleId" over MCP), or set "iosBundleId" in config.json.';
|
|
241
|
-
/**
|
|
242
|
-
* A bundle id is the **only trailing positional** in the `devicectl device process launch` argv, so
|
|
243
|
-
* a value beginning with `-` would be read by `devicectl` as an option rather than as the app to
|
|
244
|
-
* launch — `--console`, say, silently changing what the command does. Apple's own grammar for a
|
|
245
|
-
* bundle identifier is alphanumerics, `.` and `-`, which never legitimately starts with `-`, so
|
|
246
|
-
* enforcing exactly that shape closes the hole at every entry point.
|
|
247
|
-
*
|
|
248
|
-
* A `--` terminator is deliberately *not* used instead: `devicectl` takes launch arguments after
|
|
249
|
-
* the bundle id, its handling of `--` is undocumented, and none of it can be verified without
|
|
250
|
-
* hardware — whereas this check is exact and fully testable.
|
|
251
|
-
*/
|
|
252
|
-
const BUNDLE_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9.-]*$/u;
|
|
253
|
-
export const isValidBundleId = (value) => BUNDLE_ID_PATTERN.test(value);
|
|
254
|
-
export const invalidBundleIdMessage = (value) => `"${value}" is not a valid iOS bundle id (letters, digits, "." and "-", starting with a letter ` +
|
|
255
|
-
"or digit).";
|
|
256
|
-
/** Same ambiguity rules as `deliverIosSim`: exactly one paired device, or the caller names one. */
|
|
257
|
-
const resolveSingleIosDevice = async (exec) => {
|
|
258
|
-
const paired = await listPairedIosDevices(exec);
|
|
259
|
-
if (paired.length === 0) {
|
|
260
|
-
throw usageError('No connected iOS device was found ("xcrun devicectl list devices" listed none, ignoring ' +
|
|
261
|
-
"disconnected devices and non-iOS ones such as a paired Watch or Vision Pro). Connect the " +
|
|
262
|
-
"device, trust this Mac, and enable Developer Mode on it, then retry.");
|
|
263
|
-
}
|
|
264
|
-
if (paired.length > 1) {
|
|
265
|
-
throw usageError(`Multiple connected iOS devices were found (${paired
|
|
266
|
-
.map(describeIosDevice)
|
|
267
|
-
.join(", ")}); specify --device <udid>.`);
|
|
268
|
-
}
|
|
269
|
-
return paired[0].udid;
|
|
270
|
-
};
|
|
271
|
-
/**
|
|
272
|
-
* Experimental physical-iPhone delivery (issue #31). `xcrun devicectl device process launch
|
|
273
|
-
* --device <udid> --payload-url <url> <bundle-id>` is undocumented by Apple but is the mechanism
|
|
274
|
-
* Xcode 15+ exposes for handing a URL to an installed, dev-signed app on a paired iOS 17+ device.
|
|
275
|
-
* It may cold-launch (rather than foreground) the app; the app side copes either way, because a
|
|
276
|
-
* delivered link supersedes a held session and the initial-URL path is handled.
|
|
277
|
-
*
|
|
278
|
-
* Unlike `ios-sim`, this is never auto-detected — see {@link detectBootedTargets}.
|
|
279
|
-
*/
|
|
280
|
-
const deliverIosDevice = async (exec, deepLink, device, bundleId, relaunch) => {
|
|
281
|
-
// Checked before any enumeration: without a bundle id the launch cannot happen at all, so
|
|
282
|
-
// spending a `devicectl list devices` timeout first would only delay the same error.
|
|
283
|
-
if (!bundleId) {
|
|
284
|
-
throw usageError(MISSING_BUNDLE_ID_MESSAGE);
|
|
285
|
-
}
|
|
286
|
-
// Last line of defence: every caller validates too, but this is the one place the value reaches
|
|
287
|
-
// an argv, so it is the one place that must not be bypassable.
|
|
288
|
-
if (!isValidBundleId(bundleId)) {
|
|
289
|
-
throw usageError(invalidBundleIdMessage(bundleId));
|
|
290
|
-
}
|
|
291
|
-
// An explicit udid is honored without a preflight, mirroring the `ios-sim`/`android` passthrough.
|
|
292
|
-
const udid = device ?? (await resolveSingleIosDevice(exec));
|
|
293
|
-
await run(exec, "xcrun", [
|
|
294
|
-
"devicectl",
|
|
295
|
-
"device",
|
|
296
|
-
"process",
|
|
297
|
-
"launch",
|
|
298
|
-
"--device",
|
|
299
|
-
udid,
|
|
300
|
-
// Opt-in only (`--relaunch` / `relaunch: true`). A plain `process launch` is what the vendored
|
|
301
|
-
// Expo CLI does, so it is the better-attested default; what it does when the app is *already
|
|
302
|
-
// running* is the one thing nobody here can verify without hardware. If that turns out to fail
|
|
303
|
-
// rather than deliver the URL, `--terminate-existing` is the escape hatch — it kills the
|
|
304
|
-
// running instance first, which Appduct copes with because a delivered link supersedes a
|
|
305
|
-
// held session.
|
|
306
|
-
...(relaunch ? ["--terminate-existing"] : []),
|
|
307
|
-
"--payload-url",
|
|
308
|
-
deepLink,
|
|
309
|
-
bundleId,
|
|
310
|
-
]);
|
|
311
|
-
};
|
|
312
|
-
/** Parses `adb devices` output: header line, then `<serial>\t<state>` per attached device. */
|
|
313
|
-
const parseAdbDevices = (stdout) => {
|
|
314
|
-
return stdout
|
|
315
|
-
.split("\n")
|
|
316
|
-
.slice(1)
|
|
317
|
-
.map((line) => line.trim())
|
|
318
|
-
.filter((line) => line.length > 0)
|
|
319
|
-
.map((line) => {
|
|
320
|
-
const [serial = "", state = ""] = line.split(/\s+/u);
|
|
321
|
-
return { serial, state };
|
|
322
|
-
})
|
|
323
|
-
.filter((entry) => entry.serial.length > 0);
|
|
324
|
-
};
|
|
325
|
-
const listAttachedAndroidDevices = async (exec) => {
|
|
326
|
-
const listing = await run(exec, "adb", ["devices"]);
|
|
327
|
-
return parseAdbDevices(listing.stdout)
|
|
328
|
-
.filter((entry) => entry.state === "device")
|
|
329
|
-
.map((entry) => entry.serial);
|
|
330
|
-
};
|
|
331
|
-
/**
|
|
332
|
-
* Resolves the `-s <serial>` argument, if any. `--device` always wins; otherwise, if
|
|
333
|
-
* `ANDROID_SERIAL` is set, `adb` already honors it with no flag needed from us; otherwise exactly
|
|
334
|
-
* one attached device is required (zero or several is an error naming what's attached).
|
|
335
|
-
*/
|
|
336
|
-
const resolveAndroidSerial = async (exec, device, env) => {
|
|
337
|
-
if (device) {
|
|
338
|
-
return device;
|
|
339
|
-
}
|
|
340
|
-
if (env.ANDROID_SERIAL) {
|
|
341
|
-
return undefined;
|
|
342
|
-
}
|
|
343
|
-
const attached = await listAttachedAndroidDevices(exec);
|
|
344
|
-
if (attached.length === 0) {
|
|
345
|
-
throw usageError('No Android device or emulator is attached ("adb devices" listed none).');
|
|
346
|
-
}
|
|
347
|
-
if (attached.length > 1) {
|
|
348
|
-
throw usageError(`Multiple Android devices are attached (${attached.join(", ")}); specify --device <serial> or set ANDROID_SERIAL.`);
|
|
349
|
-
}
|
|
350
|
-
return undefined;
|
|
351
|
-
};
|
|
352
|
-
const withSerial = (args, serial) => {
|
|
353
|
-
return serial ? ["-s", serial, ...args] : args;
|
|
354
|
-
};
|
|
355
|
-
const deliverAndroid = async (exec, deepLink, wssPort, device, env) => {
|
|
356
|
-
const serial = await resolveAndroidSerial(exec, device, env);
|
|
357
|
-
// `adb reverse` before `am start`: the port forward must exist before the app tries to connect,
|
|
358
|
-
// which can happen the instant the deep link is handled.
|
|
359
|
-
await run(exec, "adb", withSerial(["reverse", `tcp:${wssPort}`, `tcp:${wssPort}`], serial));
|
|
360
|
-
await run(exec, "adb", withSerial([
|
|
361
|
-
"shell",
|
|
362
|
-
"am",
|
|
363
|
-
"start",
|
|
364
|
-
"-a",
|
|
365
|
-
"android.intent.action.VIEW",
|
|
366
|
-
"-d",
|
|
367
|
-
// `adb shell` reconstructs everything after "shell" into one string and re-parses it on the
|
|
368
|
-
// device's own shell; single-quoting the URL here (not just for local execFile, which never
|
|
369
|
-
// re-tokenizes argv) protects against that second, remote parse (ARCHITECTURE.md §8).
|
|
370
|
-
`'${deepLink}'`,
|
|
371
|
-
], serial));
|
|
372
|
-
};
|
|
373
|
-
/** Best-effort enumeration: a missing or failing `xcrun`/`adb` means "no devices of that platform"
|
|
374
|
-
* rather than an error. Detection runs when the caller named *no* target, so a machine without the
|
|
375
|
-
* Android tools installed (or without Xcode) must still reach a clean `{ kind: "none" }` and its
|
|
376
|
-
* QR fallback instead of failing the whole call. */
|
|
377
|
-
const safeList = async (list) => {
|
|
378
|
-
try {
|
|
379
|
-
return await list();
|
|
380
|
-
}
|
|
381
|
-
catch {
|
|
382
|
-
return [];
|
|
383
|
-
}
|
|
384
|
-
};
|
|
385
|
-
/**
|
|
386
|
-
* Enumerates every booted simulator and attached Android device so a caller with no explicit
|
|
387
|
-
* target can deliver to the single obvious one. `ANDROID_SERIAL`, when set, narrows the Android
|
|
388
|
-
* side to that serial — it is exactly the disambiguation the user has already made.
|
|
389
|
-
*
|
|
390
|
-
* Physical iPhones (`ios-device`) are deliberately *not* enumerated here (issue #31): a paired
|
|
391
|
-
* iPhone is often someone's personal phone rather than a test device, delivery to it may
|
|
392
|
-
* cold-launch the app, and it additionally needs a bundle id the caller has to supply. It is an
|
|
393
|
-
* explicit, opt-in target only.
|
|
394
|
-
*/
|
|
395
|
-
export const detectBootedTargets = async (options = {}) => {
|
|
396
|
-
const exec = options.exec ?? defaultExec;
|
|
397
|
-
const env = options.env ?? process.env;
|
|
398
|
-
const [simulators, androidSerials] = await Promise.all([
|
|
399
|
-
safeList(() => listBootedSimulators(exec)),
|
|
400
|
-
env.ANDROID_SERIAL
|
|
401
|
-
? Promise.resolve([env.ANDROID_SERIAL])
|
|
402
|
-
: safeList(() => listAttachedAndroidDevices(exec)),
|
|
403
|
-
]);
|
|
404
|
-
const candidates = [
|
|
405
|
-
...simulators.map((simulator) => ({
|
|
406
|
-
target: "ios-sim",
|
|
407
|
-
device: simulator.udid,
|
|
408
|
-
label: describeSimulator(simulator),
|
|
409
|
-
})),
|
|
410
|
-
...androidSerials.map((serial) => ({
|
|
411
|
-
target: "android",
|
|
412
|
-
device: serial,
|
|
413
|
-
label: serial,
|
|
414
|
-
})),
|
|
415
|
-
];
|
|
416
|
-
if (candidates.length === 0) {
|
|
417
|
-
return { kind: "none" };
|
|
418
|
-
}
|
|
419
|
-
if (candidates.length === 1) {
|
|
420
|
-
return { kind: "single", detected: candidates[0] };
|
|
421
|
-
}
|
|
422
|
-
return { kind: "ambiguous", candidates };
|
|
423
|
-
};
|
|
424
|
-
/** Delivers `deepLink` to a booted Android device/emulator, an iOS simulator, or (experimentally)
|
|
425
|
-
* a paired physical iOS device. Throws a clear, actionable error (missing tool, no booted device,
|
|
426
|
-
* ambiguous device, missing bundle id) rather than a raw one. */
|
|
427
|
-
export const deliverToOpenTarget = async (options) => {
|
|
428
|
-
const exec = options.exec ?? defaultExec;
|
|
429
|
-
if (options.target === "ios-sim") {
|
|
430
|
-
await deliverIosSim(exec, options.deepLink, options.device);
|
|
431
|
-
return;
|
|
432
|
-
}
|
|
433
|
-
if (options.target === "ios-device") {
|
|
434
|
-
await deliverIosDevice(exec, options.deepLink, options.device, options.bundleId, options.relaunch ?? false);
|
|
435
|
-
return;
|
|
436
|
-
}
|
|
437
|
-
await deliverAndroid(exec, options.deepLink, options.wssPort, options.device, options.env ?? process.env);
|
|
438
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"result-types.d.ts","sourceRoot":"","sources":["../../src/cli/result-types.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/result-types.js
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CLI command-result types (ARCHITECTURE.md §10). The v2 command table replaces the v1 shapes that
|
|
3
|
-
* used to live here (`host`/`connect`/`session` — all removed from the v2 command surface) with
|
|
4
|
-
* the thin-client shapes for the v2 command table.
|
|
5
|
-
*/
|
|
6
|
-
export {};
|
package/dist/cli/runner.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/cli/runner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAGnD,OAAO,EAAgB,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,EAAqB,KAAK,KAAK,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAE9E;;;;GAIG;AACH,KAAK,mBAAmB,GAAG;IACzB,MAAM,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC;IAC3B,UAAU,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,IAAI,EAAE,MAAM,IAAI,CAAC;CAClB,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC;IAC9B,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAAC;AAeF,eAAO,MAAM,cAAc,GACzB,SAAS,MAAM,EACf,SAAS,MAAM,SAAS,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,EAC/D,SAAS,YAAY,GACnB,IAAI,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,GACrC,OAAO,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,GAAG,MAAM,CAAC,CAAC,GAAG;IAC5C,KAAK,EAAE,KAAK,CAAC;CACd,KACF,OAAO,CAAC,MAAM,CAkChB,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAC/B,SAAS,MAAM,EACf,SAAS,MAAM,OAAO,CAAC,mBAAmB,CAAC,EAC3C,SAAS,YAAY,GAAG;IACtB,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,CAAC,EAAE,qBAAqB,CAAC;CAClC,KACA,OAAO,CAAC,MAAM,CAoFhB,CAAC"}
|
package/dist/cli/runner.js
DELETED
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import { getExitCodeForError, toCliError } from "../errors.js";
|
|
2
|
-
import { renderResult } from "../output.js";
|
|
3
|
-
import { createCommandMeta } from "./types.js";
|
|
4
|
-
const writeRenderedOutput = (rendered, writers) => {
|
|
5
|
-
if (rendered.stdout) {
|
|
6
|
-
writers.stdout.write(rendered.stdout);
|
|
7
|
-
}
|
|
8
|
-
if (rendered.stderr) {
|
|
9
|
-
writers.stderr.write(rendered.stderr);
|
|
10
|
-
}
|
|
11
|
-
};
|
|
12
|
-
export const executeCommand = async (command, handler, options) => {
|
|
13
|
-
const startedAt = options.clock.now();
|
|
14
|
-
const renderOptions = {
|
|
15
|
-
command,
|
|
16
|
-
json: options.json,
|
|
17
|
-
color: options.color,
|
|
18
|
-
qr: options.qr,
|
|
19
|
-
full: options.full,
|
|
20
|
-
};
|
|
21
|
-
try {
|
|
22
|
-
const result = await handler();
|
|
23
|
-
const finishedAt = options.clock.now();
|
|
24
|
-
const withMeta = {
|
|
25
|
-
...result,
|
|
26
|
-
meta: createCommandMeta(command, startedAt, finishedAt),
|
|
27
|
-
};
|
|
28
|
-
writeRenderedOutput(renderResult(withMeta, renderOptions), options);
|
|
29
|
-
return 0;
|
|
30
|
-
}
|
|
31
|
-
catch (error) {
|
|
32
|
-
const finishedAt = options.clock.now();
|
|
33
|
-
const cliError = toCliError(error);
|
|
34
|
-
const result = {
|
|
35
|
-
ok: false,
|
|
36
|
-
error: cliError,
|
|
37
|
-
meta: createCommandMeta(command, startedAt, finishedAt),
|
|
38
|
-
};
|
|
39
|
-
writeRenderedOutput(renderResult(result, renderOptions), options);
|
|
40
|
-
return getExitCodeForError(error);
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
export const executeHostedCommand = async (command, handler, options) => {
|
|
44
|
-
const startedAt = options.clock.now();
|
|
45
|
-
let renderedSuccess = false;
|
|
46
|
-
try {
|
|
47
|
-
const hosted = await handler();
|
|
48
|
-
const finishedAt = options.clock.now();
|
|
49
|
-
const withMeta = {
|
|
50
|
-
...hosted.result,
|
|
51
|
-
meta: createCommandMeta(command, startedAt, finishedAt),
|
|
52
|
-
};
|
|
53
|
-
// A live reporter (e.g. `events`'s streaming NDJSON/human lines) renders its own output as it
|
|
54
|
-
// goes; only the absence of a reporter (or an explicit "plain" one) falls back to the default
|
|
55
|
-
// one-shot `renderResult` bootstrap rendering.
|
|
56
|
-
const liveReporter = options.reporter;
|
|
57
|
-
const shouldRenderBootstrap = !liveReporter || liveReporter.kind === "plain";
|
|
58
|
-
if (shouldRenderBootstrap) {
|
|
59
|
-
writeRenderedOutput(renderResult(withMeta, {
|
|
60
|
-
command,
|
|
61
|
-
json: options.json,
|
|
62
|
-
color: options.color,
|
|
63
|
-
}), options);
|
|
64
|
-
renderedSuccess = true;
|
|
65
|
-
}
|
|
66
|
-
let resolved = false;
|
|
67
|
-
const stop = () => {
|
|
68
|
-
if (resolved) {
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
resolved = true;
|
|
72
|
-
hosted.stop();
|
|
73
|
-
};
|
|
74
|
-
process.once("SIGINT", stop);
|
|
75
|
-
process.once("SIGTERM", stop);
|
|
76
|
-
try {
|
|
77
|
-
await hosted.completion;
|
|
78
|
-
}
|
|
79
|
-
finally {
|
|
80
|
-
resolved = true;
|
|
81
|
-
process.off("SIGINT", stop);
|
|
82
|
-
process.off("SIGTERM", stop);
|
|
83
|
-
options.reporter?.dispose();
|
|
84
|
-
}
|
|
85
|
-
return 0;
|
|
86
|
-
}
|
|
87
|
-
catch (error) {
|
|
88
|
-
options.reporter?.dispose();
|
|
89
|
-
const finishedAt = options.clock.now();
|
|
90
|
-
const cliError = toCliError(error);
|
|
91
|
-
const result = {
|
|
92
|
-
ok: false,
|
|
93
|
-
error: cliError,
|
|
94
|
-
meta: createCommandMeta(command, startedAt, finishedAt),
|
|
95
|
-
};
|
|
96
|
-
if (renderedSuccess && options.json) {
|
|
97
|
-
// The single-JSON-object-on-stdout contract was already fulfilled by the bootstrap render
|
|
98
|
-
// above; a failure that happens later (e.g. during a long-running `completion`) must still
|
|
99
|
-
// be a JSON object, just on stderr instead — bare text here was v1's defect (leaked
|
|
100
|
-
// unparseable output onto stderr in `--json` mode).
|
|
101
|
-
options.stderr.write(`${JSON.stringify(result)}\n`);
|
|
102
|
-
return getExitCodeForError(error);
|
|
103
|
-
}
|
|
104
|
-
writeRenderedOutput(renderResult(result, {
|
|
105
|
-
command,
|
|
106
|
-
json: options.json,
|
|
107
|
-
color: options.color,
|
|
108
|
-
}), options);
|
|
109
|
-
return getExitCodeForError(error);
|
|
110
|
-
}
|
|
111
|
-
};
|
package/dist/cli/types.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/cli/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,MAAM,MAAM,KAAK,GAAG;IAClB,GAAG,EAAE,MAAM,IAAI,CAAC;CACjB,CAAC;AAEF,eAAO,MAAM,WAAW,EAAE,KAEzB,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAC5B,SAAS,MAAM,EACf,WAAW,IAAI,EACf,YAAY,IAAI,KACf,WAMF,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,CAAC,EAAE,IAAI,CAAC,OAAO,OAAO,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC;IACxD,MAAM,CAAC,EAAE,IAAI,CAAC,OAAO,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9C,KAAK,CAAC,EAAE,KAAK,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,QAAQ,CAAC,IAAI,CAAC,aAAa,EAAE,QAAQ,GAAG,QAAQ,CAAC,CAAC,CAAC;AAE9E,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAC;IAC/B,MAAM,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAC;IAC/B,KAAK,EAAE,KAAK,CAAC;CACd,CAAC"}
|
package/dist/cli/types.js
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
export const systemClock = {
|
|
2
|
-
now: () => new Date(),
|
|
3
|
-
};
|
|
4
|
-
export const createCommandMeta = (command, startedAt, finishedAt) => {
|
|
5
|
-
return {
|
|
6
|
-
command,
|
|
7
|
-
timestamp: finishedAt.toISOString(),
|
|
8
|
-
duration_ms: finishedAt.getTime() - startedAt.getTime(),
|
|
9
|
-
};
|
|
10
|
-
};
|
package/dist/cli.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,YAAY,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/cli.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"app-client.d.ts","sourceRoot":"","sources":["../../src/client/app-client.ts"],"names":[],"mappings":"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"}
|