vyft 0.4.0-alpha → 0.4.2-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/LICENSE +191 -21
- package/README.md +3 -49
- package/dist/commands/context/add.d.ts +4 -0
- package/dist/commands/context/add.d.ts.map +1 -0
- package/dist/commands/context/add.js +98 -0
- package/dist/commands/context/add.js.map +1 -0
- package/dist/commands/context/index.d.ts +4 -0
- package/dist/commands/context/index.d.ts.map +1 -0
- package/dist/commands/context/index.js +12 -0
- package/dist/commands/context/index.js.map +1 -0
- package/dist/commands/context/list.d.ts +4 -0
- package/dist/commands/context/list.d.ts.map +1 -0
- package/dist/commands/context/list.js +25 -0
- package/dist/commands/context/list.js.map +1 -0
- package/dist/commands/context/remove.d.ts +4 -0
- package/dist/commands/context/remove.d.ts.map +1 -0
- package/dist/commands/context/remove.js +36 -0
- package/dist/commands/context/remove.js.map +1 -0
- package/dist/commands/context/use.d.ts +4 -0
- package/dist/commands/context/use.d.ts.map +1 -0
- package/dist/commands/context/use.js +32 -0
- package/dist/commands/context/use.js.map +1 -0
- package/dist/commands/deploy.d.ts +4 -0
- package/dist/commands/deploy.d.ts.map +1 -0
- package/dist/commands/deploy.js +55 -0
- package/dist/commands/deploy.js.map +1 -0
- package/dist/commands/destroy.d.ts +4 -0
- package/dist/commands/destroy.d.ts.map +1 -0
- package/dist/commands/destroy.js +70 -0
- package/dist/commands/destroy.js.map +1 -0
- package/dist/commands/diff.d.ts +4 -0
- package/dist/commands/diff.d.ts.map +1 -0
- package/dist/commands/diff.js +52 -0
- package/dist/commands/diff.js.map +1 -0
- package/dist/commands/init.d.ts +4 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +92 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/local/detect.d.ts +7 -0
- package/dist/commands/local/detect.d.ts.map +1 -0
- package/dist/commands/local/detect.js +146 -0
- package/dist/commands/local/detect.js.map +1 -0
- package/dist/commands/local/dev.d.ts +4 -0
- package/dist/commands/local/dev.d.ts.map +1 -0
- package/dist/commands/local/dev.js +387 -0
- package/dist/commands/local/dev.js.map +1 -0
- package/dist/commands/local/down.d.ts +4 -0
- package/dist/commands/local/down.d.ts.map +1 -0
- package/dist/commands/local/down.js +61 -0
- package/dist/commands/local/down.js.map +1 -0
- package/dist/commands/local/index.d.ts +4 -0
- package/dist/commands/local/index.d.ts.map +1 -0
- package/dist/commands/local/index.js +12 -0
- package/dist/commands/local/index.js.map +1 -0
- package/dist/commands/local/reset.d.ts +4 -0
- package/dist/commands/local/reset.d.ts.map +1 -0
- package/dist/commands/local/reset.js +67 -0
- package/dist/commands/local/reset.js.map +1 -0
- package/dist/commands/local/up.d.ts +4 -0
- package/dist/commands/local/up.d.ts.map +1 -0
- package/dist/commands/local/up.js +58 -0
- package/dist/commands/local/up.js.map +1 -0
- package/dist/commands/refresh.d.ts +4 -0
- package/dist/commands/refresh.d.ts.map +1 -0
- package/dist/commands/refresh.js +39 -0
- package/dist/commands/refresh.js.map +1 -0
- package/dist/config.d.ts +8 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +65 -0
- package/dist/config.js.map +1 -0
- package/dist/contexts.d.ts +21 -0
- package/dist/contexts.d.ts.map +1 -0
- package/dist/contexts.js +72 -0
- package/dist/contexts.js.map +1 -0
- package/dist/index.d.ts +2 -9
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -7
- package/dist/index.js.map +1 -0
- package/dist/lib.d.ts +7 -0
- package/dist/lib.d.ts.map +1 -0
- package/dist/lib.js +6 -0
- package/dist/lib.js.map +1 -0
- package/dist/providers.d.ts +9 -0
- package/dist/providers.d.ts.map +1 -0
- package/dist/providers.js +41 -0
- package/dist/providers.js.map +1 -0
- package/dist/runtime.d.ts +18 -15
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +154 -0
- package/dist/runtime.js.map +1 -0
- package/dist/runtime.test.d.ts +2 -0
- package/dist/runtime.test.d.ts.map +1 -0
- package/dist/runtime.test.js +119 -0
- package/dist/runtime.test.js.map +1 -0
- package/dist/utils/fs.d.ts +4 -0
- package/dist/utils/fs.d.ts.map +1 -0
- package/dist/utils/fs.js +33 -0
- package/dist/utils/fs.js.map +1 -0
- package/dist/utils/pm.d.ts +3 -0
- package/dist/utils/pm.d.ts.map +1 -0
- package/dist/utils/pm.js +17 -0
- package/dist/utils/pm.js.map +1 -0
- package/dist/utils/prompts.d.ts +2 -0
- package/dist/utils/prompts.d.ts.map +1 -0
- package/dist/utils/prompts.js +6 -0
- package/dist/utils/prompts.js.map +1 -0
- package/dist/utils/templates.d.ts +3 -0
- package/dist/utils/templates.d.ts.map +1 -0
- package/dist/utils/templates.js +48 -0
- package/dist/utils/templates.js.map +1 -0
- package/package.json +31 -48
- package/templates/bun/index.ts +8 -0
- package/templates/bun/package.json +15 -0
- package/templates/bun/tsconfig.json +15 -0
- package/templates/bun/vyft.config.ts +3 -0
- package/dist/build.d.ts +0 -12
- package/dist/build.js +0 -44
- package/dist/cli.d.ts +0 -2
- package/dist/cli.js +0 -842
- package/dist/context.d.ts +0 -39
- package/dist/context.js +0 -101
- package/dist/docker.d.ts +0 -69
- package/dist/docker.js +0 -958
- package/dist/exec.d.ts +0 -2
- package/dist/exec.js +0 -28
- package/dist/init.d.ts +0 -1
- package/dist/init.js +0 -117
- package/dist/interpolate.d.ts +0 -13
- package/dist/interpolate.js +0 -19
- package/dist/local/dev.d.ts +0 -31
- package/dist/local/dev.js +0 -109
- package/dist/local/index.d.ts +0 -2
- package/dist/local/index.js +0 -2
- package/dist/local/runtime.d.ts +0 -61
- package/dist/local/runtime.js +0 -391
- package/dist/logger.d.ts +0 -2
- package/dist/logger.js +0 -10
- package/dist/proxy.d.ts +0 -16
- package/dist/proxy.js +0 -0
- package/dist/resource.d.ts +0 -181
- package/dist/resource.js +0 -45
- package/dist/services/index.d.ts +0 -26
- package/dist/services/index.js +0 -35
- package/dist/services/minio.d.ts +0 -36
- package/dist/services/minio.js +0 -53
- package/dist/services/mongo.d.ts +0 -28
- package/dist/services/mongo.js +0 -45
- package/dist/services/mysql.d.ts +0 -28
- package/dist/services/mysql.js +0 -44
- package/dist/services/nats.d.ts +0 -26
- package/dist/services/nats.js +0 -38
- package/dist/services/postgres.d.ts +0 -28
- package/dist/services/postgres.js +0 -45
- package/dist/services/rabbitmq.d.ts +0 -28
- package/dist/services/rabbitmq.js +0 -44
- package/dist/services/redis.d.ts +0 -28
- package/dist/services/redis.js +0 -49
- package/dist/services/storage.d.ts +0 -39
- package/dist/services/storage.js +0 -94
- package/dist/swarm/factories.d.ts +0 -16
- package/dist/swarm/factories.js +0 -63
- package/dist/swarm/index.d.ts +0 -20
- package/dist/swarm/index.js +0 -5
- package/dist/swarm/proxy.d.ts +0 -24
- package/dist/swarm/proxy.js +0 -339
- package/dist/swarm/types.d.ts +0 -26
- package/dist/swarm/types.js +0 -0
- package/dist/symbols.d.ts +0 -15
- package/dist/symbols.js +0 -4
- package/templates/fullstack/apps/api/Dockerfile +0 -22
- package/templates/fullstack/apps/api/package.json +0 -26
- package/templates/fullstack/apps/api/src/auth.ts +0 -21
- package/templates/fullstack/apps/api/src/db.ts +0 -16
- package/templates/fullstack/apps/api/src/index.ts +0 -17
- package/templates/fullstack/apps/api/src/router.ts +0 -11
- package/templates/fullstack/apps/api/src/schema.ts +0 -11
- package/templates/fullstack/apps/api/tsconfig.json +0 -8
- package/templates/fullstack/apps/web/index.html +0 -12
- package/templates/fullstack/apps/web/package.json +0 -21
- package/templates/fullstack/apps/web/src/app.tsx +0 -8
- package/templates/fullstack/apps/web/src/main.tsx +0 -9
- package/templates/fullstack/apps/web/tsconfig.json +0 -7
- package/templates/fullstack/apps/web/vite.config.ts +0 -14
- package/templates/fullstack/dockerignore +0 -7
- package/templates/fullstack/gitignore +0 -3
- package/templates/fullstack/package.json +0 -14
- package/templates/fullstack/pnpm-workspace.yaml +0 -2
- package/templates/fullstack/tsconfig.json +0 -11
- package/templates/fullstack/vyft.config.ts +0 -22
package/dist/local/runtime.js
DELETED
|
@@ -1,391 +0,0 @@
|
|
|
1
|
-
import { randomBytes } from "node:crypto";
|
|
2
|
-
import { PassThrough } from "node:stream";
|
|
3
|
-
import Docker from "dockerode";
|
|
4
|
-
import { logger as defaultLogger } from "../logger.js";
|
|
5
|
-
import { isInterpolation, isReference } from "../resource.js";
|
|
6
|
-
/**
|
|
7
|
-
* Docker runtime for local development.
|
|
8
|
-
*
|
|
9
|
-
* Runs managed infrastructure (databases, caches, etc.) as plain Docker
|
|
10
|
-
* containers with ports published to localhost. Secrets are generated
|
|
11
|
-
* in-memory and injected as environment variables.
|
|
12
|
-
*/
|
|
13
|
-
export class LocalRuntime {
|
|
14
|
-
docker;
|
|
15
|
-
project;
|
|
16
|
-
log;
|
|
17
|
-
secretValues = new Map();
|
|
18
|
-
containers = [];
|
|
19
|
-
networkName;
|
|
20
|
-
constructor(project, opts) {
|
|
21
|
-
this.docker = new Docker({ socketPath: "/var/run/docker.sock" });
|
|
22
|
-
this.project = project;
|
|
23
|
-
this.networkName = `${project}-dev`;
|
|
24
|
-
this.log = (opts?.parentLogger ?? defaultLogger).child({
|
|
25
|
-
component: "local-runtime",
|
|
26
|
-
project,
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
/** Create bridge network for inter-container DNS. */
|
|
30
|
-
async ensureInfrastructure() {
|
|
31
|
-
const networks = await this.docker.listNetworks({
|
|
32
|
-
filters: JSON.stringify({ name: [this.networkName] }),
|
|
33
|
-
});
|
|
34
|
-
const exists = networks.some((n) => n.Name === this.networkName);
|
|
35
|
-
if (!exists) {
|
|
36
|
-
this.log.debug({ network: this.networkName }, "creating bridge network");
|
|
37
|
-
await this.docker.createNetwork({
|
|
38
|
-
Name: this.networkName,
|
|
39
|
-
Driver: "bridge",
|
|
40
|
-
Labels: {
|
|
41
|
-
"vyft.dev": "true",
|
|
42
|
-
"vyft.project": this.project,
|
|
43
|
-
},
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
/** Generate a random secret value and store it in memory. */
|
|
48
|
-
createSecret(secret) {
|
|
49
|
-
const length = secret.config.length || 32;
|
|
50
|
-
const value = randomBytes(length).toString("base64url");
|
|
51
|
-
this.secretValues.set(secret.id, value);
|
|
52
|
-
this.log.debug({ secretName: secret.id }, "secret generated");
|
|
53
|
-
}
|
|
54
|
-
/** Get the generated value for a secret. */
|
|
55
|
-
getSecretValue(id) {
|
|
56
|
-
return this.secretValues.get(id);
|
|
57
|
-
}
|
|
58
|
-
/** Create a Docker volume for persistent data. */
|
|
59
|
-
async createVolume(volume) {
|
|
60
|
-
const name = volume.id;
|
|
61
|
-
try {
|
|
62
|
-
await this.docker.getVolume(name).inspect();
|
|
63
|
-
this.log.debug({ volumeName: name }, "volume already exists");
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
catch {
|
|
67
|
-
// Volume doesn't exist, create it
|
|
68
|
-
}
|
|
69
|
-
await this.docker.createVolume({
|
|
70
|
-
Name: name,
|
|
71
|
-
Labels: {
|
|
72
|
-
"vyft.dev": "true",
|
|
73
|
-
"vyft.project": this.project,
|
|
74
|
-
},
|
|
75
|
-
});
|
|
76
|
-
this.log.debug({ volumeName: name }, "volume created");
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* Resolve env vars for a managed service container.
|
|
80
|
-
*
|
|
81
|
-
* Secrets become plain `KEY=<value>` env vars.
|
|
82
|
-
* Interpolations are fully resolved with generated secret values.
|
|
83
|
-
*/
|
|
84
|
-
resolveEnv(env) {
|
|
85
|
-
const result = [];
|
|
86
|
-
for (const [key, value] of Object.entries(env)) {
|
|
87
|
-
if (typeof value === "string") {
|
|
88
|
-
result.push(`${key}=${value}`);
|
|
89
|
-
}
|
|
90
|
-
else if (isReference(value)) {
|
|
91
|
-
const secretValue = this.secretValues.get(value.id);
|
|
92
|
-
if (!secretValue) {
|
|
93
|
-
throw new Error(`Secret ${value.id} not yet generated`);
|
|
94
|
-
}
|
|
95
|
-
result.push(`${key}=${secretValue}`);
|
|
96
|
-
}
|
|
97
|
-
else if (isInterpolation(value)) {
|
|
98
|
-
const parts = [];
|
|
99
|
-
for (let i = 0; i < value.strings.length; i++) {
|
|
100
|
-
parts.push(value.strings[i]);
|
|
101
|
-
if (i < value.values.length) {
|
|
102
|
-
const v = value.values[i];
|
|
103
|
-
if (isReference(v)) {
|
|
104
|
-
const secretValue = this.secretValues.get(v.id);
|
|
105
|
-
if (!secretValue) {
|
|
106
|
-
throw new Error(`Secret ${v.id} not yet generated`);
|
|
107
|
-
}
|
|
108
|
-
parts.push(secretValue);
|
|
109
|
-
}
|
|
110
|
-
else {
|
|
111
|
-
parts.push(v);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
result.push(`${key}=${parts.join("")}`);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
return result;
|
|
119
|
-
}
|
|
120
|
-
/**
|
|
121
|
-
* Build an entrypoint wrapper that writes secret values to /run/secrets/
|
|
122
|
-
* before exec'ing the original entrypoint.
|
|
123
|
-
*
|
|
124
|
-
* This is needed because some factory images (e.g. redis) read passwords
|
|
125
|
-
* from `/run/secrets/*-password` via shell glob.
|
|
126
|
-
*/
|
|
127
|
-
buildSecretInit(secrets, originalEntrypoint, originalCmd) {
|
|
128
|
-
if (secrets.length === 0) {
|
|
129
|
-
return { entrypoint: originalEntrypoint, cmd: originalCmd };
|
|
130
|
-
}
|
|
131
|
-
const writeCommands = secrets
|
|
132
|
-
.map((s) => `printf '%s' "$${s.envVar}" > /run/secrets/${s.name}`)
|
|
133
|
-
.join(" && ");
|
|
134
|
-
// Build the original command to exec
|
|
135
|
-
const original = [...originalEntrypoint, ...originalCmd];
|
|
136
|
-
const execCmd = original.length > 0 ? `exec ${original.map(shellEscape).join(" ")}` : "";
|
|
137
|
-
const script = `mkdir -p /run/secrets && ${writeCommands}${execCmd ? ` && ${execCmd}` : ""}`;
|
|
138
|
-
return {
|
|
139
|
-
entrypoint: ["sh", "-c", script],
|
|
140
|
-
cmd: [],
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
/** Pull, create, and start a container for a managed service. */
|
|
144
|
-
async createService(service) {
|
|
145
|
-
const config = service.config;
|
|
146
|
-
const image = typeof config.image === "string" ? config.image : `${service.id}:latest`;
|
|
147
|
-
const port = config.port || 3000;
|
|
148
|
-
// Stop and remove existing container if present
|
|
149
|
-
await this.removeContainer(service.id);
|
|
150
|
-
// Pull image
|
|
151
|
-
this.log.debug({ image }, "pulling image");
|
|
152
|
-
const pullStream = await this.docker.pull(image);
|
|
153
|
-
await new Promise((resolve, reject) => {
|
|
154
|
-
this.docker.modem.followProgress(pullStream, (err) => err ? reject(err) : resolve());
|
|
155
|
-
});
|
|
156
|
-
// Resolve env
|
|
157
|
-
const envList = config.env ? this.resolveEnv(config.env) : [];
|
|
158
|
-
// Collect secrets that need /run/secrets/ files
|
|
159
|
-
const secretMappings = [];
|
|
160
|
-
if (config.env) {
|
|
161
|
-
let idx = 0;
|
|
162
|
-
for (const [, value] of Object.entries(config.env)) {
|
|
163
|
-
if (isReference(value)) {
|
|
164
|
-
const envVar = `__VYFT_S${idx}`;
|
|
165
|
-
secretMappings.push({
|
|
166
|
-
name: value.id.replace(/^.*-/, ""), // short name e.g. "db-password" -> "password"
|
|
167
|
-
envVar,
|
|
168
|
-
});
|
|
169
|
-
// The full id as a file too, for glob patterns like *-password
|
|
170
|
-
secretMappings.push({
|
|
171
|
-
name: value.id,
|
|
172
|
-
envVar,
|
|
173
|
-
});
|
|
174
|
-
const secretVal = this.secretValues.get(value.id);
|
|
175
|
-
if (secretVal) {
|
|
176
|
-
envList.push(`${envVar}=${secretVal}`);
|
|
177
|
-
}
|
|
178
|
-
idx++;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
// Inspect image for default entrypoint/cmd
|
|
183
|
-
const imageInfo = await this.docker.getImage(image).inspect();
|
|
184
|
-
const rawEntrypoint = imageInfo.Config?.Entrypoint ?? [];
|
|
185
|
-
const defaultEntrypoint = Array.isArray(rawEntrypoint)
|
|
186
|
-
? rawEntrypoint
|
|
187
|
-
: [rawEntrypoint];
|
|
188
|
-
const rawCmd = imageInfo.Config?.Cmd ?? [];
|
|
189
|
-
const defaultCmd = Array.isArray(rawCmd) ? rawCmd : [rawCmd];
|
|
190
|
-
// Use config.command if specified, otherwise image defaults
|
|
191
|
-
const userCmd = config.command ?? defaultCmd;
|
|
192
|
-
const { entrypoint, cmd } = this.buildSecretInit(secretMappings, defaultEntrypoint, userCmd);
|
|
193
|
-
// Build volume mounts
|
|
194
|
-
const mounts = config.volumes?.map(({ volume, mount }) => ({
|
|
195
|
-
Type: "volume",
|
|
196
|
-
Source: volume.id,
|
|
197
|
-
Target: mount,
|
|
198
|
-
}));
|
|
199
|
-
// Health check
|
|
200
|
-
const healthCheck = config.healthCheck
|
|
201
|
-
? {
|
|
202
|
-
Test: ["CMD", ...config.healthCheck.command],
|
|
203
|
-
Interval: config.healthCheck.interval
|
|
204
|
-
? parseDurationNs(config.healthCheck.interval)
|
|
205
|
-
: undefined,
|
|
206
|
-
Timeout: config.healthCheck.timeout
|
|
207
|
-
? parseDurationNs(config.healthCheck.timeout)
|
|
208
|
-
: undefined,
|
|
209
|
-
Retries: config.healthCheck.retries,
|
|
210
|
-
StartPeriod: config.healthCheck.startPeriod
|
|
211
|
-
? parseDurationNs(config.healthCheck.startPeriod)
|
|
212
|
-
: undefined,
|
|
213
|
-
}
|
|
214
|
-
: undefined;
|
|
215
|
-
const containerName = `${service.id}-dev`;
|
|
216
|
-
this.log.debug({ containerName, image, port }, "creating container");
|
|
217
|
-
const container = await this.docker.createContainer({
|
|
218
|
-
name: containerName,
|
|
219
|
-
Image: image,
|
|
220
|
-
Entrypoint: entrypoint.length > 0 ? entrypoint : undefined,
|
|
221
|
-
Cmd: cmd.length > 0 ? cmd : undefined,
|
|
222
|
-
Env: envList,
|
|
223
|
-
ExposedPorts: { [`${port}/tcp`]: {} },
|
|
224
|
-
Healthcheck: healthCheck,
|
|
225
|
-
Labels: {
|
|
226
|
-
"vyft.dev": "true",
|
|
227
|
-
"vyft.project": this.project,
|
|
228
|
-
"vyft.service": service.id,
|
|
229
|
-
},
|
|
230
|
-
HostConfig: {
|
|
231
|
-
PortBindings: {
|
|
232
|
-
[`${port}/tcp`]: [{ HostIp: "127.0.0.1", HostPort: `${port}` }],
|
|
233
|
-
},
|
|
234
|
-
NetworkMode: this.networkName,
|
|
235
|
-
Mounts: mounts,
|
|
236
|
-
},
|
|
237
|
-
});
|
|
238
|
-
await container.start();
|
|
239
|
-
this.containers.push({
|
|
240
|
-
id: service.id,
|
|
241
|
-
name: containerName,
|
|
242
|
-
containerId: container.id,
|
|
243
|
-
});
|
|
244
|
-
this.log.debug({ containerName, port }, "container started");
|
|
245
|
-
return port;
|
|
246
|
-
}
|
|
247
|
-
/** Wait for a container to pass its health check. */
|
|
248
|
-
async waitForHealthy(id, timeoutMs = 120000) {
|
|
249
|
-
const info = this.containers.find((c) => c.id === id);
|
|
250
|
-
if (!info)
|
|
251
|
-
return;
|
|
252
|
-
const start = performance.now();
|
|
253
|
-
const interval = 1000;
|
|
254
|
-
this.log.debug({ resourceId: id }, "waiting for healthy");
|
|
255
|
-
while (performance.now() - start < timeoutMs) {
|
|
256
|
-
const container = this.docker.getContainer(info.containerId);
|
|
257
|
-
const data = await container.inspect();
|
|
258
|
-
const health = data.State?.Health?.Status;
|
|
259
|
-
if (health === "healthy") {
|
|
260
|
-
this.log.debug({ resourceId: id }, "container is healthy");
|
|
261
|
-
return;
|
|
262
|
-
}
|
|
263
|
-
if (health === undefined) {
|
|
264
|
-
// No health check configured, just check if running
|
|
265
|
-
if (data.State?.Running) {
|
|
266
|
-
this.log.debug({ resourceId: id }, "container running (no health check)");
|
|
267
|
-
return;
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
await new Promise((r) => setTimeout(r, interval));
|
|
271
|
-
}
|
|
272
|
-
throw new Error(`Container ${id} did not become healthy within ${timeoutMs}ms`);
|
|
273
|
-
}
|
|
274
|
-
/** Stream logs from a container. */
|
|
275
|
-
async *containerLogs(id) {
|
|
276
|
-
const info = this.containers.find((c) => c.id === id);
|
|
277
|
-
if (!info)
|
|
278
|
-
return;
|
|
279
|
-
const container = this.docker.getContainer(info.containerId);
|
|
280
|
-
const stream = await container.logs({
|
|
281
|
-
stdout: true,
|
|
282
|
-
stderr: true,
|
|
283
|
-
follow: true,
|
|
284
|
-
tail: 0,
|
|
285
|
-
});
|
|
286
|
-
const buffer = [];
|
|
287
|
-
let notify = null;
|
|
288
|
-
let done = false;
|
|
289
|
-
const push = (entry) => {
|
|
290
|
-
buffer.push(entry);
|
|
291
|
-
if (notify) {
|
|
292
|
-
const n = notify;
|
|
293
|
-
notify = null;
|
|
294
|
-
n();
|
|
295
|
-
}
|
|
296
|
-
};
|
|
297
|
-
const stdoutPT = new PassThrough();
|
|
298
|
-
const stderrPT = new PassThrough();
|
|
299
|
-
stdoutPT.on("data", (chunk) => {
|
|
300
|
-
for (const line of chunk.toString().split("\n")) {
|
|
301
|
-
if (line)
|
|
302
|
-
push({ stream: "stdout", text: line });
|
|
303
|
-
}
|
|
304
|
-
});
|
|
305
|
-
stderrPT.on("data", (chunk) => {
|
|
306
|
-
for (const line of chunk.toString().split("\n")) {
|
|
307
|
-
if (line)
|
|
308
|
-
push({ stream: "stderr", text: line });
|
|
309
|
-
}
|
|
310
|
-
});
|
|
311
|
-
this.docker.modem.demuxStream(stream, stdoutPT, stderrPT);
|
|
312
|
-
stream.on("end", () => {
|
|
313
|
-
done = true;
|
|
314
|
-
if (notify) {
|
|
315
|
-
const n = notify;
|
|
316
|
-
notify = null;
|
|
317
|
-
n();
|
|
318
|
-
}
|
|
319
|
-
});
|
|
320
|
-
while (!done || buffer.length > 0) {
|
|
321
|
-
if (buffer.length === 0) {
|
|
322
|
-
await new Promise((r) => {
|
|
323
|
-
notify = r;
|
|
324
|
-
});
|
|
325
|
-
}
|
|
326
|
-
while (buffer.length > 0) {
|
|
327
|
-
yield buffer.shift();
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
/** Stop and remove all managed containers (keep volumes). */
|
|
332
|
-
async stop() {
|
|
333
|
-
for (const info of this.containers) {
|
|
334
|
-
try {
|
|
335
|
-
const container = this.docker.getContainer(info.containerId);
|
|
336
|
-
await container.stop({ t: 5 });
|
|
337
|
-
await container.remove();
|
|
338
|
-
this.log.debug({ containerName: info.name }, "container stopped");
|
|
339
|
-
}
|
|
340
|
-
catch {
|
|
341
|
-
this.log.debug({ containerName: info.name }, "container already stopped");
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
this.containers = [];
|
|
345
|
-
}
|
|
346
|
-
/** List running dev containers for this project. */
|
|
347
|
-
async listContainers() {
|
|
348
|
-
const containers = await this.docker.listContainers({
|
|
349
|
-
filters: JSON.stringify({
|
|
350
|
-
label: ["vyft.dev=true", `vyft.project=${this.project}`],
|
|
351
|
-
}),
|
|
352
|
-
});
|
|
353
|
-
return containers.map((c) => ({
|
|
354
|
-
id: c.Labels["vyft.service"] || "",
|
|
355
|
-
name: c.Names[0]?.replace(/^\//, "") || "",
|
|
356
|
-
containerId: c.Id,
|
|
357
|
-
}));
|
|
358
|
-
}
|
|
359
|
-
async removeContainer(serviceId) {
|
|
360
|
-
const containerName = `${serviceId}-dev`;
|
|
361
|
-
try {
|
|
362
|
-
const container = this.docker.getContainer(containerName);
|
|
363
|
-
await container.stop({ t: 2 });
|
|
364
|
-
await container.remove();
|
|
365
|
-
this.log.debug({ containerName }, "removed existing container");
|
|
366
|
-
}
|
|
367
|
-
catch {
|
|
368
|
-
// Container didn't exist
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
function parseDurationNs(duration) {
|
|
373
|
-
const match = duration.match(/^(\d+)(ms|s|m|h)$/);
|
|
374
|
-
if (!match?.[1] || !match[2]) {
|
|
375
|
-
throw new Error(`Invalid duration format: ${duration}`);
|
|
376
|
-
}
|
|
377
|
-
const value = parseInt(match[1], 10);
|
|
378
|
-
const unit = match[2];
|
|
379
|
-
const multipliers = {
|
|
380
|
-
ms: 1_000_000,
|
|
381
|
-
s: 1_000_000_000,
|
|
382
|
-
m: 60_000_000_000,
|
|
383
|
-
h: 3_600_000_000_000,
|
|
384
|
-
};
|
|
385
|
-
return value * multipliers[unit];
|
|
386
|
-
}
|
|
387
|
-
function shellEscape(s) {
|
|
388
|
-
if (/^[a-zA-Z0-9_./:=-]+$/.test(s))
|
|
389
|
-
return s;
|
|
390
|
-
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
391
|
-
}
|
package/dist/logger.d.ts
DELETED
package/dist/logger.js
DELETED
package/dist/proxy.d.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
export interface ReverseProxy {
|
|
2
|
-
/** Ensure the proxy service is running and configured. */
|
|
3
|
-
ensure(): Promise<void>;
|
|
4
|
-
/** Register or update a route for a resource. */
|
|
5
|
-
addRoute(resourceId: string, route: string, target: {
|
|
6
|
-
host: string;
|
|
7
|
-
port: number;
|
|
8
|
-
}): Promise<void>;
|
|
9
|
-
/** Remove a route for a resource. No-op if it doesn't exist. */
|
|
10
|
-
removeRoute(resourceId: string): Promise<void>;
|
|
11
|
-
/** Stream proxy access logs. */
|
|
12
|
-
logs(options: {
|
|
13
|
-
follow?: boolean;
|
|
14
|
-
tail?: number;
|
|
15
|
-
}): Promise<void>;
|
|
16
|
-
}
|
package/dist/proxy.js
DELETED
|
File without changes
|
package/dist/resource.d.ts
DELETED
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
import type { RuntimeRef } from "./symbols.js";
|
|
2
|
-
/** Discriminant for the four resource kinds. */
|
|
3
|
-
export type ResourceType = "volume" | "secret" | "service" | "site";
|
|
4
|
-
/** Configuration for a persistent volume. */
|
|
5
|
-
export interface VolumeConfig {
|
|
6
|
-
/** Human-readable size hint (e.g. `"10GB"`). Informational only. */
|
|
7
|
-
size?: string;
|
|
8
|
-
}
|
|
9
|
-
/** Configuration for an auto-generated secret. */
|
|
10
|
-
export interface SecretConfig {
|
|
11
|
-
/** Length of the generated random value in bytes. */
|
|
12
|
-
length?: number;
|
|
13
|
-
}
|
|
14
|
-
/** Container health-check configuration. */
|
|
15
|
-
export interface HealthCheckConfig {
|
|
16
|
-
/** Command to run inside the container (e.g. `["pg_isready", "-U", "postgres"]`). */
|
|
17
|
-
command: string[];
|
|
18
|
-
/** Time between checks (e.g. `"5s"`, `"1m"`). */
|
|
19
|
-
interval?: string;
|
|
20
|
-
/** Maximum time a single check may take. */
|
|
21
|
-
timeout?: string;
|
|
22
|
-
/** Consecutive failures required to mark unhealthy. */
|
|
23
|
-
retries?: number;
|
|
24
|
-
/** Grace period before the first check runs. */
|
|
25
|
-
startPeriod?: string;
|
|
26
|
-
}
|
|
27
|
-
/** CPU and memory limits for a service. */
|
|
28
|
-
export interface ResourceLimits {
|
|
29
|
-
/** Memory ceiling (e.g. `"512MB"`, `"2GB"`). */
|
|
30
|
-
memory?: string;
|
|
31
|
-
/** CPU core limit (e.g. `0.5`, `2`). */
|
|
32
|
-
cpus?: number;
|
|
33
|
-
}
|
|
34
|
-
/** A persistent storage volume. */
|
|
35
|
-
export interface Volume extends RuntimeRef {
|
|
36
|
-
type: "volume";
|
|
37
|
-
id: string;
|
|
38
|
-
config: VolumeConfig;
|
|
39
|
-
}
|
|
40
|
-
/** An auto-generated secret value, injected at deploy time. */
|
|
41
|
-
export interface Secret extends RuntimeRef {
|
|
42
|
-
type: "secret";
|
|
43
|
-
id: string;
|
|
44
|
-
config: SecretConfig;
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* A long-running service (container).
|
|
48
|
-
*
|
|
49
|
-
* Use `host` and `port` to reference this service from other services
|
|
50
|
-
* in the same project, or `url` for the full address.
|
|
51
|
-
*/
|
|
52
|
-
export interface Service extends RuntimeRef {
|
|
53
|
-
type: "service";
|
|
54
|
-
id: string;
|
|
55
|
-
config: ServiceConfig;
|
|
56
|
-
/** Internal hostname reachable by other services. */
|
|
57
|
-
host: string;
|
|
58
|
-
/** Port the service listens on. */
|
|
59
|
-
port: number;
|
|
60
|
-
/** Full URL — `https://<route>` if routed, otherwise `http://<host>:<port>`. */
|
|
61
|
-
url: string;
|
|
62
|
-
}
|
|
63
|
-
/** A static site served via Caddy. */
|
|
64
|
-
export interface Site extends RuntimeRef {
|
|
65
|
-
type: "site";
|
|
66
|
-
id: string;
|
|
67
|
-
config: SiteConfig;
|
|
68
|
-
/** Public URL derived from the route (e.g. `https://example.com`). */
|
|
69
|
-
url: string;
|
|
70
|
-
}
|
|
71
|
-
/** Union of all deployable resource types. */
|
|
72
|
-
export type Resource = Volume | Secret | Service | Site;
|
|
73
|
-
/**
|
|
74
|
-
* A deferred reference to a value that isn't known until deploy time.
|
|
75
|
-
*
|
|
76
|
-
* Currently only {@link Secret} produces deferred values. Expand this
|
|
77
|
-
* union as more resources gain deferred outputs.
|
|
78
|
-
*/
|
|
79
|
-
export type Reference = Secret;
|
|
80
|
-
/**
|
|
81
|
-
* A tagged-template interpolation that mixes literal strings with
|
|
82
|
-
* {@link Reference} values. Created via {@link interpolate}.
|
|
83
|
-
*
|
|
84
|
-
* @example
|
|
85
|
-
* ```ts
|
|
86
|
-
* const connStr = interpolate`postgres://user:${dbPassword}@${db.host}:5432/app`;
|
|
87
|
-
* ```
|
|
88
|
-
*/
|
|
89
|
-
export interface Interpolation {
|
|
90
|
-
type: "interpolation";
|
|
91
|
-
strings: TemplateStringsArray;
|
|
92
|
-
values: Array<Reference | string>;
|
|
93
|
-
}
|
|
94
|
-
/**
|
|
95
|
-
* A value that can appear in a service's `env` map.
|
|
96
|
-
*
|
|
97
|
-
* - Plain `string` — set verbatim.
|
|
98
|
-
* - {@link Reference} — resolved at deploy time (e.g. a secret).
|
|
99
|
-
* - {@link Interpolation} — a template mixing strings and references.
|
|
100
|
-
*/
|
|
101
|
-
export type EnvValue = string | Reference | Interpolation;
|
|
102
|
-
/** Type-guard: returns `true` if `value` is a {@link Reference}. */
|
|
103
|
-
export declare function isReference(value: unknown): value is Reference;
|
|
104
|
-
/** Type-guard: returns `true` if `value` is a {@link Secret}. */
|
|
105
|
-
export declare function isSecret(value: unknown): value is Secret;
|
|
106
|
-
/** Type-guard: returns `true` if `value` is an {@link Interpolation}. */
|
|
107
|
-
export declare function isInterpolation(value: unknown): value is Interpolation;
|
|
108
|
-
/** Configuration for a long-running service. */
|
|
109
|
-
export interface ServiceConfig {
|
|
110
|
-
/**
|
|
111
|
-
* Container image to run.
|
|
112
|
-
*
|
|
113
|
-
* - `string` — a registry image (e.g. `"node:22"`).
|
|
114
|
-
* - `{ context, dockerfile }` — build from a local Dockerfile.
|
|
115
|
-
*/
|
|
116
|
-
image: string | {
|
|
117
|
-
context?: string;
|
|
118
|
-
dockerfile?: string;
|
|
119
|
-
};
|
|
120
|
-
/** Public route to expose (e.g. `"api.example.com"` or `"example.com/api"`). */
|
|
121
|
-
route?: string;
|
|
122
|
-
/** Port the container listens on. Defaults to `3000`. */
|
|
123
|
-
port?: number;
|
|
124
|
-
/** Environment variables injected into the container. */
|
|
125
|
-
env?: Record<string, EnvValue>;
|
|
126
|
-
/** Override the container's default command. */
|
|
127
|
-
command?: string[];
|
|
128
|
-
/** Volumes to mount into the container. */
|
|
129
|
-
volumes?: Array<{
|
|
130
|
-
volume: Volume;
|
|
131
|
-
mount: string;
|
|
132
|
-
}>;
|
|
133
|
-
/** Services that must be healthy before this one starts. */
|
|
134
|
-
dependsOn?: Service[];
|
|
135
|
-
/** Container health check. */
|
|
136
|
-
healthCheck?: HealthCheckConfig;
|
|
137
|
-
/**
|
|
138
|
-
* Restart behaviour on failure.
|
|
139
|
-
* @defaultValue `"any"`
|
|
140
|
-
*/
|
|
141
|
-
restartPolicy?: "none" | "on-failure" | "any";
|
|
142
|
-
/** Local development configuration. Used by `vyft dev`. */
|
|
143
|
-
dev?: {
|
|
144
|
-
/** Shell command to run (e.g. `"bun run dev"`). */
|
|
145
|
-
command: string;
|
|
146
|
-
/** Working directory relative to project root. */
|
|
147
|
-
cwd?: string;
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
/** Configuration for a static site. */
|
|
151
|
-
export interface SiteConfig {
|
|
152
|
-
/** Domain (and optional path) to serve the site on. */
|
|
153
|
-
route: string;
|
|
154
|
-
/**
|
|
155
|
-
* Enable single-page application mode. When `true`, all requests that
|
|
156
|
-
* don't match a static file are rewritten to `/index.html`.
|
|
157
|
-
* @defaultValue `true`
|
|
158
|
-
*/
|
|
159
|
-
spa?: boolean;
|
|
160
|
-
/** Build settings for the static site. */
|
|
161
|
-
build: {
|
|
162
|
-
/** Working directory containing the source (relative to project root). */
|
|
163
|
-
cwd: string;
|
|
164
|
-
/** Directory containing the built output (relative to `cwd`). Defaults to `"dist"`. */
|
|
165
|
-
output?: string;
|
|
166
|
-
/** Build command to run (e.g. `"npm run build"`). */
|
|
167
|
-
command?: string;
|
|
168
|
-
/** Environment variables passed to the build command. */
|
|
169
|
-
env?: Record<string, string>;
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
/**
|
|
173
|
-
* Validates a resource ID.
|
|
174
|
-
* @throws If the ID is empty, too long, or contains invalid characters.
|
|
175
|
-
*/
|
|
176
|
-
export declare function validateId(id: string): void;
|
|
177
|
-
/**
|
|
178
|
-
* Validates a route string.
|
|
179
|
-
* @throws If the route is empty or not a valid domain with optional path.
|
|
180
|
-
*/
|
|
181
|
-
export declare function validateRoute(route: string): void;
|
package/dist/resource.js
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
/** Type-guard: returns `true` if `value` is a {@link Reference}. */
|
|
2
|
-
export function isReference(value) {
|
|
3
|
-
return isSecret(value);
|
|
4
|
-
}
|
|
5
|
-
/** Type-guard: returns `true` if `value` is a {@link Secret}. */
|
|
6
|
-
export function isSecret(value) {
|
|
7
|
-
return (typeof value === "object" &&
|
|
8
|
-
value !== null &&
|
|
9
|
-
value.type === "secret");
|
|
10
|
-
}
|
|
11
|
-
/** Type-guard: returns `true` if `value` is an {@link Interpolation}. */
|
|
12
|
-
export function isInterpolation(value) {
|
|
13
|
-
return (typeof value === "object" &&
|
|
14
|
-
value !== null &&
|
|
15
|
-
value.type === "interpolation");
|
|
16
|
-
}
|
|
17
|
-
const ID_PATTERN = /^[a-z][a-z0-9-]*[a-z0-9]$|^[a-z]$/;
|
|
18
|
-
const ROUTE_PATTERN = /^(\*\.)?[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*(\/.+)?$/;
|
|
19
|
-
/**
|
|
20
|
-
* Validates a resource ID.
|
|
21
|
-
* @throws If the ID is empty, too long, or contains invalid characters.
|
|
22
|
-
*/
|
|
23
|
-
export function validateId(id) {
|
|
24
|
-
if (!id || id.length < 1) {
|
|
25
|
-
throw new Error("Resource ID cannot be empty");
|
|
26
|
-
}
|
|
27
|
-
if (id.length > 63) {
|
|
28
|
-
throw new Error("Resource ID cannot exceed 63 characters");
|
|
29
|
-
}
|
|
30
|
-
if (!ID_PATTERN.test(id)) {
|
|
31
|
-
throw new Error(`Invalid resource ID "${id}": must start with a letter, contain only lowercase letters, numbers, and hyphens, and end with a letter or number`);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Validates a route string.
|
|
36
|
-
* @throws If the route is empty or not a valid domain with optional path.
|
|
37
|
-
*/
|
|
38
|
-
export function validateRoute(route) {
|
|
39
|
-
if (!route) {
|
|
40
|
-
throw new Error("Route cannot be empty");
|
|
41
|
-
}
|
|
42
|
-
if (!ROUTE_PATTERN.test(route)) {
|
|
43
|
-
throw new Error(`Invalid route "${route}": must be a valid domain with optional path`);
|
|
44
|
-
}
|
|
45
|
-
}
|
package/dist/services/index.d.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import type { Secret, SecretConfig, Service, ServiceConfig, Volume, VolumeConfig } from "../resource.js";
|
|
2
|
-
export interface Primitives {
|
|
3
|
-
volume(id: string, config?: VolumeConfig): Volume;
|
|
4
|
-
service(id: string, config: ServiceConfig): Service;
|
|
5
|
-
secret(id: string, config?: SecretConfig): Secret;
|
|
6
|
-
}
|
|
7
|
-
/** Returns `true` if a resource was created by a built-in factory. */
|
|
8
|
-
export declare function isManaged(resource: unknown): boolean;
|
|
9
|
-
export declare function createServices(p: Primitives): {
|
|
10
|
-
postgres: (id: string, config?: import("./postgres.js").PostgresConfig) => import("./postgres.js").Postgres;
|
|
11
|
-
redis: (id: string, config?: import("./redis.js").RedisConfig) => import("./redis.js").Redis;
|
|
12
|
-
rabbitmq: (id: string, config?: import("./rabbitmq.js").RabbitmqConfig) => import("./rabbitmq.js").Rabbitmq;
|
|
13
|
-
nats: (id: string, config?: import("./nats.js").NatsConfig) => import("./nats.js").Nats;
|
|
14
|
-
mysql: (id: string, config?: import("./mysql.js").MysqlConfig) => import("./mysql.js").Mysql;
|
|
15
|
-
mongo: (id: string, config?: import("./mongo.js").MongoConfig) => import("./mongo.js").Mongo;
|
|
16
|
-
minio: (id: string, config?: import("./minio.js").MinioConfig) => import("./minio.js").Minio;
|
|
17
|
-
storage: (id: string, config?: import("./storage.js").StorageConfig) => import("./storage.js").Storage;
|
|
18
|
-
};
|
|
19
|
-
export type { Bucket, Minio, MinioConfig } from "./minio.js";
|
|
20
|
-
export type { Mongo, MongoConfig } from "./mongo.js";
|
|
21
|
-
export type { Mysql, MysqlConfig } from "./mysql.js";
|
|
22
|
-
export type { Nats, NatsConfig } from "./nats.js";
|
|
23
|
-
export type { Postgres, PostgresConfig } from "./postgres.js";
|
|
24
|
-
export type { Rabbitmq, RabbitmqConfig } from "./rabbitmq.js";
|
|
25
|
-
export type { Redis, RedisConfig } from "./redis.js";
|
|
26
|
-
export type { BackupConfig, Storage, StorageConfig } from "./storage.js";
|