vyft 0.2.0-alpha → 0.3.0-alpha
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 +5 -16
- package/dist/cli.js +170 -9
- package/dist/context.d.ts +39 -0
- package/dist/context.js +101 -0
- package/dist/docker.d.ts +14 -9
- package/dist/docker.js +145 -317
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/init.js +19 -2
- package/dist/interpolate.d.ts +11 -0
- package/dist/interpolate.js +11 -0
- package/dist/proxy.d.ts +16 -0
- package/dist/proxy.js +0 -0
- package/dist/resource.d.ts +97 -1
- package/dist/resource.js +11 -1
- package/dist/runtime.d.ts +4 -0
- package/dist/services/index.d.ts +24 -0
- package/dist/services/index.js +20 -0
- package/dist/services/minio.d.ts +36 -0
- package/dist/services/minio.js +53 -0
- package/dist/services/mongo.d.ts +28 -0
- package/dist/services/mongo.js +45 -0
- package/dist/services/mysql.d.ts +28 -0
- package/dist/services/mysql.js +44 -0
- package/dist/services/nats.d.ts +26 -0
- package/dist/services/nats.js +38 -0
- package/dist/services/postgres.d.ts +28 -0
- package/dist/services/postgres.js +45 -0
- package/dist/services/rabbitmq.d.ts +28 -0
- package/dist/services/rabbitmq.js +44 -0
- package/dist/services/redis.d.ts +28 -0
- package/dist/services/redis.js +49 -0
- package/dist/services/storage.d.ts +39 -0
- package/dist/services/storage.js +94 -0
- package/dist/swarm/factories.d.ts +9 -2
- package/dist/swarm/factories.js +9 -32
- package/dist/swarm/index.d.ts +11 -2
- package/dist/swarm/proxy.d.ts +24 -0
- package/dist/swarm/proxy.js +339 -0
- package/dist/swarm/types.d.ts +11 -21
- package/dist/symbols.d.ts +5 -0
- package/dist/symbols.js +1 -0
- package/package.json +2 -5
- package/templates/fullstack/vyft.config.ts +13 -28
package/README.md
CHANGED
|
@@ -14,28 +14,17 @@ This scaffolds a fullstack project with a Hono API, React SPA, and Postgres —
|
|
|
14
14
|
|
|
15
15
|
```typescript
|
|
16
16
|
// vyft.config.ts
|
|
17
|
-
import {
|
|
18
|
-
import { swarm } from 'vyft/swarm';
|
|
17
|
+
import { service, secret, postgres, site } from 'vyft';
|
|
19
18
|
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
export const dbPassword = secret('db-password', { length: 32 });
|
|
23
|
-
export const dbData = volume('db-data', { size: '10GB' });
|
|
24
|
-
|
|
25
|
-
export const db = service('db', {
|
|
26
|
-
image: 'postgres:17',
|
|
27
|
-
volumes: [{ volume: dbData, mount: '/var/lib/postgresql/data' }],
|
|
28
|
-
env: {
|
|
29
|
-
POSTGRES_PASSWORD: dbPassword,
|
|
30
|
-
POSTGRES_DB: 'myapp',
|
|
31
|
-
}
|
|
32
|
-
});
|
|
19
|
+
export const authSecret = secret('auth-secret', { length: 64 });
|
|
20
|
+
export const db = postgres('db');
|
|
33
21
|
|
|
34
22
|
export const api = service('api', {
|
|
35
23
|
route: 'example.com/api/*',
|
|
36
24
|
image: { dockerfile: './apps/api/Dockerfile' },
|
|
37
25
|
env: {
|
|
38
|
-
DATABASE_URL:
|
|
26
|
+
DATABASE_URL: db.url,
|
|
27
|
+
AUTH_SECRET: authSecret,
|
|
39
28
|
}
|
|
40
29
|
});
|
|
41
30
|
|
package/dist/cli.js
CHANGED
|
@@ -4,6 +4,8 @@ import { access, readFile } from "node:fs/promises";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { intro, log, outro } from "@clack/prompts";
|
|
6
6
|
import { Command } from "commander";
|
|
7
|
+
import pc from "picocolors";
|
|
8
|
+
import { getCurrentContextName, listContexts, removeContext, resolveContext, saveContext, setCurrentContext, } from "./context.js";
|
|
7
9
|
import { DockerClient } from "./docker.js";
|
|
8
10
|
import { init } from "./init.js";
|
|
9
11
|
import { logger } from "./logger.js";
|
|
@@ -67,23 +69,47 @@ function collectResources(exports) {
|
|
|
67
69
|
}
|
|
68
70
|
return resources;
|
|
69
71
|
}
|
|
72
|
+
function topoSortServices(services) {
|
|
73
|
+
const byId = new Map(services.map((s) => [s.id, s]));
|
|
74
|
+
const visited = new Set();
|
|
75
|
+
const result = [];
|
|
76
|
+
function visit(svc) {
|
|
77
|
+
if (visited.has(svc.id))
|
|
78
|
+
return;
|
|
79
|
+
visited.add(svc.id);
|
|
80
|
+
for (const dep of svc.config.dependsOn ?? []) {
|
|
81
|
+
const depSvc = byId.get(dep.id);
|
|
82
|
+
if (depSvc)
|
|
83
|
+
visit(depSvc);
|
|
84
|
+
}
|
|
85
|
+
result.push(svc);
|
|
86
|
+
}
|
|
87
|
+
for (const svc of services)
|
|
88
|
+
visit(svc);
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
70
91
|
function hasRuntime(value) {
|
|
71
92
|
return (typeof value === "object" &&
|
|
72
93
|
value !== null &&
|
|
73
94
|
VYFT_RUNTIME in value);
|
|
74
95
|
}
|
|
96
|
+
function getContextHost() {
|
|
97
|
+
const ctx = resolveContext(program.opts().context);
|
|
98
|
+
return ctx?.host;
|
|
99
|
+
}
|
|
75
100
|
function createRuntime(resources, project, sessionLogger) {
|
|
101
|
+
const host = getContextHost();
|
|
76
102
|
const ref = resources.find((r) => hasRuntime(r));
|
|
77
103
|
if (ref && hasRuntime(ref)) {
|
|
78
104
|
const meta = ref[VYFT_RUNTIME];
|
|
79
105
|
switch (meta.name) {
|
|
80
106
|
case "swarm":
|
|
81
|
-
return new DockerClient(project, sessionLogger);
|
|
107
|
+
return new DockerClient(project, { host, parentLogger: sessionLogger });
|
|
82
108
|
default:
|
|
83
109
|
throw new Error(`Unknown runtime: ${meta.name}`);
|
|
84
110
|
}
|
|
85
111
|
}
|
|
86
|
-
return new DockerClient(project, sessionLogger);
|
|
112
|
+
return new DockerClient(project, { host, parentLogger: sessionLogger });
|
|
87
113
|
}
|
|
88
114
|
async function deploy(configFile, verbose) {
|
|
89
115
|
const sessionId = randomBytes(4).toString("hex");
|
|
@@ -101,13 +127,25 @@ async function deploy(configFile, verbose) {
|
|
|
101
127
|
}
|
|
102
128
|
sessionLog.info({ project, resourceCount: resources.length }, "deploy started");
|
|
103
129
|
resources.sort((a, b) => DEPLOY_ORDER[a.type] - DEPLOY_ORDER[b.type]);
|
|
130
|
+
// Topologically sort services so dependencies deploy first
|
|
131
|
+
const services = resources.filter((r) => r.type === "service");
|
|
132
|
+
const nonServices = resources.filter((r) => r.type !== "service");
|
|
133
|
+
const sorted = topoSortServices(services);
|
|
134
|
+
const ordered = [...nonServices, ...sorted];
|
|
135
|
+
// Collect which service IDs have dependents waiting on them
|
|
136
|
+
const depTargets = new Set();
|
|
137
|
+
for (const svc of services) {
|
|
138
|
+
for (const dep of svc.config.dependsOn ?? []) {
|
|
139
|
+
depTargets.add(dep.id);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
104
142
|
const docker = createRuntime(resources, project, sessionLog);
|
|
105
143
|
docker.verbose = verbose;
|
|
106
144
|
await docker.ensureInfrastructure();
|
|
107
145
|
const currentResources = await docker.listManagedResources();
|
|
108
146
|
let created = 0;
|
|
109
147
|
let skipped = 0;
|
|
110
|
-
for (const resource of
|
|
148
|
+
for (const resource of ordered) {
|
|
111
149
|
const exists = await docker.exists(resource);
|
|
112
150
|
if (exists && resource.type !== "service" && resource.type !== "site") {
|
|
113
151
|
log.success(resource.id);
|
|
@@ -117,6 +155,10 @@ async function deploy(configFile, verbose) {
|
|
|
117
155
|
await docker.create(resource);
|
|
118
156
|
created++;
|
|
119
157
|
}
|
|
158
|
+
// Wait for services that other services depend on
|
|
159
|
+
if (resource.type === "service" && depTargets.has(resource.id)) {
|
|
160
|
+
await docker.waitForHealthy(resource.id);
|
|
161
|
+
}
|
|
120
162
|
}
|
|
121
163
|
const desiredIds = new Set(resources.map((r) => r.id));
|
|
122
164
|
// Keep derived secrets whose parent service is still desired
|
|
@@ -141,13 +183,16 @@ async function deploy(configFile, verbose) {
|
|
|
141
183
|
sessionLog.info({ project, created, removed, skipped, durationMs }, "deploy completed");
|
|
142
184
|
outro("Deploy complete");
|
|
143
185
|
}
|
|
144
|
-
async function destroy(searchDir) {
|
|
186
|
+
async function destroy(searchDir, host) {
|
|
145
187
|
const sessionId = randomBytes(4).toString("hex");
|
|
146
188
|
const sessionLog = logger.child({ sessionId, command: "destroy" });
|
|
147
189
|
const start = performance.now();
|
|
148
190
|
const project = await findProjectName(path.join(searchDir, "dummy"));
|
|
149
191
|
intro(`Destroying ${project}`);
|
|
150
|
-
const docker = new DockerClient(project,
|
|
192
|
+
const docker = new DockerClient(project, {
|
|
193
|
+
host,
|
|
194
|
+
parentLogger: sessionLog,
|
|
195
|
+
});
|
|
151
196
|
const currentResources = await docker.listManagedResources();
|
|
152
197
|
if (currentResources.length === 0) {
|
|
153
198
|
log.warn("No resources found");
|
|
@@ -173,7 +218,8 @@ const program = new Command();
|
|
|
173
218
|
program
|
|
174
219
|
.name("vyft")
|
|
175
220
|
.description("Deploy apps to Docker Swarm with TypeScript")
|
|
176
|
-
.version("0.1.0")
|
|
221
|
+
.version("0.1.0")
|
|
222
|
+
.option("--context <name>", "override active context for this invocation");
|
|
177
223
|
program
|
|
178
224
|
.command("init")
|
|
179
225
|
.description("Create a new project")
|
|
@@ -212,12 +258,51 @@ proxy
|
|
|
212
258
|
.option("--tail <n>", "number of lines to show", "100")
|
|
213
259
|
.action(async (opts) => {
|
|
214
260
|
const project = await findProjectName(path.join(process.cwd(), "package.json"));
|
|
215
|
-
const docker = new DockerClient(project);
|
|
216
|
-
await docker.
|
|
261
|
+
const docker = new DockerClient(project, { host: getContextHost() });
|
|
262
|
+
await docker.proxy.logs({
|
|
217
263
|
follow: opts.follow,
|
|
218
264
|
tail: parseInt(opts.tail, 10),
|
|
219
265
|
});
|
|
220
266
|
});
|
|
267
|
+
program
|
|
268
|
+
.command("logs")
|
|
269
|
+
.description("View service logs")
|
|
270
|
+
.argument("[services...]", "service names to filter (short names without project prefix)")
|
|
271
|
+
.option("-f, --follow", "stream logs continuously", false)
|
|
272
|
+
.option("--tail <n>", "number of lines to show", "100")
|
|
273
|
+
.action(async (services, opts) => {
|
|
274
|
+
const project = await findProjectName(path.join(process.cwd(), "package.json"));
|
|
275
|
+
const docker = new DockerClient(project, { host: getContextHost() });
|
|
276
|
+
// Support comma-separated names: "api,web" -> ["api", "web"]
|
|
277
|
+
const parsed = services.flatMap((s) => s.split(","));
|
|
278
|
+
const logs = docker.serviceLogs({
|
|
279
|
+
follow: opts.follow,
|
|
280
|
+
tail: parseInt(opts.tail, 10),
|
|
281
|
+
services: parsed.length > 0 ? parsed : undefined,
|
|
282
|
+
});
|
|
283
|
+
const colors = [
|
|
284
|
+
pc.cyan,
|
|
285
|
+
pc.magenta,
|
|
286
|
+
pc.green,
|
|
287
|
+
pc.yellow,
|
|
288
|
+
pc.blue,
|
|
289
|
+
pc.red,
|
|
290
|
+
];
|
|
291
|
+
const colorMap = new Map();
|
|
292
|
+
let colorIdx = 0;
|
|
293
|
+
for await (const entry of logs) {
|
|
294
|
+
if (!colorMap.has(entry.service)) {
|
|
295
|
+
colorMap.set(entry.service, colors[colorIdx++ % colors.length]);
|
|
296
|
+
}
|
|
297
|
+
const color = colorMap.get(entry.service);
|
|
298
|
+
const prefix = parsed.length === 1
|
|
299
|
+
? ""
|
|
300
|
+
: `${color(pc.bold(entry.service))} ${pc.dim("|")} `;
|
|
301
|
+
const line = entry.text.endsWith("\n") ? entry.text : `${entry.text}\n`;
|
|
302
|
+
const dest = entry.stream === "stderr" ? process.stderr : process.stdout;
|
|
303
|
+
dest.write(`${prefix}${line}`);
|
|
304
|
+
}
|
|
305
|
+
});
|
|
221
306
|
program
|
|
222
307
|
.command("destroy")
|
|
223
308
|
.description("Destroy all deployed resources")
|
|
@@ -227,11 +312,87 @@ program
|
|
|
227
312
|
? path.resolve(process.cwd(), configFile)
|
|
228
313
|
: process.cwd();
|
|
229
314
|
try {
|
|
230
|
-
await destroy(absolutePath);
|
|
315
|
+
await destroy(absolutePath, getContextHost());
|
|
231
316
|
}
|
|
232
317
|
catch (err) {
|
|
233
318
|
logger.fatal({ err }, "destroy failed");
|
|
234
319
|
throw err;
|
|
235
320
|
}
|
|
236
321
|
});
|
|
322
|
+
const context = program
|
|
323
|
+
.command("context")
|
|
324
|
+
.description("Manage deployment contexts");
|
|
325
|
+
context
|
|
326
|
+
.command("create")
|
|
327
|
+
.description("Create a new context")
|
|
328
|
+
.argument("<name>", "context name")
|
|
329
|
+
.requiredOption("--host <endpoint>", "Docker endpoint (e.g. ssh://root@1.2.3.4)")
|
|
330
|
+
.option("--runtime <rt>", "runtime backend", "swarm")
|
|
331
|
+
.option("--description <text>", "optional description")
|
|
332
|
+
.action((name, opts) => {
|
|
333
|
+
saveContext({
|
|
334
|
+
name,
|
|
335
|
+
host: opts.host,
|
|
336
|
+
runtime: opts.runtime,
|
|
337
|
+
description: opts.description,
|
|
338
|
+
});
|
|
339
|
+
console.log(`Context "${name}" created`);
|
|
340
|
+
});
|
|
341
|
+
context
|
|
342
|
+
.command("ls")
|
|
343
|
+
.description("List all contexts")
|
|
344
|
+
.action(() => {
|
|
345
|
+
const contexts = listContexts();
|
|
346
|
+
if (contexts.length === 0) {
|
|
347
|
+
console.log("No contexts configured. Create one with: vyft context create <name> --host <endpoint>");
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
const current = getCurrentContextName();
|
|
351
|
+
for (const ctx of contexts) {
|
|
352
|
+
const marker = ctx.name === current ? "* " : " ";
|
|
353
|
+
const desc = ctx.description ? ` - ${ctx.description}` : "";
|
|
354
|
+
const host = ctx.host ? ` (${ctx.host})` : "";
|
|
355
|
+
console.log(`${marker}${ctx.name}${host}${desc}`);
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
context
|
|
359
|
+
.command("show")
|
|
360
|
+
.description("Print current context name")
|
|
361
|
+
.action(() => {
|
|
362
|
+
const name = getCurrentContextName();
|
|
363
|
+
if (!name) {
|
|
364
|
+
console.log("No context set");
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
console.log(name);
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
context
|
|
371
|
+
.command("use")
|
|
372
|
+
.description("Set the active context")
|
|
373
|
+
.argument("<name>", "context name")
|
|
374
|
+
.action((name) => {
|
|
375
|
+
setCurrentContext(name);
|
|
376
|
+
console.log(`Switched to context "${name}"`);
|
|
377
|
+
});
|
|
378
|
+
context
|
|
379
|
+
.command("rm")
|
|
380
|
+
.description("Remove a context")
|
|
381
|
+
.argument("<name>", "context name")
|
|
382
|
+
.action((name) => {
|
|
383
|
+
removeContext(name);
|
|
384
|
+
console.log(`Context "${name}" removed`);
|
|
385
|
+
});
|
|
386
|
+
context
|
|
387
|
+
.command("inspect")
|
|
388
|
+
.description("Print context as JSON")
|
|
389
|
+
.argument("[name]", "context name (defaults to current)")
|
|
390
|
+
.action((name) => {
|
|
391
|
+
const ctx = resolveContext(name);
|
|
392
|
+
if (!ctx) {
|
|
393
|
+
console.log("No context set");
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
console.log(JSON.stringify(ctx, null, 2));
|
|
397
|
+
});
|
|
237
398
|
await program.parseAsync();
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** A named deployment target storing a host endpoint and runtime backend. */
|
|
2
|
+
export interface VyftContext {
|
|
3
|
+
/** Unique context name (e.g. `"prod"`, `"staging"`). */
|
|
4
|
+
name: string;
|
|
5
|
+
/** Docker endpoint (e.g. `"ssh://root@1.2.3.4"`). `undefined` means local socket. */
|
|
6
|
+
host?: string;
|
|
7
|
+
/** Runtime backend. Currently only `"swarm"`. */
|
|
8
|
+
runtime: string;
|
|
9
|
+
/** Optional human-readable description. */
|
|
10
|
+
description?: string;
|
|
11
|
+
}
|
|
12
|
+
/** Returns the vyft config directory, creating it if necessary. Respects `$XDG_CONFIG_HOME`. */
|
|
13
|
+
export declare function configDir(): string;
|
|
14
|
+
/** Load a context by name. Returns `undefined` if no matching file exists. */
|
|
15
|
+
export declare function getContext(name: string): VyftContext | undefined;
|
|
16
|
+
/** List all explicitly created contexts. */
|
|
17
|
+
export declare function listContexts(): VyftContext[];
|
|
18
|
+
/**
|
|
19
|
+
* Persist a context to disk.
|
|
20
|
+
*/
|
|
21
|
+
export declare function saveContext(ctx: VyftContext): void;
|
|
22
|
+
/**
|
|
23
|
+
* Delete a context from disk.
|
|
24
|
+
* @throws If the context is currently active or doesn't exist.
|
|
25
|
+
*/
|
|
26
|
+
export declare function removeContext(name: string): void;
|
|
27
|
+
/** Read the active context name from `config.json`. Returns `undefined` when no context is set. */
|
|
28
|
+
export declare function getCurrentContextName(): string | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Set the active context.
|
|
31
|
+
* @throws If the named context doesn't exist.
|
|
32
|
+
*/
|
|
33
|
+
export declare function setCurrentContext(name: string): void;
|
|
34
|
+
/**
|
|
35
|
+
* Resolve a context by name. If no override is given, uses the current active context.
|
|
36
|
+
* Returns `undefined` when no context is configured and no override is provided.
|
|
37
|
+
* @throws If a named context doesn't exist.
|
|
38
|
+
*/
|
|
39
|
+
export declare function resolveContext(override?: string): VyftContext | undefined;
|
package/dist/context.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync, } from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
/** Returns the vyft config directory, creating it if necessary. Respects `$XDG_CONFIG_HOME`. */
|
|
5
|
+
export function configDir() {
|
|
6
|
+
const xdg = process.env.XDG_CONFIG_HOME;
|
|
7
|
+
const dir = xdg
|
|
8
|
+
? path.join(xdg, "vyft")
|
|
9
|
+
: path.join(os.homedir(), ".config", "vyft");
|
|
10
|
+
mkdirSync(path.join(dir, "contexts"), { recursive: true });
|
|
11
|
+
return dir;
|
|
12
|
+
}
|
|
13
|
+
/** Load a context by name. Returns `undefined` if no matching file exists. */
|
|
14
|
+
export function getContext(name) {
|
|
15
|
+
const file = path.join(configDir(), "contexts", `${name}.json`);
|
|
16
|
+
if (!existsSync(file))
|
|
17
|
+
return undefined;
|
|
18
|
+
return JSON.parse(readFileSync(file, "utf-8"));
|
|
19
|
+
}
|
|
20
|
+
/** List all explicitly created contexts. */
|
|
21
|
+
export function listContexts() {
|
|
22
|
+
const dir = configDir();
|
|
23
|
+
const contextsDir = path.join(dir, "contexts");
|
|
24
|
+
const results = [];
|
|
25
|
+
if (!existsSync(contextsDir))
|
|
26
|
+
return results;
|
|
27
|
+
for (const file of readdirSync(contextsDir)) {
|
|
28
|
+
if (!file.endsWith(".json"))
|
|
29
|
+
continue;
|
|
30
|
+
try {
|
|
31
|
+
const ctx = JSON.parse(readFileSync(path.join(contextsDir, file), "utf-8"));
|
|
32
|
+
results.push(ctx);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// skip malformed files
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return results;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Persist a context to disk.
|
|
42
|
+
*/
|
|
43
|
+
export function saveContext(ctx) {
|
|
44
|
+
const file = path.join(configDir(), "contexts", `${ctx.name}.json`);
|
|
45
|
+
writeFileSync(file, `${JSON.stringify(ctx, null, 2)}\n`);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Delete a context from disk.
|
|
49
|
+
* @throws If the context is currently active or doesn't exist.
|
|
50
|
+
*/
|
|
51
|
+
export function removeContext(name) {
|
|
52
|
+
const current = getCurrentContextName();
|
|
53
|
+
if (name === current) {
|
|
54
|
+
throw new Error(`Cannot remove "${name}" because it is the active context. Switch first with: vyft context use <other>`);
|
|
55
|
+
}
|
|
56
|
+
const file = path.join(configDir(), "contexts", `${name}.json`);
|
|
57
|
+
if (!existsSync(file)) {
|
|
58
|
+
throw new Error(`Context "${name}" does not exist`);
|
|
59
|
+
}
|
|
60
|
+
rmSync(file);
|
|
61
|
+
}
|
|
62
|
+
/** Read the active context name from `config.json`. Returns `undefined` when no context is set. */
|
|
63
|
+
export function getCurrentContextName() {
|
|
64
|
+
const file = path.join(configDir(), "config.json");
|
|
65
|
+
if (!existsSync(file))
|
|
66
|
+
return undefined;
|
|
67
|
+
try {
|
|
68
|
+
const config = JSON.parse(readFileSync(file, "utf-8"));
|
|
69
|
+
return config.currentContext;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Set the active context.
|
|
77
|
+
* @throws If the named context doesn't exist.
|
|
78
|
+
*/
|
|
79
|
+
export function setCurrentContext(name) {
|
|
80
|
+
const ctx = getContext(name);
|
|
81
|
+
if (!ctx)
|
|
82
|
+
throw new Error(`Context "${name}" does not exist`);
|
|
83
|
+
const file = path.join(configDir(), "config.json");
|
|
84
|
+
const config = { currentContext: name };
|
|
85
|
+
writeFileSync(file, `${JSON.stringify(config, null, 2)}\n`);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Resolve a context by name. If no override is given, uses the current active context.
|
|
89
|
+
* Returns `undefined` when no context is configured and no override is provided.
|
|
90
|
+
* @throws If a named context doesn't exist.
|
|
91
|
+
*/
|
|
92
|
+
export function resolveContext(override) {
|
|
93
|
+
const name = override ?? getCurrentContextName();
|
|
94
|
+
if (!name)
|
|
95
|
+
return undefined;
|
|
96
|
+
const ctx = getContext(name);
|
|
97
|
+
if (!ctx) {
|
|
98
|
+
throw new Error(`Context "${name}" not found`);
|
|
99
|
+
}
|
|
100
|
+
return ctx;
|
|
101
|
+
}
|
package/dist/docker.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { Logger } from "pino";
|
|
2
|
+
import type { ReverseProxy } from "./proxy.js";
|
|
2
3
|
import type { Resource, ResourceType } from "./resource.js";
|
|
3
4
|
import type { Runtime } from "./runtime.js";
|
|
4
5
|
export declare function parseRoute(route: string): {
|
|
5
6
|
host: string;
|
|
6
7
|
path?: string;
|
|
7
8
|
};
|
|
8
|
-
export declare function buildCaddyRoute(resourceId: string, route: string, handler: Record<string, unknown>): Record<string, unknown>;
|
|
9
9
|
export interface ManagedResource {
|
|
10
10
|
id: string;
|
|
11
11
|
type: ResourceType;
|
|
@@ -20,15 +20,14 @@ export declare class DockerClient implements Runtime {
|
|
|
20
20
|
private project;
|
|
21
21
|
private secretValues;
|
|
22
22
|
private log;
|
|
23
|
+
proxy: ReverseProxy;
|
|
23
24
|
verbose: boolean;
|
|
24
|
-
constructor(project: string,
|
|
25
|
+
constructor(project: string, opts?: {
|
|
26
|
+
host?: string;
|
|
27
|
+
parentLogger?: Logger;
|
|
28
|
+
});
|
|
25
29
|
private ensureNetwork;
|
|
26
30
|
ensureInfrastructure(): Promise<void>;
|
|
27
|
-
private findProxyContainer;
|
|
28
|
-
private caddyApiRequest;
|
|
29
|
-
private seedCaddyConfig;
|
|
30
|
-
private addRoute;
|
|
31
|
-
private removeRoute;
|
|
32
31
|
listManagedResources(): Promise<ManagedResource[]>;
|
|
33
32
|
create(resource: Resource): Promise<void>;
|
|
34
33
|
exists(resource: Resource): Promise<boolean>;
|
|
@@ -45,13 +44,19 @@ export declare class DockerClient implements Runtime {
|
|
|
45
44
|
private resolveEnv;
|
|
46
45
|
private createService;
|
|
47
46
|
private serviceExists;
|
|
47
|
+
waitForHealthy(id: string, timeoutMs?: number): Promise<void>;
|
|
48
48
|
private removeService;
|
|
49
49
|
private pullImage;
|
|
50
50
|
private createStatic;
|
|
51
51
|
private removeStatic;
|
|
52
|
-
|
|
52
|
+
serviceLogs(options: {
|
|
53
53
|
follow?: boolean;
|
|
54
54
|
tail?: number;
|
|
55
|
-
|
|
55
|
+
services?: string[];
|
|
56
|
+
}): AsyncGenerator<{
|
|
57
|
+
service: string;
|
|
58
|
+
stream: "stdout" | "stderr";
|
|
59
|
+
text: string;
|
|
60
|
+
}>;
|
|
56
61
|
removeProjectNetwork(): Promise<void>;
|
|
57
62
|
}
|