@nylorun/cli 0.2.1-beta → 0.3.0-beta

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 (55) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +87 -40
  3. package/dist/cli.d.ts +1 -1
  4. package/dist/cli.js +132 -220
  5. package/dist/dev-entry.d.ts +5 -1
  6. package/dist/dev-entry.js +7 -6
  7. package/dist/dev.d.ts +45 -4
  8. package/dist/dev.js +245 -32
  9. package/dist/doctor.d.ts +25 -3
  10. package/dist/doctor.js +104 -16
  11. package/dist/environment.d.ts +4 -1
  12. package/dist/environment.js +12 -3
  13. package/dist/launcher.d.ts +5 -5
  14. package/dist/launcher.js +6 -3
  15. package/dist/model/configure.d.ts +30 -0
  16. package/dist/model/configure.js +117 -11
  17. package/dist/model/host-model.d.ts +5 -3
  18. package/dist/model/host-model.js +36 -15
  19. package/dist/project/attach.d.ts +32 -0
  20. package/dist/project/attach.js +241 -0
  21. package/dist/project/create-tenant.d.ts +26 -0
  22. package/dist/project/create-tenant.js +65 -0
  23. package/dist/project/credentials.d.ts +18 -0
  24. package/dist/project/credentials.js +64 -0
  25. package/dist/project/link.d.ts +20 -0
  26. package/dist/project/link.js +77 -0
  27. package/dist/project/root.d.ts +9 -0
  28. package/dist/project/root.js +51 -0
  29. package/dist/project/seed.d.ts +19 -0
  30. package/dist/project/seed.js +94 -0
  31. package/dist/runtime/baseline.d.ts +2 -0
  32. package/dist/runtime/baseline.js +11 -0
  33. package/dist/runtime/commands.d.ts +16 -0
  34. package/dist/runtime/commands.js +292 -0
  35. package/dist/runtime/ephemeral.d.ts +37 -0
  36. package/dist/runtime/ephemeral.js +84 -0
  37. package/dist/runtime/exit-codes.d.ts +9 -0
  38. package/dist/runtime/exit-codes.js +43 -0
  39. package/dist/runtime/index.d.ts +8 -0
  40. package/dist/runtime/index.js +6 -0
  41. package/dist/runtime/launcher.d.ts +81 -0
  42. package/dist/runtime/launcher.js +243 -0
  43. package/dist/runtime/progress.d.ts +10 -0
  44. package/dist/runtime/progress.js +8 -0
  45. package/dist/runtime/version.d.ts +6 -0
  46. package/dist/runtime/version.js +27 -0
  47. package/dist/tenant/commands.d.ts +4 -0
  48. package/dist/tenant/commands.js +262 -0
  49. package/package.json +6 -3
  50. package/dist/project-runner.d.ts +0 -20
  51. package/dist/project-runner.js +0 -141
  52. package/dist/runtime-host.d.ts +0 -39
  53. package/dist/runtime-host.js +0 -355
  54. package/dist/scope.d.ts +0 -65
  55. package/dist/scope.js +0 -191
@@ -0,0 +1,37 @@
1
+ import { createAdmin as defaultCreateAdmin } from "@nylorun/admin";
2
+ import type { Admin } from "@nylorun/admin";
3
+ import { type LauncherOptions } from "./launcher.js";
4
+ export interface EphemeralOptions extends LauncherOptions {
5
+ /** Temporary Host root. Default: a new directory under os.tmpdir(). */
6
+ home?: string;
7
+ /** Tenant display name. Default: directory basename or "ephemeral". */
8
+ name?: string;
9
+ /** Injected Admin factory (tests). Default: `@nylorun/admin` createAdmin. */
10
+ createAdmin?: typeof defaultCreateAdmin;
11
+ /** Abort / cleanup signal (tests and SIGINT wiring). */
12
+ signal?: AbortSignal;
13
+ }
14
+ export interface EphemeralTenant {
15
+ id: string;
16
+ name: string;
17
+ }
18
+ export interface EphemeralRuntime {
19
+ home: string;
20
+ url: string;
21
+ hostId: string;
22
+ version: string;
23
+ tenant: EphemeralTenant;
24
+ applicationKey: string;
25
+ admin: Admin;
26
+ /** Stop the Host and delete the temporary home. */
27
+ close(): Promise<void>;
28
+ }
29
+ /**
30
+ * `nylorun dev --ephemeral` Host setup (D§12):
31
+ * temporary home, start Host on port 0, `createAdmin({ home })`, `createTenant`.
32
+ *
33
+ * Uses launcher `up --port 0` (detached Host) rather than foreground `run`, so
34
+ * the CLI can continue with Project setup / tsx watch and still `down` on exit.
35
+ * Semantically matches D§12: temporary home, free port, createTenant, cleanup.
36
+ */
37
+ export declare function startEphemeral(options?: EphemeralOptions): Promise<EphemeralRuntime>;
@@ -0,0 +1,84 @@
1
+ import { mkdtemp, rm } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { createAdmin as defaultCreateAdmin } from "@nylorun/admin";
5
+ import { CliError } from "../errors.js";
6
+ import { launcher, throwOnLauncherFailure, } from "./launcher.js";
7
+ /**
8
+ * `nylorun dev --ephemeral` Host setup (D§12):
9
+ * temporary home, start Host on port 0, `createAdmin({ home })`, `createTenant`.
10
+ *
11
+ * Uses launcher `up --port 0` (detached Host) rather than foreground `run`, so
12
+ * the CLI can continue with Project setup / tsx watch and still `down` on exit.
13
+ * Semantically matches D§12: temporary home, free port, createTenant, cleanup.
14
+ */
15
+ export async function startEphemeral(options = {}) {
16
+ const home = options.home ??
17
+ (await mkdtemp(join(tmpdir(), "nylorun-ephemeral-")));
18
+ const createAdminFn = options.createAdmin ?? defaultCreateAdmin;
19
+ const env = options.env ?? process.env;
20
+ const handle = await launcher(home, { env });
21
+ const up = await handle.invoke(["up", "--port", "0"], { env });
22
+ throwOnLauncherFailure(up);
23
+ const result = up.result ?? {};
24
+ const url = typeof result.url === "string" ? result.url : undefined;
25
+ if (!url) {
26
+ throw new CliError(`Ephemeral Runtime Host started without a URL under ${home}.`, 7);
27
+ }
28
+ const ready = {
29
+ url,
30
+ hostId: typeof result.hostId === "string" ? result.hostId : "host_unknown",
31
+ version: typeof result.version === "string"
32
+ ? result.version
33
+ : handle.runtime.version,
34
+ };
35
+ const admin = createAdminFn({ home });
36
+ const name = options.name ??
37
+ home.split(/[/\\]/).filter(Boolean).pop() ??
38
+ "ephemeral";
39
+ const created = await admin.createTenant({ name });
40
+ const tenant = {
41
+ id: created.tenant.id,
42
+ name: created.tenant.name ?? name,
43
+ };
44
+ let closed = false;
45
+ let closing;
46
+ const close = async () => {
47
+ if (closing)
48
+ return closing;
49
+ closing = (async () => {
50
+ if (closed)
51
+ return;
52
+ closed = true;
53
+ try {
54
+ const down = await handle.invoke(["down", "--force"], { env });
55
+ if (down.exitCode !== 0)
56
+ throwOnLauncherFailure(down);
57
+ }
58
+ catch {
59
+ /* best-effort stop */
60
+ }
61
+ await rm(home, { recursive: true, force: true }).catch(() => undefined);
62
+ })();
63
+ return closing;
64
+ };
65
+ if (options.signal) {
66
+ const onAbort = () => {
67
+ void close();
68
+ };
69
+ if (options.signal.aborted)
70
+ void close();
71
+ else
72
+ options.signal.addEventListener("abort", onAbort, { once: true });
73
+ }
74
+ return {
75
+ home,
76
+ url: ready.url,
77
+ hostId: ready.hostId,
78
+ version: ready.version,
79
+ tenant,
80
+ applicationKey: created.applicationKey,
81
+ admin,
82
+ close,
83
+ };
84
+ }
@@ -0,0 +1,9 @@
1
+ import type { ErrorCode } from "@nylorun/agents";
2
+ /**
3
+ * Map launcher error codes (and a few Host states) to Tenants-era CLI exit codes.
4
+ * See cli/README.md exit code table.
5
+ */
6
+ export declare const LAUNCHER_ERROR_EXIT_CODES: Record<string, number>;
7
+ export declare function exitCodeForLauncherError(code: ErrorCode | string | undefined): number;
8
+ /** `runtime status` when the Host is not running. */
9
+ export declare function exitCodeForStatusState(state: string | undefined): number | undefined;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Map launcher error codes (and a few Host states) to Tenants-era CLI exit codes.
3
+ * See cli/README.md exit code table.
4
+ */
5
+ export const LAUNCHER_ERROR_EXIT_CODES = {
6
+ foreign_port: 4,
7
+ host_start_failed: 7,
8
+ incompatible_host: 5,
9
+ protocol_unsupported: 5,
10
+ // Everything else falls through to 1.
11
+ platform_unsupported: 1,
12
+ launcher_failed: 1,
13
+ lock_timeout: 1,
14
+ host_unresponsive: 1,
15
+ host_schema_newer: 1,
16
+ host_format_newer: 1,
17
+ downgrade_refused: 1,
18
+ upgrade_failed: 1,
19
+ active_work: 1,
20
+ not_found: 1,
21
+ connection_missing: 1,
22
+ host_rejected: 1,
23
+ origin_rejected: 1,
24
+ unsupported_media_type: 1,
25
+ tenant_conflict: 1,
26
+ };
27
+ export function exitCodeForLauncherError(code) {
28
+ if (!code)
29
+ return 1;
30
+ return LAUNCHER_ERROR_EXIT_CODES[code] ?? 1;
31
+ }
32
+ /** `runtime status` when the Host is not running. */
33
+ export function exitCodeForStatusState(state) {
34
+ if (state === "running")
35
+ return undefined;
36
+ if (state === "foreign-port")
37
+ return 4;
38
+ if (state === "stopped" || state === "absent")
39
+ return 3;
40
+ if (state === "unresponsive")
41
+ return 1;
42
+ return 3;
43
+ }
@@ -0,0 +1,8 @@
1
+ export { baselineEnv } from "./baseline.js";
2
+ export { runtimeVersion } from "./version.js";
3
+ export { findOnPath, launcher, runtimeInstallCommand, throwOnLauncherFailure, } from "./launcher.js";
4
+ export type { InstalledRuntime, LauncherHandle, LauncherInvokeResult, LauncherJsonEvent, LauncherOptions, } from "./launcher.js";
5
+ export { runtimeCommand, runtimeUsage } from "./commands.js";
6
+ export { startEphemeral } from "./ephemeral.js";
7
+ export type { EphemeralOptions, EphemeralRuntime, EphemeralTenant, } from "./ephemeral.js";
8
+ export { LAUNCHER_ERROR_EXIT_CODES, exitCodeForLauncherError, exitCodeForStatusState, } from "./exit-codes.js";
@@ -0,0 +1,6 @@
1
+ export { baselineEnv } from "./baseline.js";
2
+ export { runtimeVersion } from "./version.js";
3
+ export { findOnPath, launcher, runtimeInstallCommand, throwOnLauncherFailure, } from "./launcher.js";
4
+ export { runtimeCommand, runtimeUsage } from "./commands.js";
5
+ export { startEphemeral } from "./ephemeral.js";
6
+ export { LAUNCHER_ERROR_EXIT_CODES, exitCodeForLauncherError, exitCodeForStatusState, } from "./exit-codes.js";
@@ -0,0 +1,81 @@
1
+ import { type ProgressEvent } from "./progress.js";
2
+ /** The launcher command `@nylorun/runtime` installs (npm bin). */
3
+ export declare const LAUNCHER_BIN = "nylorun-runtime";
4
+ /** Resolve NYLORUN_HOME, an explicit home, or `~/.nylorun`. */
5
+ export declare function resolveHome(home?: string): string;
6
+ export type LauncherJsonEvent = ProgressEvent | {
7
+ type: "result";
8
+ [field: string]: unknown;
9
+ } | {
10
+ type: "error";
11
+ code: string;
12
+ message: string;
13
+ remedy: string;
14
+ details?: unknown;
15
+ } | {
16
+ type: "log";
17
+ source: "host" | "tenant";
18
+ tenantId?: string;
19
+ line: string;
20
+ };
21
+ export interface LauncherInvokeResult {
22
+ events: LauncherJsonEvent[];
23
+ result?: Record<string, unknown>;
24
+ error?: {
25
+ code: string;
26
+ message: string;
27
+ remedy: string;
28
+ details?: unknown;
29
+ };
30
+ exitCode: number;
31
+ /** Raw stdout (NDJSON). */
32
+ stdout: string;
33
+ stderr: string;
34
+ }
35
+ /** The installed Runtime a handle runs, as its `version` command reports it. */
36
+ export interface InstalledRuntime {
37
+ /** `nylorun-runtime` found on PATH. */
38
+ readonly bin: string;
39
+ readonly version: string;
40
+ /** Node the Runtime reported (the Host runs on the same Node). */
41
+ readonly node: string;
42
+ }
43
+ export interface LauncherHandle {
44
+ readonly home: string;
45
+ readonly runtime: InstalledRuntime;
46
+ /**
47
+ * Run a launcher command with `--json --home`. Progress events are rendered
48
+ * to stderr by default. Returns parsed events and the process exit code.
49
+ */
50
+ invoke(args: readonly string[], options?: InvokeOptions): Promise<LauncherInvokeResult>;
51
+ /**
52
+ * Long-running invoke (e.g. `run`, `logs --follow`). Resolves when the
53
+ * child exits; streams progress/log events as they arrive.
54
+ */
55
+ invokeStreaming(args: readonly string[], options?: InvokeOptions): Promise<LauncherInvokeResult>;
56
+ }
57
+ export interface InvokeOptions {
58
+ env?: NodeJS.ProcessEnv;
59
+ onEvent?: (event: LauncherJsonEvent) => void;
60
+ /** When false, skip default progress rendering. Default true. */
61
+ renderProgress?: boolean;
62
+ signal?: AbortSignal;
63
+ }
64
+ export interface LauncherOptions {
65
+ /** Environment for the launcher, including the PATH it is found on. */
66
+ env?: NodeJS.ProcessEnv;
67
+ }
68
+ /** Install command shown whenever the Runtime is missing or incompatible. */
69
+ export declare function runtimeInstallCommand(): string;
70
+ /** Find `name` on PATH (with PATHEXT on Windows). */
71
+ export declare function findOnPath(name: string, env: NodeJS.ProcessEnv): string | undefined;
72
+ /**
73
+ * Find the installed Runtime's launcher on PATH and check it speaks this CLI's
74
+ * launcher and Host protocols. Never downloads or installs anything: a missing
75
+ * Runtime is a prerequisite error that names the install command.
76
+ */
77
+ export declare function launcher(home: string, options?: LauncherOptions): Promise<LauncherHandle>;
78
+ /**
79
+ * Throw CliError with the Tenants-era exit code for a failed launcher invoke.
80
+ */
81
+ export declare function throwOnLauncherFailure(outcome: LauncherInvokeResult): void;
@@ -0,0 +1,243 @@
1
+ import { spawn } from "node:child_process";
2
+ import { existsSync, realpathSync, statSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { delimiter, dirname, join, resolve } from "node:path";
5
+ import { PROTOCOL_VERSION } from "@nylorun/agents";
6
+ import { CliError } from "../errors.js";
7
+ import { exitCodeForLauncherError, } from "./exit-codes.js";
8
+ import { renderProgress } from "./progress.js";
9
+ import { runtimeVersion } from "./version.js";
10
+ /** The launcher command `@nylorun/runtime` installs (npm bin). */
11
+ export const LAUNCHER_BIN = "nylorun-runtime";
12
+ /** The `nylorun-runtime --json` contract this CLI speaks (core LAUNCHER_PROTOCOL). */
13
+ const LAUNCHER_PROTOCOL = 1;
14
+ /** Resolve NYLORUN_HOME, an explicit home, or `~/.nylorun`. */
15
+ export function resolveHome(home) {
16
+ if (home !== undefined && home.trim() !== "")
17
+ return resolve(home);
18
+ const fromEnv = process.env.NYLORUN_HOME?.trim();
19
+ if (fromEnv)
20
+ return resolve(fromEnv);
21
+ return resolve(join(homedir(), ".nylorun"));
22
+ }
23
+ /** Install command shown whenever the Runtime is missing or incompatible. */
24
+ export function runtimeInstallCommand() {
25
+ return `npm install --global @nylorun/runtime@${runtimeVersion()}`;
26
+ }
27
+ /** Find `name` on PATH (with PATHEXT on Windows). */
28
+ export function findOnPath(name, env) {
29
+ const dirs = (env.PATH ?? env.Path ?? "").split(delimiter).filter(Boolean);
30
+ const extensions = process.platform === "win32"
31
+ ? (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean)
32
+ : [""];
33
+ for (const dir of dirs) {
34
+ for (const extension of extensions) {
35
+ const candidate = join(dir, `${name}${extension.toLowerCase()}`);
36
+ try {
37
+ if (statSync(candidate).isFile())
38
+ return candidate;
39
+ }
40
+ catch {
41
+ /* not here */
42
+ }
43
+ }
44
+ }
45
+ return undefined;
46
+ }
47
+ /**
48
+ * npm links a bin to the package's JS file: a symlink on POSIX, a `.cmd` shim
49
+ * next to the package on Windows. Running that file on this Node avoids
50
+ * spawning `.cmd` shims, which Node refuses without a shell.
51
+ */
52
+ function launcherScript(bin) {
53
+ if (process.platform !== "win32") {
54
+ const real = realpathSync(bin);
55
+ return real.endsWith(".js") ? real : undefined;
56
+ }
57
+ const dir = dirname(bin);
58
+ const main = join("@nylorun", "runtime", "dist", "launcher", "main.js");
59
+ // Global prefix (`<prefix>/nylorun-runtime.cmd`) or project `.bin`.
60
+ for (const candidate of [
61
+ join(dir, "node_modules", main),
62
+ join(dir, "..", main),
63
+ ]) {
64
+ if (existsSync(candidate))
65
+ return candidate;
66
+ }
67
+ return undefined;
68
+ }
69
+ /**
70
+ * Find the installed Runtime's launcher on PATH and check it speaks this CLI's
71
+ * launcher and Host protocols. Never downloads or installs anything: a missing
72
+ * Runtime is a prerequisite error that names the install command.
73
+ */
74
+ export async function launcher(home, options = {}) {
75
+ const resolvedHome = resolve(home);
76
+ const env = options.env ?? process.env;
77
+ const bin = findOnPath(LAUNCHER_BIN, env);
78
+ if (!bin) {
79
+ throw new CliError(`The Nylorun Runtime is not installed (no "${LAUNCHER_BIN}" on PATH).\nInstall it, then retry:\n ${runtimeInstallCommand()}`, 1);
80
+ }
81
+ const probe = await invokeLauncher(bin, resolvedHome, ["version"], { env, renderProgress: false }, false);
82
+ throwOnLauncherFailure(probe);
83
+ const info = probe.result ?? {};
84
+ const version = typeof info.runtimeVersion === "string" ? info.runtimeVersion : "unknown";
85
+ const protocol = info.protocol;
86
+ const hostCompatible = typeof protocol?.min === "number" &&
87
+ typeof protocol.max === "number" &&
88
+ protocol.min <= PROTOCOL_VERSION &&
89
+ PROTOCOL_VERSION <= protocol.max;
90
+ if (info.launcherProtocol !== LAUNCHER_PROTOCOL || !hostCompatible) {
91
+ throw new CliError(`The installed Nylorun Runtime ${version} (${bin}) is not compatible with this CLI.\nInstall a compatible Runtime, then retry:\n ${runtimeInstallCommand()}`, 1);
92
+ }
93
+ return createHandle(resolvedHome, {
94
+ bin,
95
+ version,
96
+ node: typeof info.node === "string" ? info.node : "unknown",
97
+ }, env);
98
+ }
99
+ function createHandle(home, runtime, defaultEnv) {
100
+ const run = (args, options = {}, streaming) => invokeLauncher(runtime.bin, home, args, {
101
+ ...options,
102
+ env: options.env ?? defaultEnv,
103
+ }, streaming);
104
+ return {
105
+ home,
106
+ runtime,
107
+ invoke: (args, options) => run(args, options, false),
108
+ invokeStreaming: (args, options) => run(args, options, true),
109
+ };
110
+ }
111
+ /** Quote one argument for cmd.exe (only used for non-npm `.cmd` shims). */
112
+ function quoteForCmd(value) {
113
+ return `"${value.replace(/"/g, '""')}"`;
114
+ }
115
+ function spawnLauncher(bin, args, env) {
116
+ const options = {
117
+ env: { ...env },
118
+ stdio: ["ignore", "pipe", "pipe"],
119
+ };
120
+ const script = launcherScript(bin);
121
+ if (script)
122
+ return spawn(process.execPath, [script, ...args], options);
123
+ if (process.platform === "win32" && /\.(cmd|bat)$/i.test(bin)) {
124
+ // Windows runs .cmd files only through cmd.exe, which needs quoting.
125
+ return spawn(quoteForCmd(bin), args.map(quoteForCmd), {
126
+ ...options,
127
+ shell: true,
128
+ });
129
+ }
130
+ return spawn(bin, args, options);
131
+ }
132
+ async function invokeLauncher(bin, home, args, options, _streaming) {
133
+ const childArgs = ["--json", "--home", home, ...args];
134
+ const child = spawnLauncher(bin, childArgs, options.env);
135
+ const events = [];
136
+ let result;
137
+ let error;
138
+ let stdout = "";
139
+ let stderr = "";
140
+ let lineBuffer = "";
141
+ const handleLine = (line) => {
142
+ const trimmed = line.trim();
143
+ if (!trimmed)
144
+ return;
145
+ let event;
146
+ try {
147
+ event = JSON.parse(trimmed);
148
+ }
149
+ catch {
150
+ return;
151
+ }
152
+ events.push(event);
153
+ options.onEvent?.(event);
154
+ if (event.type === "progress" && options.renderProgress !== false) {
155
+ renderProgress(event);
156
+ }
157
+ if (event.type === "result") {
158
+ const { type: _t, ...fields } = event;
159
+ result = fields;
160
+ }
161
+ if (event.type === "error") {
162
+ error = {
163
+ code: event.code,
164
+ message: event.message,
165
+ remedy: event.remedy,
166
+ ...(event.details !== undefined ? { details: event.details } : {}),
167
+ };
168
+ }
169
+ if (event.type === "log") {
170
+ const prefix = event.source === "tenant" && event.tenantId
171
+ ? `[${event.tenantId}] `
172
+ : "";
173
+ process.stdout.write(`${prefix}${event.line}\n`);
174
+ }
175
+ };
176
+ child.stdout?.on("data", (chunk) => {
177
+ const text = chunk.toString("utf8");
178
+ stdout += text;
179
+ lineBuffer += text;
180
+ const parts = lineBuffer.split("\n");
181
+ lineBuffer = parts.pop() ?? "";
182
+ for (const part of parts)
183
+ handleLine(part);
184
+ });
185
+ child.stderr?.on("data", (chunk) => {
186
+ stderr += chunk.toString("utf8");
187
+ });
188
+ const abort = () => {
189
+ try {
190
+ child.kill("SIGTERM");
191
+ }
192
+ catch {
193
+ /* ignore */
194
+ }
195
+ };
196
+ if (options.signal) {
197
+ if (options.signal.aborted)
198
+ abort();
199
+ else
200
+ options.signal.addEventListener("abort", abort, { once: true });
201
+ }
202
+ const exitCode = await waitForExit(child);
203
+ if (lineBuffer.trim())
204
+ handleLine(lineBuffer);
205
+ if (options.signal) {
206
+ options.signal.removeEventListener("abort", abort);
207
+ }
208
+ return {
209
+ events,
210
+ ...(result ? { result } : {}),
211
+ ...(error ? { error } : {}),
212
+ exitCode,
213
+ stdout,
214
+ stderr,
215
+ };
216
+ }
217
+ function waitForExit(child) {
218
+ return new Promise((resolvePromise, reject) => {
219
+ child.once("error", reject);
220
+ child.once("exit", (code, signal) => {
221
+ if (code !== null)
222
+ resolvePromise(code);
223
+ else
224
+ resolvePromise(signal === "SIGINT" ? 130 : signal === "SIGTERM" ? 143 : 1);
225
+ });
226
+ });
227
+ }
228
+ /**
229
+ * Throw CliError with the Tenants-era exit code for a failed launcher invoke.
230
+ */
231
+ export function throwOnLauncherFailure(outcome) {
232
+ if (outcome.exitCode === 0)
233
+ return;
234
+ if (outcome.exitCode === 2) {
235
+ throw new CliError(outcome.error?.message ??
236
+ (outcome.stderr.trim() || "Invalid runtime arguments."), 2);
237
+ }
238
+ const code = outcome.error?.code;
239
+ const message = outcome.error
240
+ ? `${outcome.error.message}${outcome.error.remedy ? `\n${outcome.error.remedy}` : ""}`
241
+ : outcome.stderr.trim() || `Runtime launcher failed (exit ${outcome.exitCode}).`;
242
+ throw new CliError(message, exitCodeForLauncherError(code));
243
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Render launcher progress the way the Tenants-era CLI did: phase name on
3
+ * stderr.
4
+ */
5
+ export type ProgressEvent = {
6
+ type: "progress";
7
+ phase: "start" | "stop" | "wait";
8
+ message?: string;
9
+ };
10
+ export declare function renderProgress(event: ProgressEvent, write?: (line: string) => void): void;
@@ -0,0 +1,8 @@
1
+ export function renderProgress(event, write = (line) => {
2
+ process.stderr.write(`${line}\n`);
3
+ }) {
4
+ const parts = [event.phase];
5
+ if (event.message)
6
+ parts.push(event.message);
7
+ write(parts.join(" "));
8
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Recommended Runtime version from `cli/package.json` `nylorun.runtime` (D7):
3
+ * the version named in install instructions. Compatibility itself is checked
4
+ * by launcher and Host protocol, not by an exact match.
5
+ */
6
+ export declare function runtimeVersion(): string;
@@ -0,0 +1,27 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { CliError } from "../errors.js";
5
+ function cliPackageJsonPath() {
6
+ // src/runtime/*.ts and dist/runtime/*.js both sit two levels below cli/.
7
+ return join(dirname(fileURLToPath(import.meta.url)), "..", "..", "package.json");
8
+ }
9
+ /**
10
+ * Recommended Runtime version from `cli/package.json` `nylorun.runtime` (D7):
11
+ * the version named in install instructions. Compatibility itself is checked
12
+ * by launcher and Host protocol, not by an exact match.
13
+ */
14
+ export function runtimeVersion() {
15
+ let raw;
16
+ try {
17
+ raw = JSON.parse(readFileSync(cliPackageJsonPath(), "utf8"));
18
+ }
19
+ catch (error) {
20
+ throw new CliError(`Cannot read CLI package.json: ${error instanceof Error ? error.message : String(error)}`, 1);
21
+ }
22
+ const version = raw?.nylorun?.runtime;
23
+ if (typeof version !== "string" || version.trim() === "") {
24
+ throw new CliError('cli/package.json is missing "nylorun.runtime" (the recommended Runtime version).', 1);
25
+ }
26
+ return version;
27
+ }
@@ -0,0 +1,4 @@
1
+ import { type AdminTenant } from "@nylorun/admin";
2
+ /** Exact-and-unique name or id match. */
3
+ export declare function matchTenant(tenants: readonly AdminTenant[], nameOrId: string): AdminTenant;
4
+ export declare function tenantCommand(args: readonly string[]): Promise<void>;