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.
- package/CHANGELOG.md +42 -0
- package/README.md +136 -60
- package/dist/cli.js +13 -11
- package/dist/doctor.d.ts +3 -1
- package/dist/doctor.js +42 -13
- package/dist/project/derived-key.d.ts +8 -0
- package/dist/project/derived-key.js +18 -0
- package/dist/project/link.d.ts +32 -4
- package/dist/project/link.js +83 -14
- package/dist/project/seed.d.ts +27 -0
- package/dist/project/seed.js +111 -0
- package/dist/stack/commands.d.ts +38 -15
- package/dist/stack/commands.js +517 -144
- package/dist/stack/compose-file.d.ts +30 -11
- package/dist/stack/compose-file.js +84 -16
- package/dist/stack/env-file.d.ts +19 -3
- package/dist/stack/env-file.js +44 -4
- package/dist/stack/host-files.d.ts +9 -0
- package/dist/stack/host-files.js +24 -1
- package/dist/stack/index.d.ts +3 -3
- package/dist/stack/index.js +8 -3
- package/dist/stack/paths.d.ts +11 -3
- package/dist/stack/paths.js +9 -6
- package/dist/stack/prepare.d.ts +10 -3
- package/dist/stack/prepare.js +23 -12
- package/dist/stack/restate-identity.d.ts +1 -1
- package/dist/stack/restate-identity.js +1 -1
- package/dist/stack/stacks.d.ts +47 -0
- package/dist/stack/stacks.js +116 -0
- package/package.json +4 -4
|
@@ -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
|
+
}
|
package/dist/stack/commands.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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`
|
|
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 {};
|