nylorun 0.3.0-beta → 0.4.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.
@@ -0,0 +1,27 @@
1
+ type FetchLike = (input: string, init?: RequestInit) => Promise<Response>;
2
+ export interface SeedResult {
3
+ /** Settings the seed set, and the ones the Tenant already had. */
4
+ applied: string[];
5
+ kept: string[];
6
+ /** `provider/model` stored in the Tenant's vault. */
7
+ model?: string;
8
+ }
9
+ /**
10
+ * The Project's `.env` as a value map. It is never applied to this process or written back:
11
+ * a Project's `.env` never configures the Host.
12
+ */
13
+ export declare function readProjectEnv(projectRoot: string): Record<string, string>;
14
+ /**
15
+ * Seed a new Project's Tenant from the Project's `.env`: the sandbox backend
16
+ * (`NYLORUN_SANDBOX=auto|virtual`, `PUT /v1/tenant/config/seed`, which keeps settings the
17
+ * Tenant has) and the model credential (`MODEL_PROVIDER`, `MODEL`, `MODEL_PROVIDER_API_KEY`,
18
+ * `MODEL_PROVIDER_BASE_URL`, or `.nylorun/auth.json`), stored in the Tenant's vault unless the
19
+ * Tenant has a model or `NYLORUN_DEV_MODEL=fixture`.
20
+ */
21
+ export declare function seedTenant(input: {
22
+ fetch: FetchLike;
23
+ runtimeUrl: string;
24
+ applicationKey: string;
25
+ projectRoot: string;
26
+ }): Promise<SeedResult>;
27
+ export {};
@@ -0,0 +1,111 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { readFileSync, statSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { parseEnv } from "node:util";
5
+ import { PROTOCOL_HEADER, PROTOCOL_VERSION } from "@nylorun/core/compatibility";
6
+ /**
7
+ * The Project's `.env` as a value map. It is never applied to this process or written back:
8
+ * a Project's `.env` never configures the Host.
9
+ */
10
+ export function readProjectEnv(projectRoot) {
11
+ const file = join(projectRoot, ".env");
12
+ try {
13
+ if (!statSync(file).isFile())
14
+ return {};
15
+ const result = {};
16
+ for (const [key, value] of Object.entries(parseEnv(readFileSync(file, "utf8"))))
17
+ if (value !== undefined)
18
+ result[key] = value;
19
+ return result;
20
+ }
21
+ catch {
22
+ return {};
23
+ }
24
+ }
25
+ function modelFromEnv(env, projectRoot) {
26
+ const provider = env.MODEL_PROVIDER?.trim();
27
+ const model = env.MODEL?.trim();
28
+ if (!provider || !model)
29
+ return undefined;
30
+ const baseUrl = env.MODEL_PROVIDER_BASE_URL?.trim();
31
+ const base = { provider, model, ...(baseUrl ? { baseUrl } : {}) };
32
+ const key = env.MODEL_PROVIDER_API_KEY;
33
+ if (key)
34
+ return { ...base, auth: { type: "api_key", key } };
35
+ for (const relative of [".nylorun/auth.json", ".env/auth.json"]) {
36
+ try {
37
+ const all = JSON.parse(readFileSync(join(projectRoot, relative), "utf8"));
38
+ const credential = all[provider];
39
+ if (credential?.type === "api_key" || credential?.type === "oauth")
40
+ return { ...base, auth: credential };
41
+ }
42
+ catch {
43
+ /* absent */
44
+ }
45
+ }
46
+ return undefined;
47
+ }
48
+ async function call(fetch, url, applicationKey, init = {}) {
49
+ const response = await fetch(url, {
50
+ ...(init.method ? { method: init.method } : {}),
51
+ headers: {
52
+ authorization: `Bearer ${applicationKey}`,
53
+ [PROTOCOL_HEADER]: String(PROTOCOL_VERSION),
54
+ accept: "application/json",
55
+ ...(init.body === undefined ? {} : { "content-type": "application/json" }),
56
+ },
57
+ ...(init.body === undefined ? {} : { body: JSON.stringify(init.body) }),
58
+ redirect: "error",
59
+ signal: AbortSignal.timeout(15_000),
60
+ });
61
+ const body = (await response.json().catch(() => ({})));
62
+ if (!response.ok)
63
+ throw new Error(`${init.method ?? "GET"} ${new URL(url).pathname} returned ${response.status}${typeof body.message === "string" ? `: ${body.message}` : ""}`);
64
+ return body;
65
+ }
66
+ /**
67
+ * Seed a new Project's Tenant from the Project's `.env`: the sandbox backend
68
+ * (`NYLORUN_SANDBOX=auto|virtual`, `PUT /v1/tenant/config/seed`, which keeps settings the
69
+ * Tenant has) and the model credential (`MODEL_PROVIDER`, `MODEL`, `MODEL_PROVIDER_API_KEY`,
70
+ * `MODEL_PROVIDER_BASE_URL`, or `.nylorun/auth.json`), stored in the Tenant's vault unless the
71
+ * Tenant has a model or `NYLORUN_DEV_MODEL=fixture`.
72
+ */
73
+ export async function seedTenant(input) {
74
+ const env = readProjectEnv(input.projectRoot);
75
+ const base = input.runtimeUrl.replace(/\/$/, "");
76
+ const result = { applied: [], kept: [] };
77
+ const sandbox = env.NYLORUN_SANDBOX?.trim();
78
+ if (sandbox === "auto" || sandbox === "virtual") {
79
+ const body = (await call(input.fetch, `${base}/v1/tenant/config/seed`, input.applicationKey, {
80
+ method: "PUT",
81
+ body: { requestId: randomUUID(), sandbox: { backend: sandbox } },
82
+ }));
83
+ if (Array.isArray(body.applied))
84
+ result.applied = body.applied.map(String);
85
+ if (Array.isArray(body.kept))
86
+ result.kept = body.kept.map(String);
87
+ }
88
+ if (env.NYLORUN_DEV_MODEL?.trim() === "fixture")
89
+ return result;
90
+ const seeded = modelFromEnv(env, input.projectRoot);
91
+ if (!seeded)
92
+ return result;
93
+ const current = (await call(input.fetch, `${base}/v1/tenant/model`, input.applicationKey));
94
+ if (current.configured === true) {
95
+ result.kept.push("model");
96
+ return result;
97
+ }
98
+ await call(input.fetch, `${base}/v1/tenant/model`, input.applicationKey, {
99
+ method: "PUT",
100
+ body: {
101
+ requestId: randomUUID(),
102
+ idempotencyKey: randomUUID(),
103
+ provider: seeded.provider,
104
+ model: seeded.model,
105
+ ...(seeded.baseUrl ? { baseUrl: seeded.baseUrl } : {}),
106
+ auth: seeded.auth,
107
+ },
108
+ });
109
+ result.model = `${seeded.provider}/${seeded.model}`;
110
+ return result;
111
+ }
@@ -1,10 +1,11 @@
1
+ import type { HostTenant } from "@nylorun/core/contracts";
1
2
  import { type ComposeService, type DockerRunner } from "./docker.js";
2
3
  import type { PortProbe } from "./ports.js";
3
4
  import { type FetchLike } from "./studio-login.js";
4
- export declare const STACK_SERVICES: readonly ["postgres", "restate", "s2", "runtime", "studio"];
5
+ export declare const STACK_SERVICES: readonly ["postgres", "restate", "s2", "gateway", "runtime", "studio"];
5
6
  export declare const stackUsage: string;
6
7
  export interface StackDeps {
7
- /** Environment snapshot (NYLORUN_HOME, image overrides, NYLORUN_STACK_PROJECT). */
8
+ /** Environment snapshot (NYLORUN_HOME, NYLORUN_STACK, image overrides, NYLORUN_STACK_PROJECT). */
8
9
  env: Readonly<Record<string, string | undefined>>;
9
10
  docker: DockerRunner;
10
11
  fetch: FetchLike;
@@ -23,14 +24,19 @@ export interface StackDeps {
23
24
  openBrowser(url: string): Promise<boolean>;
24
25
  /** Is this process id alive? (launcher-managed Runtime detection) */
25
26
  pidAlive(pid: number): boolean;
27
+ /** Where the project is looked for (default: the process's working directory). */
28
+ cwd?: string;
29
+ /** `~/.nylorun`: the stacks and the legacy stack (tests use a temporary directory). */
30
+ nylorunRoot?: string;
26
31
  /** Delay between health polls; tests shorten it. */
27
32
  pollMs?: number;
28
- /** How long `start` waits for the Runtime's /health after Compose. */
33
+ /** How long `start` waits for the Runtime's /health, and then its Tenant, after Compose. */
29
34
  healthTimeoutMs?: number;
30
35
  /** How long a Studio login is retried before warning. */
31
36
  loginTimeoutMs?: number;
32
37
  }
33
- export declare function stackProject(env: StackDeps["env"]): string;
38
+ /** The Compose project of stack `name`: `nylorun-<name>`, or `NYLORUN_STACK_PROJECT`. */
39
+ export declare function stackProject(env: StackDeps["env"], name: string): string;
34
40
  interface Flags {
35
41
  rest: string[];
36
42
  booleans: Set<string>;
@@ -44,12 +50,16 @@ export declare function parseStackFlags(args: readonly string[], allowed: {
44
50
  lists?: readonly string[];
45
51
  aliases?: Record<string, string>;
46
52
  }, usage: string): Flags;
53
+ /** The Host's one Tenant as `/v1/admin/status` reports it (no envelope). */
54
+ export type StackTenant = Pick<HostTenant, "id" | "name" | "state" | "cause">;
47
55
  /** Printed by `start` when it does not open Studio itself. */
48
56
  export declare const STUDIO_SIGN_IN_HINT = "To sign a browser in to Studio, run \"npx nylorun studio\".";
49
- /** Printed by `start` while the Host has no Tenant: nylorun never creates one. */
50
- export declare const TENANT_HINT = "No Tenant yet. Create one in Studio, or run `npx @nylorun/cli tenant create` in your project.";
51
57
  export interface StackStatus {
58
+ /** The stack's name. */
59
+ name: string;
60
+ /** The Compose project. */
52
61
  project: string;
62
+ /** The Host root. */
53
63
  home: string;
54
64
  state: "running" | "stopped" | "absent";
55
65
  runtime: {
@@ -59,8 +69,9 @@ export interface StackStatus {
59
69
  healthy: boolean;
60
70
  version?: string;
61
71
  hostId?: string;
62
- tenants?: number;
63
72
  };
73
+ /** The stack's one Tenant, while the Runtime answers. */
74
+ tenant?: StackTenant;
64
75
  studio: {
65
76
  url?: string;
66
77
  state: string;
@@ -70,16 +81,27 @@ export interface StackStatus {
70
81
  restate: {
71
82
  url?: string;
72
83
  };
84
+ /** The gateway container (the Model Gate), in the combined packing. */
85
+ gateway: {
86
+ state: string;
87
+ healthy: boolean;
88
+ };
73
89
  services: ComposeService[];
74
90
  }
75
91
  /** What `nylorun status` reports, as data (`nylorun doctor`). */
76
- export declare function readStackStatus(deps: StackDeps): Promise<StackStatus>;
92
+ export declare function readStackStatus(deps: StackDeps, options?: {
93
+ name?: string;
94
+ }): Promise<StackStatus>;
77
95
  /** The running stack as clients reach it. */
78
96
  export interface StackEndpoints {
79
- /** The Host root (`NYLORUN_HOME` or `~/.nylorun`). */
97
+ /** The stack's name. */
98
+ name: string;
99
+ /** The Host root. */
80
100
  home: string;
81
101
  /** `http://localhost:<port>` */
82
102
  runtimeUrl: string;
103
+ /** The operator listener (Admin API). */
104
+ adminUrl: string;
83
105
  hostId: string;
84
106
  adminKey: string;
85
107
  studioPort: number;
@@ -89,27 +111,28 @@ export interface StackEndpoints {
89
111
  started: boolean;
90
112
  }
91
113
  /**
92
- * Start the stack unless it is already running (`nylorun studio`): the
93
- * `start` code path without its own output. Compose progress
94
- * still streams, since the first run pulls images.
114
+ * Start the selected stack unless it is already running (`nylorun studio`): the
115
+ * `start` code path without its output, its Tenant wait or the Project link. Compose
116
+ * progress still streams, since the first run pulls images.
95
117
  */
96
118
  export declare function ensureStack(deps: StackDeps, options: {
97
119
  studio: boolean;
120
+ name?: string;
98
121
  }): Promise<StackEndpoints>;
99
122
  /** Add Studio's `next` path (e.g. `/tenants/<id>`) to a login URL. */
100
123
  export declare function withNext(loginUrl: string, next: string | undefined): string;
101
- /** The Studio page for one Tenant, as a login `next` path. */
124
+ /** The Studio page of the stack's Tenant, as a login `next` path. */
102
125
  export declare function tenantStudioPath(tenantId: string): string;
103
126
  /**
104
127
  * Mint a fresh Studio login URL on a running stack, landing on `next`.
105
128
  * Undefined (after a warning) when Studio does not answer.
106
129
  */
107
130
  export declare function studioLoginUrl(deps: StackDeps, stack: Pick<StackEndpoints, "studioPort" | "adminKey">, next?: string): Promise<string | undefined>;
108
- /** `nylorun studio`, landing on `next` (the linked Project's Tenant) when given. */
131
+ /** `nylorun studio`, landing on `next` when given, else on the stack's Tenant. */
109
132
  export declare function runStudioCommand(args: readonly string[], deps: StackDeps, options?: {
110
133
  next?: string;
111
134
  }): Promise<number>;
112
135
  export declare function isStackCommand(name: string | undefined): boolean;
113
- /** Run one stack command (`up`/`start`, `down`/`stop`, `status`, `logs`, `reset`, `studio`). */
136
+ /** Run one stack command (`up`/`start`, `down`/`stop`, `status`, `logs`, `reset`, `studio`, `ls`, `delete`, `legacy`). */
114
137
  export declare function runStackCommand(name: string, args: readonly string[], deps: StackDeps): Promise<number>;
115
138
  export {};