vyft 0.4.0-alpha → 0.4.1-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 +339 -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 +69 -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/cli.js
DELETED
|
@@ -1,842 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { randomBytes } from "node:crypto";
|
|
3
|
-
import { access, readFile } from "node:fs/promises";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
import { log, spinner } from "@clack/prompts";
|
|
6
|
-
import { Command } from "commander";
|
|
7
|
-
import pc from "picocolors";
|
|
8
|
-
import { getCurrentContextName, listContexts, removeContext, resolveContext, saveContext, setCurrentContext, } from "./context.js";
|
|
9
|
-
import { DockerClient } from "./docker.js";
|
|
10
|
-
import { init } from "./init.js";
|
|
11
|
-
import { DevRunner, detectPackageManager, LocalRuntime, resolveDevEnv, } from "./local/index.js";
|
|
12
|
-
import { logger } from "./logger.js";
|
|
13
|
-
import { isInterpolation, isReference } from "./resource.js";
|
|
14
|
-
import { isManaged } from "./services/index.js";
|
|
15
|
-
import { VYFT_RUNTIME } from "./symbols.js";
|
|
16
|
-
const DESTROY_ORDER = {
|
|
17
|
-
site: 0,
|
|
18
|
-
service: 1,
|
|
19
|
-
volume: 2,
|
|
20
|
-
secret: 3,
|
|
21
|
-
};
|
|
22
|
-
async function findProjectName(configPath) {
|
|
23
|
-
let dir = path.dirname(configPath);
|
|
24
|
-
while (dir !== path.dirname(dir)) {
|
|
25
|
-
try {
|
|
26
|
-
const pkgPath = path.join(dir, "package.json");
|
|
27
|
-
const content = await readFile(pkgPath, "utf-8");
|
|
28
|
-
const pkg = JSON.parse(content);
|
|
29
|
-
if (pkg.name) {
|
|
30
|
-
return pkg.name;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
catch {
|
|
34
|
-
// No package.json here, keep walking up
|
|
35
|
-
}
|
|
36
|
-
dir = path.dirname(dir);
|
|
37
|
-
}
|
|
38
|
-
throw new Error("Could not find package.json with a name field");
|
|
39
|
-
}
|
|
40
|
-
function isResource(value) {
|
|
41
|
-
return (typeof value === "object" &&
|
|
42
|
-
value !== null &&
|
|
43
|
-
"type" in value &&
|
|
44
|
-
"id" in value &&
|
|
45
|
-
typeof value.type === "string" &&
|
|
46
|
-
typeof value.id === "string");
|
|
47
|
-
}
|
|
48
|
-
function collectResources(exports) {
|
|
49
|
-
const seen = new Set();
|
|
50
|
-
const resources = [];
|
|
51
|
-
for (const value of Object.values(exports)) {
|
|
52
|
-
if (isResource(value)) {
|
|
53
|
-
if (!seen.has(value.id)) {
|
|
54
|
-
seen.add(value.id);
|
|
55
|
-
resources.push(value);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
else if (typeof value === "object" && value !== null) {
|
|
59
|
-
for (const nested of Object.values(value)) {
|
|
60
|
-
if (isResource(nested) && !seen.has(nested.id)) {
|
|
61
|
-
seen.add(nested.id);
|
|
62
|
-
resources.push(nested);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
return resources;
|
|
68
|
-
}
|
|
69
|
-
function topoSortServices(services) {
|
|
70
|
-
const byId = new Map(services.map((s) => [s.id, s]));
|
|
71
|
-
const visited = new Set();
|
|
72
|
-
const result = [];
|
|
73
|
-
function visit(svc) {
|
|
74
|
-
if (visited.has(svc.id))
|
|
75
|
-
return;
|
|
76
|
-
visited.add(svc.id);
|
|
77
|
-
for (const dep of svc.config.dependsOn ?? []) {
|
|
78
|
-
const depSvc = byId.get(dep.id);
|
|
79
|
-
if (depSvc)
|
|
80
|
-
visit(depSvc);
|
|
81
|
-
}
|
|
82
|
-
result.push(svc);
|
|
83
|
-
}
|
|
84
|
-
for (const svc of services)
|
|
85
|
-
visit(svc);
|
|
86
|
-
return result;
|
|
87
|
-
}
|
|
88
|
-
function hasRuntime(value) {
|
|
89
|
-
return (typeof value === "object" &&
|
|
90
|
-
value !== null &&
|
|
91
|
-
VYFT_RUNTIME in value);
|
|
92
|
-
}
|
|
93
|
-
function getContextHost() {
|
|
94
|
-
const ctx = resolveContext(program.opts().context);
|
|
95
|
-
return ctx?.host;
|
|
96
|
-
}
|
|
97
|
-
function createRuntime(resources, project, sessionLogger) {
|
|
98
|
-
const host = getContextHost();
|
|
99
|
-
const ref = resources.find((r) => hasRuntime(r));
|
|
100
|
-
if (ref && hasRuntime(ref)) {
|
|
101
|
-
const meta = ref[VYFT_RUNTIME];
|
|
102
|
-
switch (meta.name) {
|
|
103
|
-
case "swarm":
|
|
104
|
-
return new DockerClient(project, { host, parentLogger: sessionLogger });
|
|
105
|
-
default:
|
|
106
|
-
throw new Error(`Unknown runtime: ${meta.name}`);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
return new DockerClient(project, { host, parentLogger: sessionLogger });
|
|
110
|
-
}
|
|
111
|
-
function stripPrefix(id, project) {
|
|
112
|
-
const prefix = `${project}-`;
|
|
113
|
-
return id.startsWith(prefix) ? id.slice(prefix.length) : id;
|
|
114
|
-
}
|
|
115
|
-
/** Limits concurrent async work to avoid exhausting SSH channels. */
|
|
116
|
-
function createSemaphore(limit) {
|
|
117
|
-
let active = 0;
|
|
118
|
-
const queue = [];
|
|
119
|
-
return {
|
|
120
|
-
async acquire() {
|
|
121
|
-
if (active < limit) {
|
|
122
|
-
active++;
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
await new Promise((r) => queue.push(r));
|
|
126
|
-
},
|
|
127
|
-
release() {
|
|
128
|
-
const next = queue.shift();
|
|
129
|
-
if (next) {
|
|
130
|
-
next();
|
|
131
|
-
}
|
|
132
|
-
else {
|
|
133
|
-
active--;
|
|
134
|
-
}
|
|
135
|
-
},
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
const MAX_CONCURRENCY = 6;
|
|
139
|
-
async function executeGraph(tasks, onStart, onComplete) {
|
|
140
|
-
const taskMap = new Map(tasks.map((t) => [t.id, t]));
|
|
141
|
-
const promises = new Map();
|
|
142
|
-
const sem = createSemaphore(MAX_CONCURRENCY);
|
|
143
|
-
function resolve(id) {
|
|
144
|
-
if (promises.has(id))
|
|
145
|
-
return promises.get(id);
|
|
146
|
-
const task = taskMap.get(id);
|
|
147
|
-
const p = (async () => {
|
|
148
|
-
await Promise.all(task.deps.map((d) => resolve(d)));
|
|
149
|
-
await sem.acquire();
|
|
150
|
-
try {
|
|
151
|
-
onStart(task.id);
|
|
152
|
-
const result = await task.run();
|
|
153
|
-
onComplete(task.id, result);
|
|
154
|
-
}
|
|
155
|
-
finally {
|
|
156
|
-
sem.release();
|
|
157
|
-
}
|
|
158
|
-
})();
|
|
159
|
-
promises.set(id, p);
|
|
160
|
-
return p;
|
|
161
|
-
}
|
|
162
|
-
await Promise.all(tasks.map((t) => resolve(t.id)));
|
|
163
|
-
}
|
|
164
|
-
function getServiceDeps(svc, resources) {
|
|
165
|
-
const deps = [];
|
|
166
|
-
const resourceIds = new Set(resources.map((r) => r.id));
|
|
167
|
-
for (const dep of svc.config.dependsOn ?? [])
|
|
168
|
-
deps.push(dep.id);
|
|
169
|
-
if (typeof svc.config.image === "object")
|
|
170
|
-
deps.push(`build:${svc.id}`);
|
|
171
|
-
for (const { volume } of svc.config.volumes ?? []) {
|
|
172
|
-
if (resourceIds.has(volume.id))
|
|
173
|
-
deps.push(volume.id);
|
|
174
|
-
}
|
|
175
|
-
if (svc.config.env) {
|
|
176
|
-
for (const value of Object.values(svc.config.env)) {
|
|
177
|
-
if (isReference(value)) {
|
|
178
|
-
if (resourceIds.has(value.id))
|
|
179
|
-
deps.push(value.id);
|
|
180
|
-
}
|
|
181
|
-
else if (isInterpolation(value)) {
|
|
182
|
-
for (const v of value.values) {
|
|
183
|
-
if (isReference(v) && resourceIds.has(v.id))
|
|
184
|
-
deps.push(v.id);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
return deps;
|
|
190
|
-
}
|
|
191
|
-
function formatDuration(ms) {
|
|
192
|
-
if (ms < 1000)
|
|
193
|
-
return `${ms}ms`;
|
|
194
|
-
return `${(ms / 1000).toFixed(1)}s`;
|
|
195
|
-
}
|
|
196
|
-
async function deploy(configFile, verbose) {
|
|
197
|
-
const sessionId = randomBytes(4).toString("hex");
|
|
198
|
-
const sessionLog = logger.child({ sessionId, command: "deploy" });
|
|
199
|
-
const start = performance.now();
|
|
200
|
-
const project = await findProjectName(configFile);
|
|
201
|
-
process.env.VYFT_PROJECT = project;
|
|
202
|
-
log.step(`Deploying ${pc.bold(project)}`);
|
|
203
|
-
const config = await import(configFile);
|
|
204
|
-
const resources = collectResources(config);
|
|
205
|
-
if (resources.length === 0) {
|
|
206
|
-
log.warn("No resources found");
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
sessionLog.info({ project, resourceCount: resources.length }, "deploy started");
|
|
210
|
-
const docker = createRuntime(resources, project, sessionLog);
|
|
211
|
-
docker.verbose = verbose;
|
|
212
|
-
await docker.ensureInfrastructure();
|
|
213
|
-
const currentResources = await docker.listManagedResources();
|
|
214
|
-
const silent = !verbose;
|
|
215
|
-
// Collect which service IDs have dependents waiting on them
|
|
216
|
-
const services = resources.filter((r) => r.type === "service");
|
|
217
|
-
const depTargets = new Set();
|
|
218
|
-
for (const svc of services) {
|
|
219
|
-
for (const dep of svc.config.dependsOn ?? []) {
|
|
220
|
-
depTargets.add(dep.id);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
// Build DAG tasks
|
|
224
|
-
const tasks = [];
|
|
225
|
-
// Secrets & volumes: no deps, check existence first
|
|
226
|
-
for (const r of resources) {
|
|
227
|
-
if (r.type === "secret" || r.type === "volume") {
|
|
228
|
-
tasks.push({
|
|
229
|
-
id: r.id,
|
|
230
|
-
deps: [],
|
|
231
|
-
run: async () => {
|
|
232
|
-
const name = stripPrefix(r.id, project);
|
|
233
|
-
if (await docker.exists(r)) {
|
|
234
|
-
return { name, action: "updated", unchanged: true, duration: 0 };
|
|
235
|
-
}
|
|
236
|
-
const result = await docker.create(r);
|
|
237
|
-
return { name, ...result, unchanged: false };
|
|
238
|
-
},
|
|
239
|
-
});
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
// Image builds: no deps
|
|
243
|
-
for (const svc of services) {
|
|
244
|
-
if (typeof svc.config.image === "object") {
|
|
245
|
-
tasks.push({
|
|
246
|
-
id: `build:${svc.id}`,
|
|
247
|
-
deps: [],
|
|
248
|
-
run: async () => {
|
|
249
|
-
const name = stripPrefix(svc.id, project);
|
|
250
|
-
const buildStart = performance.now();
|
|
251
|
-
await docker.buildImage(svc);
|
|
252
|
-
const duration = Math.round(performance.now() - buildStart);
|
|
253
|
-
return { name, action: "created", unchanged: false, duration };
|
|
254
|
-
},
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
// Services: depend on their secrets, volumes, image builds, and dependsOn
|
|
259
|
-
for (const svc of services) {
|
|
260
|
-
const deps = getServiceDeps(svc, resources);
|
|
261
|
-
tasks.push({
|
|
262
|
-
id: svc.id,
|
|
263
|
-
deps,
|
|
264
|
-
run: async () => {
|
|
265
|
-
const name = stripPrefix(svc.id, project);
|
|
266
|
-
const result = await docker.create(svc);
|
|
267
|
-
if (depTargets.has(svc.id)) {
|
|
268
|
-
await docker.waitForHealthy(svc.id);
|
|
269
|
-
}
|
|
270
|
-
return { name, ...result, unchanged: false };
|
|
271
|
-
},
|
|
272
|
-
});
|
|
273
|
-
}
|
|
274
|
-
// Sites: no deps (buildStatic runs internally)
|
|
275
|
-
for (const r of resources) {
|
|
276
|
-
if (r.type === "site") {
|
|
277
|
-
tasks.push({
|
|
278
|
-
id: r.id,
|
|
279
|
-
deps: [],
|
|
280
|
-
run: async () => {
|
|
281
|
-
const name = stripPrefix(r.id, project);
|
|
282
|
-
const result = await docker.create(r, { silent });
|
|
283
|
-
return { name, ...result, unchanged: false };
|
|
284
|
-
},
|
|
285
|
-
});
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
// Execute the graph
|
|
289
|
-
const results = [];
|
|
290
|
-
const inProgress = new Set();
|
|
291
|
-
if (verbose) {
|
|
292
|
-
// Verbose: print each item as it completes
|
|
293
|
-
await executeGraph(tasks, () => { }, (id, result) => {
|
|
294
|
-
results.push(result);
|
|
295
|
-
if (id.startsWith("build:")) {
|
|
296
|
-
log.success(`${pc.bold(result.name)} ${pc.dim("built")} ${pc.dim(formatDuration(result.duration))}`);
|
|
297
|
-
}
|
|
298
|
-
else if (result.unchanged) {
|
|
299
|
-
log.step(`${pc.bold(result.name)} ${pc.dim("exists")}`);
|
|
300
|
-
}
|
|
301
|
-
else {
|
|
302
|
-
const route = result.route
|
|
303
|
-
? ` ${pc.dim("\u2192")} ${result.route}`
|
|
304
|
-
: "";
|
|
305
|
-
log.success(`${pc.bold(result.name)} ${pc.dim(result.action)}${route}`);
|
|
306
|
-
}
|
|
307
|
-
});
|
|
308
|
-
}
|
|
309
|
-
else {
|
|
310
|
-
// Normal mode: single spinner, update message with in-progress tasks
|
|
311
|
-
const s = spinner();
|
|
312
|
-
function updateSpinner() {
|
|
313
|
-
const active = [...inProgress].map((id) => id.startsWith("build:")
|
|
314
|
-
? `Building ${stripPrefix(id.slice(6), project)}`
|
|
315
|
-
: `deploying ${stripPrefix(id, project)}`);
|
|
316
|
-
if (active.length === 0)
|
|
317
|
-
return;
|
|
318
|
-
const first = active[0];
|
|
319
|
-
const rest = active.length > 1 ? ` · ${active.slice(1).join(", ")}` : "";
|
|
320
|
-
s.message(`${first}${rest}`);
|
|
321
|
-
}
|
|
322
|
-
s.start("Deploying");
|
|
323
|
-
await executeGraph(tasks, (id) => {
|
|
324
|
-
// Skip build tasks from spinner display (they show as "Building X")
|
|
325
|
-
inProgress.add(id);
|
|
326
|
-
updateSpinner();
|
|
327
|
-
}, (id, result) => {
|
|
328
|
-
results.push(result);
|
|
329
|
-
inProgress.delete(id);
|
|
330
|
-
updateSpinner();
|
|
331
|
-
});
|
|
332
|
-
s.stop("Deployed");
|
|
333
|
-
}
|
|
334
|
-
// Summary
|
|
335
|
-
const unchanged = results.filter((r) => r.unchanged);
|
|
336
|
-
const changed = results.filter((r) => !r.unchanged && !r.name.startsWith("build:"));
|
|
337
|
-
if (unchanged.length > 0) {
|
|
338
|
-
log.message(`${unchanged.length} resource${unchanged.length === 1 ? "" : "s"} unchanged`);
|
|
339
|
-
}
|
|
340
|
-
if (!verbose && changed.length > 0) {
|
|
341
|
-
const nameWidth = Math.max(...changed.map((r) => r.name.length));
|
|
342
|
-
const actionWidth = Math.max(...changed.map((r) => r.action.length));
|
|
343
|
-
const lines = changed.map((r) => {
|
|
344
|
-
const route = r.route ? ` ${pc.dim("\u2192")} ${r.route}` : "";
|
|
345
|
-
return `${pc.bold(r.name.padEnd(nameWidth))} ${pc.dim(r.action.padEnd(actionWidth))}${route}`;
|
|
346
|
-
});
|
|
347
|
-
log.message(lines.join("\n"));
|
|
348
|
-
}
|
|
349
|
-
// Removal phase
|
|
350
|
-
const desiredIds = new Set(resources.map((r) => r.id));
|
|
351
|
-
for (const r of currentResources) {
|
|
352
|
-
if (r.derived && r.parentService && desiredIds.has(r.parentService)) {
|
|
353
|
-
desiredIds.add(r.id);
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
const toRemove = currentResources.filter((r) => !desiredIds.has(r.id));
|
|
357
|
-
let removed = 0;
|
|
358
|
-
if (toRemove.length > 0) {
|
|
359
|
-
toRemove.sort((a, b) => DESTROY_ORDER[a.type] - DESTROY_ORDER[b.type]);
|
|
360
|
-
for (const { id, type } of toRemove) {
|
|
361
|
-
const resource = { id, type };
|
|
362
|
-
if (await docker.exists(resource)) {
|
|
363
|
-
await docker.remove(resource);
|
|
364
|
-
const name = stripPrefix(id, project);
|
|
365
|
-
if (verbose) {
|
|
366
|
-
log.step(`${pc.bold(name)} ${pc.dim("removed")}`);
|
|
367
|
-
}
|
|
368
|
-
removed++;
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
if (!verbose && removed > 0) {
|
|
372
|
-
log.message(`${removed} resource${removed === 1 ? "" : "s"} removed`);
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
const durationMs = Math.round(performance.now() - start);
|
|
376
|
-
const created = changed.length;
|
|
377
|
-
sessionLog.info({ project, created, removed, skipped: unchanged.length, durationMs }, "deploy completed");
|
|
378
|
-
docker.destroy();
|
|
379
|
-
log.step(pc.dim(formatDuration(durationMs)));
|
|
380
|
-
}
|
|
381
|
-
async function dev(configFile, verbose) {
|
|
382
|
-
const sessionId = randomBytes(4).toString("hex");
|
|
383
|
-
const sessionLog = logger.child({ sessionId, command: "dev" });
|
|
384
|
-
const project = await findProjectName(configFile);
|
|
385
|
-
process.env.VYFT_PROJECT = project;
|
|
386
|
-
const projectRoot = path.dirname(configFile);
|
|
387
|
-
const config = await import(configFile);
|
|
388
|
-
const resources = collectResources(config);
|
|
389
|
-
if (resources.length === 0) {
|
|
390
|
-
log.warn("No resources found");
|
|
391
|
-
return;
|
|
392
|
-
}
|
|
393
|
-
log.step("vyft dev");
|
|
394
|
-
// Partition resources
|
|
395
|
-
const managedSecrets = [];
|
|
396
|
-
const managedVolumes = [];
|
|
397
|
-
const managedServices = [];
|
|
398
|
-
const userServices = [];
|
|
399
|
-
for (const r of resources) {
|
|
400
|
-
if (r.type === "secret" && isManaged(r)) {
|
|
401
|
-
managedSecrets.push(r);
|
|
402
|
-
}
|
|
403
|
-
else if (r.type === "volume" && isManaged(r)) {
|
|
404
|
-
managedVolumes.push(r);
|
|
405
|
-
}
|
|
406
|
-
else if (r.type === "service" && isManaged(r)) {
|
|
407
|
-
managedServices.push(r);
|
|
408
|
-
}
|
|
409
|
-
else if (r.type === "service" && !isManaged(r)) {
|
|
410
|
-
userServices.push(r);
|
|
411
|
-
}
|
|
412
|
-
else if (r.type === "site" && verbose) {
|
|
413
|
-
log.info(`Skipping site ${r.id} (not supported in dev mode)`);
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
// Color palette for log prefixes
|
|
417
|
-
const colors = [pc.cyan, pc.magenta, pc.green, pc.yellow, pc.blue, pc.red];
|
|
418
|
-
const colorMap = new Map();
|
|
419
|
-
let colorIdx = 0;
|
|
420
|
-
function getColor(name) {
|
|
421
|
-
if (!colorMap.has(name)) {
|
|
422
|
-
colorMap.set(name, colors[colorIdx++ % colors.length]);
|
|
423
|
-
}
|
|
424
|
-
return colorMap.get(name);
|
|
425
|
-
}
|
|
426
|
-
const singleProcess = userServices.length === 1 && !verbose;
|
|
427
|
-
function writeLog(name, stream, text) {
|
|
428
|
-
if (singleProcess) {
|
|
429
|
-
const line = text.endsWith("\n") ? text : `${text}\n`;
|
|
430
|
-
const dest = stream === "stderr" ? process.stderr : process.stdout;
|
|
431
|
-
dest.write(line);
|
|
432
|
-
return;
|
|
433
|
-
}
|
|
434
|
-
const short = stripPrefix(name, project);
|
|
435
|
-
const color = getColor(short);
|
|
436
|
-
const prefix = `${color(pc.bold(short))} ${pc.dim("|")} `;
|
|
437
|
-
const line = text.endsWith("\n") ? text : `${text}\n`;
|
|
438
|
-
const dest = stream === "stderr" ? process.stderr : process.stdout;
|
|
439
|
-
dest.write(`${prefix}${line}`);
|
|
440
|
-
}
|
|
441
|
-
const runtime = new LocalRuntime(project, { parentLogger: sessionLog });
|
|
442
|
-
const devRunner = new DevRunner({ onLog: writeLog });
|
|
443
|
-
// Graceful shutdown
|
|
444
|
-
let stopping = false;
|
|
445
|
-
async function shutdown() {
|
|
446
|
-
if (stopping)
|
|
447
|
-
return;
|
|
448
|
-
stopping = true;
|
|
449
|
-
process.stdout.write("\n");
|
|
450
|
-
const s = spinner();
|
|
451
|
-
s.start("Shutting down");
|
|
452
|
-
devRunner.stop();
|
|
453
|
-
await runtime.stop();
|
|
454
|
-
s.stop("Stopped");
|
|
455
|
-
process.exit(0);
|
|
456
|
-
}
|
|
457
|
-
process.on("SIGINT", shutdown);
|
|
458
|
-
process.on("SIGTERM", shutdown);
|
|
459
|
-
// Topo-sort managed services
|
|
460
|
-
const sortedManaged = topoSortServices(managedServices);
|
|
461
|
-
const depTargets = new Set();
|
|
462
|
-
for (const svc of managedServices) {
|
|
463
|
-
for (const dep of svc.config.dependsOn ?? []) {
|
|
464
|
-
depTargets.add(dep.id);
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
const servicePorts = new Map();
|
|
468
|
-
const hostRemap = new Map();
|
|
469
|
-
// Start spinner (normal mode only)
|
|
470
|
-
const s = verbose ? null : spinner();
|
|
471
|
-
s?.start("Starting services");
|
|
472
|
-
// 1. Create infrastructure
|
|
473
|
-
await runtime.ensureInfrastructure();
|
|
474
|
-
// 2. Create secrets (in-memory)
|
|
475
|
-
for (const secret of managedSecrets) {
|
|
476
|
-
runtime.createSecret(secret);
|
|
477
|
-
if (verbose)
|
|
478
|
-
log.step(`Generated ${stripPrefix(secret.id, project)}`);
|
|
479
|
-
}
|
|
480
|
-
// 3. Create volumes
|
|
481
|
-
for (const vol of managedVolumes) {
|
|
482
|
-
await runtime.createVolume(vol);
|
|
483
|
-
if (verbose)
|
|
484
|
-
log.step(`Volume ${stripPrefix(vol.id, project)}`);
|
|
485
|
-
}
|
|
486
|
-
// 4. Create managed services
|
|
487
|
-
for (const svc of sortedManaged) {
|
|
488
|
-
const port = await runtime.createService(svc);
|
|
489
|
-
servicePorts.set(svc.id, port);
|
|
490
|
-
hostRemap.set(svc.host, "localhost");
|
|
491
|
-
if (verbose)
|
|
492
|
-
log.step(`Started ${stripPrefix(svc.id, project)} on localhost:${port}`);
|
|
493
|
-
if (depTargets.has(svc.id)) {
|
|
494
|
-
await runtime.waitForHealthy(svc.id);
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
// Wait for remaining health checks
|
|
498
|
-
for (const svc of sortedManaged) {
|
|
499
|
-
if (!depTargets.has(svc.id)) {
|
|
500
|
-
await runtime.waitForHealthy(svc.id);
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
s?.stop("Services ready");
|
|
504
|
-
// 5. Build dev processes for user services
|
|
505
|
-
const pm = detectPackageManager(projectRoot);
|
|
506
|
-
const secretValues = new Map();
|
|
507
|
-
for (const secret of managedSecrets) {
|
|
508
|
-
const val = runtime.getSecretValue(secret.id);
|
|
509
|
-
if (val)
|
|
510
|
-
secretValues.set(secret.id, val);
|
|
511
|
-
}
|
|
512
|
-
const devProcesses = userServices.map((svc) => {
|
|
513
|
-
const devConfig = svc.config.dev;
|
|
514
|
-
const command = devConfig?.command ?? `${pm} run dev`;
|
|
515
|
-
const cwd = devConfig?.cwd
|
|
516
|
-
? path.resolve(projectRoot, devConfig.cwd)
|
|
517
|
-
: projectRoot;
|
|
518
|
-
const env = svc.config.env
|
|
519
|
-
? resolveDevEnv(svc.config.env, secretValues, hostRemap)
|
|
520
|
-
: {};
|
|
521
|
-
return { service: svc, command, cwd, env };
|
|
522
|
-
});
|
|
523
|
-
if (devProcesses.length === 0 && sortedManaged.length === 0) {
|
|
524
|
-
log.warn("Nothing to run");
|
|
525
|
-
return;
|
|
526
|
-
}
|
|
527
|
-
// Print summary table
|
|
528
|
-
const allNames = [
|
|
529
|
-
...sortedManaged.map((svc) => stripPrefix(svc.id, project)),
|
|
530
|
-
...devProcesses.map((dp) => stripPrefix(dp.service.id, project)),
|
|
531
|
-
];
|
|
532
|
-
const nameWidth = allNames.length > 0 ? Math.max(...allNames.map((n) => n.length)) : 0;
|
|
533
|
-
const tableLines = [];
|
|
534
|
-
if (sortedManaged.length > 0) {
|
|
535
|
-
const imageWidth = Math.max(...sortedManaged.map((svc) => (typeof svc.config.image === "string" ? svc.config.image : "custom")
|
|
536
|
-
.length));
|
|
537
|
-
for (const svc of sortedManaged) {
|
|
538
|
-
const short = stripPrefix(svc.id, project);
|
|
539
|
-
const image = typeof svc.config.image === "string" ? svc.config.image : "custom";
|
|
540
|
-
const port = servicePorts.get(svc.id) ?? svc.port;
|
|
541
|
-
tableLines.push(`${pc.bold(short.padEnd(nameWidth))} ${pc.dim(image.padEnd(imageWidth))} ${pc.cyan(`localhost:${port}`)}`);
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
if (devProcesses.length > 0) {
|
|
545
|
-
if (tableLines.length > 0)
|
|
546
|
-
tableLines.push("");
|
|
547
|
-
for (const dp of devProcesses) {
|
|
548
|
-
const short = stripPrefix(dp.service.id, project);
|
|
549
|
-
tableLines.push(`${pc.bold(short.padEnd(nameWidth))} ${pc.dim(dp.command)}`);
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
if (tableLines.length > 0) {
|
|
553
|
-
log.message(tableLines.join("\n"));
|
|
554
|
-
}
|
|
555
|
-
log.message(pc.dim("Ctrl+C to stop"));
|
|
556
|
-
// 6. Start dev processes
|
|
557
|
-
if (devProcesses.length > 0) {
|
|
558
|
-
devRunner.start(devProcesses);
|
|
559
|
-
}
|
|
560
|
-
// 7. Stream container logs (verbose only)
|
|
561
|
-
if (verbose) {
|
|
562
|
-
for (const svc of sortedManaged) {
|
|
563
|
-
(async () => {
|
|
564
|
-
for await (const entry of runtime.containerLogs(svc.id)) {
|
|
565
|
-
writeLog(svc.id, entry.stream, entry.text);
|
|
566
|
-
}
|
|
567
|
-
})();
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
// Keep alive until signal
|
|
571
|
-
await new Promise(() => { });
|
|
572
|
-
}
|
|
573
|
-
async function destroy(searchDir, host) {
|
|
574
|
-
const sessionId = randomBytes(4).toString("hex");
|
|
575
|
-
const sessionLog = logger.child({ sessionId, command: "destroy" });
|
|
576
|
-
const start = performance.now();
|
|
577
|
-
const project = await findProjectName(path.join(searchDir, "dummy"));
|
|
578
|
-
log.step(`Destroying ${pc.bold(project)}`);
|
|
579
|
-
const docker = new DockerClient(project, {
|
|
580
|
-
host,
|
|
581
|
-
parentLogger: sessionLog,
|
|
582
|
-
});
|
|
583
|
-
const currentResources = await docker.listManagedResources();
|
|
584
|
-
if (currentResources.length === 0) {
|
|
585
|
-
log.warn("No resources found");
|
|
586
|
-
docker.destroy();
|
|
587
|
-
return;
|
|
588
|
-
}
|
|
589
|
-
sessionLog.info({ project, resourceCount: currentResources.length }, "destroy started");
|
|
590
|
-
// Group by type: services/sites first (parallel), then volumes/secrets (parallel)
|
|
591
|
-
const servicesSites = currentResources.filter((r) => r.type === "service" || r.type === "site");
|
|
592
|
-
const volumesSecrets = currentResources.filter((r) => r.type === "volume" || r.type === "secret");
|
|
593
|
-
const removed = [];
|
|
594
|
-
const sem = createSemaphore(MAX_CONCURRENCY);
|
|
595
|
-
async function removeGroup(group, onRemoved) {
|
|
596
|
-
await Promise.all(group.map(async ({ id, type }) => {
|
|
597
|
-
await sem.acquire();
|
|
598
|
-
try {
|
|
599
|
-
const resource = { id, type };
|
|
600
|
-
if (await docker.exists(resource)) {
|
|
601
|
-
await docker.remove(resource);
|
|
602
|
-
const name = stripPrefix(id, project);
|
|
603
|
-
removed.push(name);
|
|
604
|
-
onRemoved(name);
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
finally {
|
|
608
|
-
sem.release();
|
|
609
|
-
}
|
|
610
|
-
}));
|
|
611
|
-
}
|
|
612
|
-
const s = spinner();
|
|
613
|
-
s.start("Destroying");
|
|
614
|
-
// Phase 1: Remove services and sites in parallel
|
|
615
|
-
if (servicesSites.length > 0) {
|
|
616
|
-
await removeGroup(servicesSites, (name) => {
|
|
617
|
-
s.message(`removing ${name}`);
|
|
618
|
-
});
|
|
619
|
-
}
|
|
620
|
-
// Phase 2: Remove volumes and secrets in parallel
|
|
621
|
-
if (volumesSecrets.length > 0) {
|
|
622
|
-
await removeGroup(volumesSecrets, (name) => {
|
|
623
|
-
s.message(`removing ${name}`);
|
|
624
|
-
});
|
|
625
|
-
}
|
|
626
|
-
await docker.removeProjectNetwork();
|
|
627
|
-
s.stop("Destroyed");
|
|
628
|
-
if (removed.length > 0) {
|
|
629
|
-
const nameWidth = Math.max(...removed.map((n) => n.length));
|
|
630
|
-
const lines = removed.map((name) => `${pc.bold(name.padEnd(nameWidth))} ${pc.dim("removed")}`);
|
|
631
|
-
log.message(lines.join("\n"));
|
|
632
|
-
}
|
|
633
|
-
const durationMs = Math.round(performance.now() - start);
|
|
634
|
-
sessionLog.info({ project, removed: removed.length, durationMs }, "destroy completed");
|
|
635
|
-
docker.destroy();
|
|
636
|
-
log.step(pc.dim(formatDuration(durationMs)));
|
|
637
|
-
}
|
|
638
|
-
const program = new Command();
|
|
639
|
-
program
|
|
640
|
-
.name("vyft")
|
|
641
|
-
.description("Deploy apps to Docker Swarm with TypeScript")
|
|
642
|
-
.version("0.1.0")
|
|
643
|
-
.option("--context <name>", "override active context for this invocation");
|
|
644
|
-
program
|
|
645
|
-
.command("init")
|
|
646
|
-
.description("Create a new project")
|
|
647
|
-
.argument("[directory]", "directory to create the project in")
|
|
648
|
-
.action(async (directory) => {
|
|
649
|
-
await init(directory);
|
|
650
|
-
});
|
|
651
|
-
program
|
|
652
|
-
.command("deploy")
|
|
653
|
-
.description("Deploy resources from a config file")
|
|
654
|
-
.argument("[config-file]", "path to config file", "vyft.config.ts")
|
|
655
|
-
.option("--verbose", "show build output", false)
|
|
656
|
-
.action(async (configFile, opts) => {
|
|
657
|
-
const absolutePath = path.resolve(process.cwd(), configFile);
|
|
658
|
-
if (configFile === "vyft.config.ts") {
|
|
659
|
-
try {
|
|
660
|
-
await access(absolutePath);
|
|
661
|
-
}
|
|
662
|
-
catch {
|
|
663
|
-
program.error("No config file specified and vyft.config.ts not found");
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
try {
|
|
667
|
-
await deploy(absolutePath, opts.verbose);
|
|
668
|
-
}
|
|
669
|
-
catch (err) {
|
|
670
|
-
logger.fatal({ err }, "deploy failed");
|
|
671
|
-
throw err;
|
|
672
|
-
}
|
|
673
|
-
});
|
|
674
|
-
program
|
|
675
|
-
.command("dev")
|
|
676
|
-
.description("Start local development environment")
|
|
677
|
-
.argument("[config-file]", "path to config file", "vyft.config.ts")
|
|
678
|
-
.option("--verbose", "show detailed output", false)
|
|
679
|
-
.action(async (configFile, opts) => {
|
|
680
|
-
const absolutePath = path.resolve(process.cwd(), configFile);
|
|
681
|
-
if (configFile === "vyft.config.ts") {
|
|
682
|
-
try {
|
|
683
|
-
await access(absolutePath);
|
|
684
|
-
}
|
|
685
|
-
catch {
|
|
686
|
-
program.error("No config file specified and vyft.config.ts not found");
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
try {
|
|
690
|
-
await dev(absolutePath, opts.verbose);
|
|
691
|
-
}
|
|
692
|
-
catch (err) {
|
|
693
|
-
logger.fatal({ err }, "dev failed");
|
|
694
|
-
throw err;
|
|
695
|
-
}
|
|
696
|
-
});
|
|
697
|
-
const proxy = program.command("proxy").description("Manage the proxy");
|
|
698
|
-
proxy
|
|
699
|
-
.command("logs")
|
|
700
|
-
.description("View proxy request logs")
|
|
701
|
-
.option("-f, --follow", "stream logs continuously", false)
|
|
702
|
-
.option("--tail <n>", "number of lines to show", "100")
|
|
703
|
-
.action(async (opts) => {
|
|
704
|
-
const project = await findProjectName(path.join(process.cwd(), "package.json"));
|
|
705
|
-
const docker = new DockerClient(project, { host: getContextHost() });
|
|
706
|
-
await docker.proxy.logs({
|
|
707
|
-
follow: opts.follow,
|
|
708
|
-
tail: parseInt(opts.tail, 10),
|
|
709
|
-
});
|
|
710
|
-
});
|
|
711
|
-
program
|
|
712
|
-
.command("logs")
|
|
713
|
-
.description("View service logs")
|
|
714
|
-
.argument("[services...]", "service names to filter (short names without project prefix)")
|
|
715
|
-
.option("-f, --follow", "stream logs continuously", false)
|
|
716
|
-
.option("--tail <n>", "number of lines to show", "100")
|
|
717
|
-
.action(async (services, opts) => {
|
|
718
|
-
const project = await findProjectName(path.join(process.cwd(), "package.json"));
|
|
719
|
-
const docker = new DockerClient(project, { host: getContextHost() });
|
|
720
|
-
// Support comma-separated names: "api,web" -> ["api", "web"]
|
|
721
|
-
const parsed = services.flatMap((s) => s.split(","));
|
|
722
|
-
const logs = docker.serviceLogs({
|
|
723
|
-
follow: opts.follow,
|
|
724
|
-
tail: parseInt(opts.tail, 10),
|
|
725
|
-
services: parsed.length > 0 ? parsed : undefined,
|
|
726
|
-
});
|
|
727
|
-
const colors = [
|
|
728
|
-
pc.cyan,
|
|
729
|
-
pc.magenta,
|
|
730
|
-
pc.green,
|
|
731
|
-
pc.yellow,
|
|
732
|
-
pc.blue,
|
|
733
|
-
pc.red,
|
|
734
|
-
];
|
|
735
|
-
const colorMap = new Map();
|
|
736
|
-
let colorIdx = 0;
|
|
737
|
-
for await (const entry of logs) {
|
|
738
|
-
if (!colorMap.has(entry.service)) {
|
|
739
|
-
colorMap.set(entry.service, colors[colorIdx++ % colors.length]);
|
|
740
|
-
}
|
|
741
|
-
const color = colorMap.get(entry.service);
|
|
742
|
-
const prefix = parsed.length === 1
|
|
743
|
-
? ""
|
|
744
|
-
: `${color(pc.bold(entry.service))} ${pc.dim("|")} `;
|
|
745
|
-
const line = entry.text.endsWith("\n") ? entry.text : `${entry.text}\n`;
|
|
746
|
-
const dest = entry.stream === "stderr" ? process.stderr : process.stdout;
|
|
747
|
-
dest.write(`${prefix}${line}`);
|
|
748
|
-
}
|
|
749
|
-
});
|
|
750
|
-
program
|
|
751
|
-
.command("destroy")
|
|
752
|
-
.description("Destroy all deployed resources")
|
|
753
|
-
.argument("[config-file]", "path to config file (used to locate project)")
|
|
754
|
-
.action(async (configFile) => {
|
|
755
|
-
const absolutePath = configFile
|
|
756
|
-
? path.resolve(process.cwd(), configFile)
|
|
757
|
-
: process.cwd();
|
|
758
|
-
try {
|
|
759
|
-
await destroy(absolutePath, getContextHost());
|
|
760
|
-
}
|
|
761
|
-
catch (err) {
|
|
762
|
-
logger.fatal({ err }, "destroy failed");
|
|
763
|
-
throw err;
|
|
764
|
-
}
|
|
765
|
-
});
|
|
766
|
-
const context = program
|
|
767
|
-
.command("context")
|
|
768
|
-
.description("Manage deployment contexts");
|
|
769
|
-
context
|
|
770
|
-
.command("create")
|
|
771
|
-
.description("Create a new context")
|
|
772
|
-
.argument("<name>", "context name")
|
|
773
|
-
.requiredOption("--host <endpoint>", "Docker endpoint (e.g. ssh://root@1.2.3.4)")
|
|
774
|
-
.option("--runtime <rt>", "runtime backend", "swarm")
|
|
775
|
-
.option("--description <text>", "optional description")
|
|
776
|
-
.action((name, opts) => {
|
|
777
|
-
saveContext({
|
|
778
|
-
name,
|
|
779
|
-
host: opts.host,
|
|
780
|
-
runtime: opts.runtime,
|
|
781
|
-
description: opts.description,
|
|
782
|
-
});
|
|
783
|
-
console.log(`Context "${name}" created`);
|
|
784
|
-
});
|
|
785
|
-
context
|
|
786
|
-
.command("ls")
|
|
787
|
-
.description("List all contexts")
|
|
788
|
-
.action(() => {
|
|
789
|
-
const contexts = listContexts();
|
|
790
|
-
if (contexts.length === 0) {
|
|
791
|
-
console.log("No contexts configured. Create one with: vyft context create <name> --host <endpoint>");
|
|
792
|
-
return;
|
|
793
|
-
}
|
|
794
|
-
const current = getCurrentContextName();
|
|
795
|
-
for (const ctx of contexts) {
|
|
796
|
-
const marker = ctx.name === current ? "* " : " ";
|
|
797
|
-
const desc = ctx.description ? ` - ${ctx.description}` : "";
|
|
798
|
-
const host = ctx.host ? ` (${ctx.host})` : "";
|
|
799
|
-
console.log(`${marker}${ctx.name}${host}${desc}`);
|
|
800
|
-
}
|
|
801
|
-
});
|
|
802
|
-
context
|
|
803
|
-
.command("show")
|
|
804
|
-
.description("Print current context name")
|
|
805
|
-
.action(() => {
|
|
806
|
-
const name = getCurrentContextName();
|
|
807
|
-
if (!name) {
|
|
808
|
-
console.log("No context set");
|
|
809
|
-
}
|
|
810
|
-
else {
|
|
811
|
-
console.log(name);
|
|
812
|
-
}
|
|
813
|
-
});
|
|
814
|
-
context
|
|
815
|
-
.command("use")
|
|
816
|
-
.description("Set the active context")
|
|
817
|
-
.argument("<name>", "context name")
|
|
818
|
-
.action((name) => {
|
|
819
|
-
setCurrentContext(name);
|
|
820
|
-
console.log(`Switched to context "${name}"`);
|
|
821
|
-
});
|
|
822
|
-
context
|
|
823
|
-
.command("rm")
|
|
824
|
-
.description("Remove a context")
|
|
825
|
-
.argument("<name>", "context name")
|
|
826
|
-
.action((name) => {
|
|
827
|
-
removeContext(name);
|
|
828
|
-
console.log(`Context "${name}" removed`);
|
|
829
|
-
});
|
|
830
|
-
context
|
|
831
|
-
.command("inspect")
|
|
832
|
-
.description("Print context as JSON")
|
|
833
|
-
.argument("[name]", "context name (defaults to current)")
|
|
834
|
-
.action((name) => {
|
|
835
|
-
const ctx = resolveContext(name);
|
|
836
|
-
if (!ctx) {
|
|
837
|
-
console.log("No context set");
|
|
838
|
-
return;
|
|
839
|
-
}
|
|
840
|
-
console.log(JSON.stringify(ctx, null, 2));
|
|
841
|
-
});
|
|
842
|
-
await program.parseAsync();
|