nylorun 0.4.0-beta → 0.6.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 (42) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +207 -105
  3. package/dist/cli.js +24 -22
  4. package/dist/doctor.d.ts +10 -8
  5. package/dist/doctor.js +54 -37
  6. package/dist/home.d.ts +1 -1
  7. package/dist/home.js +1 -1
  8. package/dist/project/derived-key.d.ts +2 -2
  9. package/dist/project/derived-key.js +2 -2
  10. package/dist/project/link.d.ts +7 -7
  11. package/dist/project/link.js +3 -3
  12. package/dist/stack/commands.d.ts +23 -15
  13. package/dist/stack/commands.js +332 -214
  14. package/dist/stack/compose-file.d.ts +11 -8
  15. package/dist/stack/compose-file.js +54 -21
  16. package/dist/stack/docker.d.ts +10 -0
  17. package/dist/stack/docker.js +64 -1
  18. package/dist/stack/env-file.d.ts +12 -2
  19. package/dist/stack/env-file.js +13 -3
  20. package/dist/stack/host-files.d.ts +4 -4
  21. package/dist/stack/host-files.js +3 -3
  22. package/dist/stack/images.d.ts +4 -2
  23. package/dist/stack/images.js +3 -2
  24. package/dist/stack/index.d.ts +4 -3
  25. package/dist/stack/index.js +10 -4
  26. package/dist/stack/paths.d.ts +3 -3
  27. package/dist/stack/paths.js +1 -1
  28. package/dist/stack/ports.d.ts +4 -2
  29. package/dist/stack/ports.js +3 -1
  30. package/dist/stack/prepare.d.ts +7 -3
  31. package/dist/stack/prepare.js +5 -3
  32. package/dist/stack/proxy.d.ts +78 -0
  33. package/dist/stack/proxy.js +224 -0
  34. package/dist/stack/stacks.d.ts +37 -34
  35. package/dist/stack/stacks.js +91 -57
  36. package/dist/stack/studio-login.d.ts +4 -3
  37. package/dist/stack/studio-login.js +5 -4
  38. package/dist/stack/versions.d.ts +1 -1
  39. package/dist/stack/versions.js +1 -1
  40. package/dist/telemetry.d.ts +31 -0
  41. package/dist/telemetry.js +79 -0
  42. package/package.json +5 -5
package/dist/doctor.js CHANGED
@@ -1,12 +1,12 @@
1
1
  import { CliError } from "./errors.js";
2
- import { checkDocker, defaultStackDeps, readStackStatus, } from "./stack/index.js";
3
- /** Oldest Node the stack's tooling and the developer's application run on. */
2
+ import { checkDocker, defaultStackDeps, readProxyReport, readStackStatus, } from "./stack/index.js";
3
+ /** Oldest Node nylorun and the developer's application run on. */
4
4
  const MIN_NODE_MAJOR = 24;
5
5
  /**
6
- * `nylorun doctor`: check the prerequisites of the local stack (Node 24+,
7
- * Docker, Compose v2) and, when they pass, the stack's health. Prints the fix
6
+ * `nylorun doctor`: check the prerequisites of a local Tenant (Node 24+,
7
+ * Docker, Compose v2) and, when they pass, the Tenant's health. Prints the fix
8
8
  * for each problem and returns 1 when a prerequisite is missing or a running
9
- * stack is unhealthy; installs and starts nothing.
9
+ * Tenant is unhealthy; installs and starts nothing.
10
10
  */
11
11
  export async function doctorStack(options) {
12
12
  const log = options.log ?? ((line) => console.log(line));
@@ -20,7 +20,7 @@ export async function doctorStack(options) {
20
20
  if (checks.docker.ok && checks.compose?.ok) {
21
21
  try {
22
22
  const status = await readStackStatus(deps);
23
- report.stack = {
23
+ report.tenant = {
24
24
  name: status.name,
25
25
  project: status.project,
26
26
  home: status.home,
@@ -34,14 +34,15 @@ export async function doctorStack(options) {
34
34
  catch (error) {
35
35
  if (!(error instanceof CliError) || error.exitCode !== 2)
36
36
  throw error;
37
- report.noStack = error.message;
37
+ report.noTenant = error.message;
38
38
  }
39
+ report.proxy = await readProxyReport(deps);
39
40
  }
40
- const stackBroken = report.stack?.state === "running" &&
41
- (!report.stack.runtime.healthy ||
42
- !report.stack.gateway.healthy ||
43
- report.stack.tenant?.cause !== undefined);
44
- const failed = !nodeOk || !checks.docker.ok || checks.compose?.ok === false || stackBroken;
41
+ const broken = report.tenant?.state === "running" &&
42
+ (!report.tenant.runtime.healthy ||
43
+ !report.tenant.gateway.healthy ||
44
+ report.tenant.tenant?.cause !== undefined);
45
+ const failed = !nodeOk || !checks.docker.ok || checks.compose?.ok === false || broken;
45
46
  if (options.json) {
46
47
  log(JSON.stringify(report, null, 2));
47
48
  return failed ? 1 : 0;
@@ -61,43 +62,59 @@ export async function doctorStack(options) {
61
62
  ["docker", line(checks.docker)],
62
63
  ["compose", line(checks.compose)],
63
64
  ];
64
- const stack = report.stack;
65
- if (report.noStack)
66
- rows.push(["stack", `- ${report.noStack}`]);
67
- if (stack) {
65
+ const local = report.tenant;
66
+ if (report.noTenant)
67
+ rows.push(["tenant", `- ${report.noTenant}`]);
68
+ if (local) {
68
69
  rows.push([
69
- "stack",
70
- stack.state === "absent"
71
- ? `- ${stack.name} not created under ${stack.home}: run nylorun up`
72
- : stack.state === "stopped"
73
- ? `- ${stack.name} stopped (Compose project ${stack.project}): run nylorun up`
74
- : stack.runtime.healthy
75
- ? `✓ ${stack.name} running (Compose project ${stack.project})`
76
- : `✗ ${stack.name} running, but the Runtime does not answer: see nylorun status and nylorun logs runtime`,
70
+ "tenant",
71
+ local.state === "absent"
72
+ ? `- ${local.name} not created under ${local.home}: run nylorun up`
73
+ : local.state === "stopped"
74
+ ? `- ${local.name} stopped (Compose project ${local.project}): run nylorun up`
75
+ : local.runtime.healthy
76
+ ? `✓ ${local.name} running (Compose project ${local.project})`
77
+ : `✗ ${local.name} running, but the Runtime does not answer: see nylorun status and nylorun logs runtime`,
77
78
  ]);
78
- if (stack.state === "running") {
79
+ if (local.state === "running") {
79
80
  rows.push([
80
81
  "runtime",
81
- stack.runtime.healthy
82
- ? `✓ ${stack.runtime.url ?? "?"} · ${stack.runtime.version ?? "?"}`
83
- : `✗ ${stack.runtime.url ?? "?"} not answering`,
82
+ local.runtime.healthy
83
+ ? `✓ ${local.runtime.url ?? "?"} · ${local.runtime.version ?? "?"}`
84
+ : `✗ ${local.runtime.url ?? "?"} not answering`,
84
85
  ]);
85
86
  rows.push([
86
87
  "gateway",
87
- stack.gateway.healthy
88
- ? `✓ ${stack.gateway.state} · combined packing (runtime: core,loop; gateway: gates)`
89
- : `✗ ${stack.gateway.state}: model calls fail; see nylorun logs gateway`,
88
+ local.gateway.healthy
89
+ ? `✓ ${local.gateway.state} · combined packing (runtime: core,loop; gateway: gates)`
90
+ : `✗ ${local.gateway.state}: model calls fail; see nylorun logs gateway`,
90
91
  ]);
91
- if (stack.tenant)
92
+ if (local.tenant)
92
93
  rows.push([
93
- "tenant",
94
- stack.tenant.state === "open"
95
- ? `✓ ${stack.tenant.id ?? "?"} open`
96
- : `✗ ${stack.tenant.id ?? "?"} unavailable${stack.tenant.cause ? `: ${stack.tenant.cause.code}: ${stack.tenant.cause.repair}` : " (opening)"}`,
94
+ "tenant id",
95
+ local.tenant.state === "open"
96
+ ? `✓ ${local.tenant.id ?? "?"} open`
97
+ : `✗ ${local.tenant.id ?? "?"} unavailable${local.tenant.cause ? `: ${local.tenant.cause.code}: ${local.tenant.cause.repair}` : " (opening)"}`,
97
98
  ]);
98
- rows.push(["studio", `${stack.studio.url ?? "?"} · ${stack.studio.state}`]);
99
+ rows.push(["studio", `${local.studio.proxyUrl ?? local.studio.url ?? "?"} · ${local.studio.state}`]);
99
100
  }
100
101
  }
102
+ const proxy = report.proxy;
103
+ if (proxy) {
104
+ const on = proxy.port
105
+ ? `127.0.0.1:${proxy.port}${proxy.ipv6 ? ` and [::1]:${proxy.port}` : ""}`
106
+ : "";
107
+ rows.push([
108
+ "proxy",
109
+ proxy.state === "running"
110
+ ? `✓ running on ${on} (http://<tenant>.localhost:${proxy.port ?? "?"})`
111
+ : proxy.state === "disabled"
112
+ ? "- disabled (NYLORUN_PROXY_DISABLED): Studio is on each Tenant's own port"
113
+ : proxy.state === "stopped"
114
+ ? `- stopped${on ? ` (${on})` : ""}: nylorun start starts it`
115
+ : "- not created yet: nylorun start creates it",
116
+ ]);
117
+ }
101
118
  const width = Math.max(...rows.map(([key]) => key.length)) + 2;
102
119
  for (const [key, value] of rows)
103
120
  log(` ${key.padEnd(width)}${value}`);
package/dist/home.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The Host root: an explicit home, `NYLORUN_HOME`, or `~/.nylorun`. The stack
2
+ * The Host root: an explicit home, `NYLORUN_HOME`, or `~/.nylorun`. Compose
3
3
  * bind-mounts it into the Runtime and Studio containers, and `@nylorun/admin`
4
4
  * reads `host.json` and the admin key from it.
5
5
  */
package/dist/home.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { homedir } from "node:os";
2
2
  import { join, resolve } from "node:path";
3
3
  /**
4
- * The Host root: an explicit home, `NYLORUN_HOME`, or `~/.nylorun`. The stack
4
+ * The Host root: an explicit home, `NYLORUN_HOME`, or `~/.nylorun`. Compose
5
5
  * bind-mounts it into the Runtime and Studio containers, and `@nylorun/admin`
6
6
  * reads `host.json` and the admin key from it.
7
7
  */
@@ -1,7 +1,7 @@
1
- /** The derived principal a Project uses; the stack's Runtime registers it on its Tenant. */
1
+ /** The derived principal a Project uses; the local Runtime registers it on its Tenant. */
2
2
  export declare const PROJECT_PRINCIPAL_ID = "project";
3
3
  /**
4
- * Key of a derived principal on the stack's Tenant, from the admin key. A copy of
4
+ * Key of a derived principal on the local Tenant, from the admin key. A copy of
5
5
  * `deriveTenantKey` in `@nylorun/admin` (nylorun depends on `@nylorun/core` only); both are
6
6
  * tested against the same fixed vector.
7
7
  */
@@ -1,8 +1,8 @@
1
1
  import { createHmac } from "node:crypto";
2
- /** The derived principal a Project uses; the stack's Runtime registers it on its Tenant. */
2
+ /** The derived principal a Project uses; the local Runtime registers it on its Tenant. */
3
3
  export const PROJECT_PRINCIPAL_ID = "project";
4
4
  /**
5
- * Key of a derived principal on the stack's Tenant, from the admin key. A copy of
5
+ * Key of a derived principal on the local Tenant, from the admin key. A copy of
6
6
  * `deriveTenantKey` in `@nylorun/admin` (nylorun depends on `@nylorun/core` only); both are
7
7
  * tested against the same fixed vector.
8
8
  */
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * The Project link (`.nylorun/link.json`) and credentials (`.nylorun/credentials.json`).
3
- * `nylorun start` writes both, format 2: the stack the Project uses, its URL and Host id, and
4
- * the Tenant id as information (nothing selects a Tenant). Formats 0 and 1 named a Tenant on a
5
- * multi-Tenant Host of an older Runtime.
3
+ * `nylorun start` writes both, format 3: the local Tenant's name, its URL and Host id, and
4
+ * the Tenant id as information (nothing in a request selects a Tenant). `start` replaces a link
5
+ * of an older format.
6
6
  */
7
7
  export interface ProjectLink {
8
- format: 0 | 1 | 2;
9
- /** The local stack (format 2). */
10
- stack?: string;
8
+ format: 0 | 1 | 2 | 3;
9
+ /** The local Tenant's name (format 3). */
10
+ tenant?: string;
11
11
  hostUrl: string;
12
12
  hostId: string;
13
13
  tenantId?: string;
@@ -17,7 +17,7 @@ export declare function credentialsPath(projectRoot: string): string;
17
17
  /** The Project's link, or undefined when it has none. An unreadable link is an error. */
18
18
  export declare function readProjectLink(projectRoot: string): Promise<ProjectLink | undefined>;
19
19
  export declare function writeProjectLink(projectRoot: string, link: {
20
- stack: string;
20
+ tenant: string;
21
21
  hostUrl: string;
22
22
  hostId: string;
23
23
  tenantId: string;
@@ -33,7 +33,7 @@ export async function readProjectLink(projectRoot) {
33
33
  const link = parsed.data;
34
34
  return {
35
35
  format: link.format,
36
- ...(link.stack ? { stack: link.stack } : {}),
36
+ ...(link.format === 3 && link.tenant ? { tenant: link.tenant } : {}),
37
37
  hostUrl: link.hostUrl.replace(/\/$/, ""),
38
38
  hostId: link.hostId,
39
39
  ...(link.tenantId ? { tenantId: link.tenantId } : {}),
@@ -60,8 +60,8 @@ async function ensureProjectDir(projectRoot) {
60
60
  export async function writeProjectLink(projectRoot, link) {
61
61
  await ensureProjectDir(projectRoot);
62
62
  await writePrivate(linkPath(projectRoot), {
63
- format: 2,
64
- stack: link.stack,
63
+ format: 3,
64
+ tenant: link.tenant,
65
65
  hostUrl: link.hostUrl.replace(/\/$/, ""),
66
66
  hostId: link.hostId,
67
67
  tenantId: link.tenantId,
@@ -1,11 +1,12 @@
1
1
  import type { HostTenant } from "@nylorun/core/contracts";
2
2
  import { type ComposeService, type DockerRunner } from "./docker.js";
3
3
  import type { PortProbe } from "./ports.js";
4
+ import { type ProxyStatus } from "./proxy.js";
4
5
  import { type FetchLike } from "./studio-login.js";
5
- export declare const STACK_SERVICES: readonly ["postgres", "restate", "s2", "gateway", "runtime", "studio"];
6
+ export declare const STACK_SERVICES: readonly ["postgres", "restate", "s2-lite", "gateway", "runtime", "studio"];
6
7
  export declare const stackUsage: string;
7
8
  export interface StackDeps {
8
- /** Environment snapshot (NYLORUN_HOME, NYLORUN_STACK, image overrides, NYLORUN_STACK_PROJECT). */
9
+ /** Environment snapshot (NYLORUN_HOME, NYLORUN_TENANT, image overrides, NYLORUN_COMPOSE_PROJECT). */
9
10
  env: Readonly<Record<string, string | undefined>>;
10
11
  docker: DockerRunner;
11
12
  fetch: FetchLike;
@@ -26,7 +27,7 @@ export interface StackDeps {
26
27
  pidAlive(pid: number): boolean;
27
28
  /** Where the project is looked for (default: the process's working directory). */
28
29
  cwd?: string;
29
- /** `~/.nylorun`: the stacks and the legacy stack (tests use a temporary directory). */
30
+ /** `~/.nylorun`: the Tenants (tests use a temporary directory). */
30
31
  nylorunRoot?: string;
31
32
  /** Delay between health polls; tests shorten it. */
32
33
  pollMs?: number;
@@ -35,7 +36,7 @@ export interface StackDeps {
35
36
  /** How long a Studio login is retried before warning. */
36
37
  loginTimeoutMs?: number;
37
38
  }
38
- /** The Compose project of stack `name`: `nylorun-<name>`, or `NYLORUN_STACK_PROJECT`. */
39
+ /** The Compose project of Tenant `name`: `nylorun-<name>`, or `NYLORUN_COMPOSE_PROJECT`. */
39
40
  export declare function stackProject(env: StackDeps["env"], name: string): string;
40
41
  interface Flags {
41
42
  rest: string[];
@@ -55,7 +56,7 @@ export type StackTenant = Pick<HostTenant, "id" | "name" | "state" | "cause">;
55
56
  /** Printed by `start` when it does not open Studio itself. */
56
57
  export declare const STUDIO_SIGN_IN_HINT = "To sign a browser in to Studio, run \"npx nylorun studio\".";
57
58
  export interface StackStatus {
58
- /** The stack's name. */
59
+ /** The Tenant's name. */
59
60
  name: string;
60
61
  /** The Compose project. */
61
62
  project: string;
@@ -64,16 +65,19 @@ export interface StackStatus {
64
65
  state: "running" | "stopped" | "absent";
65
66
  runtime: {
66
67
  url?: string;
67
- /** The Admin API (operator listener), when the stack publishes one. */
68
+ /** The Admin API (operator listener), when the Tenant publishes one. */
68
69
  adminUrl?: string;
69
70
  healthy: boolean;
70
71
  version?: string;
71
72
  hostId?: string;
72
73
  };
73
- /** The stack's one Tenant, while the Runtime answers. */
74
+ /** The Tenant as the Runtime reports it, while it answers. */
74
75
  tenant?: StackTenant;
75
76
  studio: {
77
+ /** Studio's own port: what an embedding app frames (same site as its own localhost). */
76
78
  url?: string;
79
+ /** Through the Studio proxy, when it serves the Tenant. */
80
+ proxyUrl?: string;
77
81
  state: string;
78
82
  /** Exact origins that may show Studio in a frame (Studio §8.9). */
79
83
  embedOrigins?: string[];
@@ -88,13 +92,15 @@ export interface StackStatus {
88
92
  };
89
93
  services: ComposeService[];
90
94
  }
95
+ /** The Studio proxy, as data (`nylorun doctor`). */
96
+ export declare function readProxyReport(deps: StackDeps): Promise<ProxyStatus>;
91
97
  /** What `nylorun status` reports, as data (`nylorun doctor`). */
92
98
  export declare function readStackStatus(deps: StackDeps, options?: {
93
99
  name?: string;
94
100
  }): Promise<StackStatus>;
95
- /** The running stack as clients reach it. */
101
+ /** The running Tenant as clients reach it. */
96
102
  export interface StackEndpoints {
97
- /** The stack's name. */
103
+ /** The Tenant's name. */
98
104
  name: string;
99
105
  /** The Host root. */
100
106
  home: string;
@@ -105,13 +111,15 @@ export interface StackEndpoints {
105
111
  hostId: string;
106
112
  adminKey: string;
107
113
  studioPort: number;
114
+ /** Through the proxy when it serves the Tenant, else Studio's own port. */
115
+ studioUrl: string;
108
116
  /** Studio is running and healthy. */
109
117
  studioUp: boolean;
110
118
  /** This call started the Runtime (it was not running before). */
111
119
  started: boolean;
112
120
  }
113
121
  /**
114
- * Start the selected stack unless it is already running (`nylorun studio`): the
122
+ * Start the selected Tenant unless it is already running (`nylorun studio`): the
115
123
  * `start` code path without its output, its Tenant wait or the Project link. Compose
116
124
  * progress still streams, since the first run pulls images.
117
125
  */
@@ -121,18 +129,18 @@ export declare function ensureStack(deps: StackDeps, options: {
121
129
  }): Promise<StackEndpoints>;
122
130
  /** Add Studio's `next` path (e.g. `/tenants/<id>`) to a login URL. */
123
131
  export declare function withNext(loginUrl: string, next: string | undefined): string;
124
- /** The Studio page of the stack's Tenant, as a login `next` path. */
132
+ /** The Tenant's Studio page, as a login `next` path. */
125
133
  export declare function tenantStudioPath(tenantId: string): string;
126
134
  /**
127
- * Mint a fresh Studio login URL on a running stack, landing on `next`.
135
+ * Mint a fresh Studio login URL on a running Tenant, landing on `next`.
128
136
  * Undefined (after a warning) when Studio does not answer.
129
137
  */
130
- export declare function studioLoginUrl(deps: StackDeps, stack: Pick<StackEndpoints, "studioPort" | "adminKey">, next?: string): Promise<string | undefined>;
131
- /** `nylorun studio`, landing on `next` when given, else on the stack's Tenant. */
138
+ export declare function studioLoginUrl(deps: StackDeps, running: Pick<StackEndpoints, "studioPort" | "studioUrl" | "adminKey">, next?: string): Promise<string | undefined>;
139
+ /** `nylorun studio`, landing on `next` when given, else on the Tenant's page. */
132
140
  export declare function runStudioCommand(args: readonly string[], deps: StackDeps, options?: {
133
141
  next?: string;
134
142
  }): Promise<number>;
135
143
  export declare function isStackCommand(name: string | undefined): boolean;
136
- /** Run one stack command (`up`/`start`, `down`/`stop`, `status`, `logs`, `reset`, `studio`, `ls`, `delete`, `legacy`). */
144
+ /** Run one Tenant command (`up`/`start`, `down`/`stop`, `status`, `logs`, `reset`, `studio`, `ls`, `delete`). */
137
145
  export declare function runStackCommand(name: string, args: readonly string[], deps: StackDeps): Promise<number>;
138
146
  export {};