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.
Files changed (44) hide show
  1. package/README.md +5 -16
  2. package/dist/cli.js +170 -9
  3. package/dist/context.d.ts +39 -0
  4. package/dist/context.js +101 -0
  5. package/dist/docker.d.ts +14 -9
  6. package/dist/docker.js +145 -317
  7. package/dist/index.d.ts +2 -0
  8. package/dist/index.js +3 -0
  9. package/dist/init.js +19 -2
  10. package/dist/interpolate.d.ts +11 -0
  11. package/dist/interpolate.js +11 -0
  12. package/dist/proxy.d.ts +16 -0
  13. package/dist/proxy.js +0 -0
  14. package/dist/resource.d.ts +97 -1
  15. package/dist/resource.js +11 -1
  16. package/dist/runtime.d.ts +4 -0
  17. package/dist/services/index.d.ts +24 -0
  18. package/dist/services/index.js +20 -0
  19. package/dist/services/minio.d.ts +36 -0
  20. package/dist/services/minio.js +53 -0
  21. package/dist/services/mongo.d.ts +28 -0
  22. package/dist/services/mongo.js +45 -0
  23. package/dist/services/mysql.d.ts +28 -0
  24. package/dist/services/mysql.js +44 -0
  25. package/dist/services/nats.d.ts +26 -0
  26. package/dist/services/nats.js +38 -0
  27. package/dist/services/postgres.d.ts +28 -0
  28. package/dist/services/postgres.js +45 -0
  29. package/dist/services/rabbitmq.d.ts +28 -0
  30. package/dist/services/rabbitmq.js +44 -0
  31. package/dist/services/redis.d.ts +28 -0
  32. package/dist/services/redis.js +49 -0
  33. package/dist/services/storage.d.ts +39 -0
  34. package/dist/services/storage.js +94 -0
  35. package/dist/swarm/factories.d.ts +9 -2
  36. package/dist/swarm/factories.js +9 -32
  37. package/dist/swarm/index.d.ts +11 -2
  38. package/dist/swarm/proxy.d.ts +24 -0
  39. package/dist/swarm/proxy.js +339 -0
  40. package/dist/swarm/types.d.ts +11 -21
  41. package/dist/symbols.d.ts +5 -0
  42. package/dist/symbols.js +1 -0
  43. package/package.json +2 -5
  44. package/templates/fullstack/vyft.config.ts +13 -28
package/dist/docker.js CHANGED
@@ -1,4 +1,3 @@
1
- import { execSync } from "node:child_process";
2
1
  import { randomBytes } from "node:crypto";
3
2
  import path from "node:path";
4
3
  import { PassThrough } from "node:stream";
@@ -8,18 +7,7 @@ import tar from "tar-fs";
8
7
  import { buildStatic } from "./build.js";
9
8
  import { logger as defaultLogger } from "./logger.js";
10
9
  import { isInterpolation, isReference } from "./resource.js";
11
- const PROXY_SERVICE_NAME = "vyft-proxy";
12
- function resolveDockerHost() {
13
- if (process.env.DOCKER_HOST)
14
- return undefined;
15
- try {
16
- const host = execSync("docker context inspect --format '{{.Endpoints.docker.Host}}'", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
17
- if (host && host !== `unix:///var/run/docker.sock`)
18
- return host;
19
- }
20
- catch { }
21
- return undefined;
22
- }
10
+ import { CaddyProxy } from "./swarm/proxy.js";
23
11
  function secretMount(id, name) {
24
12
  return {
25
13
  SecretID: id,
@@ -67,18 +55,6 @@ export function parseRoute(route) {
67
55
  path: route.slice(slashIndex),
68
56
  };
69
57
  }
70
- export function buildCaddyRoute(resourceId, route, handler) {
71
- const { host, path } = parseRoute(route);
72
- const match = { host: [host] };
73
- if (path)
74
- match.path = [path];
75
- return {
76
- "@id": `vyft-${resourceId}`,
77
- match: [match],
78
- terminal: true,
79
- handle: [handler],
80
- };
81
- }
82
58
  export class DockerClient {
83
59
  docker;
84
60
  localDocker;
@@ -86,21 +62,23 @@ export class DockerClient {
86
62
  project;
87
63
  secretValues = new Map();
88
64
  log;
65
+ proxy;
89
66
  verbose = false;
90
- constructor(project, parentLogger) {
67
+ constructor(project, opts) {
91
68
  this.localDocker = new Docker({ socketPath: "/var/run/docker.sock" });
92
- const contextHost = resolveDockerHost();
93
- if (contextHost)
94
- process.env.DOCKER_HOST = contextHost;
69
+ const host = opts?.host;
70
+ if (host)
71
+ process.env.DOCKER_HOST = host;
95
72
  this.docker = new Docker();
96
- this.isRemote = !!(contextHost || process.env.DOCKER_HOST);
73
+ this.isRemote = !!host;
97
74
  this.project = project;
98
- this.log = (parentLogger ?? defaultLogger).child({
75
+ this.log = (opts?.parentLogger ?? defaultLogger).child({
99
76
  component: "docker",
100
77
  project,
101
78
  });
102
- if (contextHost)
103
- this.log.debug({ host: contextHost }, "using docker context endpoint");
79
+ this.proxy = new CaddyProxy(this.docker, this.log);
80
+ if (host)
81
+ this.log.debug({ host }, "using remote endpoint");
104
82
  }
105
83
  async ensureNetwork(name, labels = {}) {
106
84
  const networks = await this.docker.listNetworks({
@@ -125,247 +103,9 @@ export class DockerClient {
125
103
  "vyft.managed": "true",
126
104
  "vyft.project": this.project,
127
105
  });
128
- this.log.debug("checking proxy existence");
129
- const exists = await this.serviceExists(PROXY_SERVICE_NAME);
130
- if (exists) {
131
- this.log.debug("proxy already exists, verifying config structure");
132
- try {
133
- const raw = await this.caddyApiRequest("GET", "/config/");
134
- const config = JSON.parse(raw);
135
- if (!config?.apps?.http?.servers?.main) {
136
- this.log.debug("proxy config missing servers.main, re-seeding");
137
- await this.seedCaddyConfig();
138
- }
139
- }
140
- catch {
141
- this.log.debug("proxy config check failed, re-seeding");
142
- await this.seedCaddyConfig();
143
- }
144
- return;
145
- }
146
- // Create shared proxy network
147
- await this.ensureNetwork("vyft-network", { "vyft.infrastructure": "true" });
148
- this.log.debug("creating proxy service");
149
- await this.docker.createService({
150
- Name: PROXY_SERVICE_NAME,
151
- Labels: { "vyft.infrastructure": "true" },
152
- TaskTemplate: {
153
- ContainerSpec: {
154
- Image: "caddy:latest",
155
- Command: ["caddy", "run", "--resume"],
156
- Mounts: [
157
- {
158
- Type: "volume",
159
- Source: "vyft-proxy-config",
160
- Target: "/config",
161
- },
162
- {
163
- Type: "volume",
164
- Source: "vyft-proxy-data",
165
- Target: "/data",
166
- },
167
- ],
168
- Labels: { "vyft.infrastructure": "true" },
169
- },
170
- Networks: [{ Target: "vyft-network" }],
171
- },
172
- EndpointSpec: {
173
- Ports: [
174
- {
175
- Protocol: "tcp",
176
- TargetPort: 80,
177
- PublishedPort: 80,
178
- PublishMode: "ingress",
179
- },
180
- {
181
- Protocol: "tcp",
182
- TargetPort: 443,
183
- PublishedPort: 443,
184
- PublishMode: "ingress",
185
- },
186
- ],
187
- },
188
- Mode: { Replicated: { Replicas: 1 } },
189
- });
190
- log.step("Created vyft-proxy");
191
- await this.seedCaddyConfig();
106
+ await this.proxy.ensure();
192
107
  const durationMs = Math.round(performance.now() - start);
193
- this.log.info({ proxyCreated: true, durationMs }, "infrastructure ready");
194
- }
195
- async findProxyContainer() {
196
- const containers = await this.docker.listContainers({
197
- filters: JSON.stringify({
198
- label: [`com.docker.swarm.service.name=${PROXY_SERVICE_NAME}`],
199
- }),
200
- });
201
- if (containers.length === 0) {
202
- throw new Error("vyft-proxy container not found");
203
- }
204
- return this.docker.getContainer(containers[0].Id);
205
- }
206
- async caddyApiRequest(method, path, body) {
207
- const start = performance.now();
208
- this.log.debug({ method, path }, "caddy request started");
209
- const container = await this.findProxyContainer();
210
- let cmd;
211
- if (method === "POST") {
212
- const bodyStr = body ? JSON.stringify(body) : "";
213
- cmd = [
214
- "wget",
215
- "-q",
216
- "-O",
217
- "-",
218
- "--timeout=10",
219
- "--header=Content-Type: application/json",
220
- `--post-data=${bodyStr}`,
221
- `http://127.0.0.1:2019${path}`,
222
- ];
223
- }
224
- else if (method === "DELETE") {
225
- cmd = [
226
- "curl",
227
- "-s",
228
- "--connect-timeout",
229
- "5",
230
- "--max-time",
231
- "10",
232
- "-X",
233
- "DELETE",
234
- `http://127.0.0.1:2019${path}`,
235
- ];
236
- }
237
- else {
238
- cmd = [
239
- "wget",
240
- "-q",
241
- "-O",
242
- "-",
243
- "--timeout=10",
244
- `http://127.0.0.1:2019${path}`,
245
- ];
246
- }
247
- this.log.trace({ cmd }, "caddy exec command");
248
- const exec = await container.exec({
249
- Cmd: cmd,
250
- AttachStdout: true,
251
- AttachStderr: true,
252
- });
253
- const stream = await exec.start({});
254
- return new Promise((resolve, reject) => {
255
- const stdout = [];
256
- const stderr = [];
257
- const stdoutStream = new PassThrough();
258
- const stderrStream = new PassThrough();
259
- stdoutStream.on("data", (chunk) => stdout.push(chunk));
260
- stderrStream.on("data", (chunk) => stderr.push(chunk));
261
- this.docker.modem.demuxStream(stream, stdoutStream, stderrStream);
262
- stream.on("end", () => {
263
- const output = Buffer.concat(stdout).toString();
264
- const errOutput = Buffer.concat(stderr).toString();
265
- const durationMs = Math.round(performance.now() - start);
266
- this.log.trace({ stdoutBytes: output.length, stderrBytes: errOutput.length }, "caddy exec output");
267
- if (errOutput) {
268
- this.log.debug({ method, path, durationMs, stderr: errOutput }, "caddy request failed");
269
- reject(new Error(`Caddy API ${method} ${path} failed: ${errOutput}`));
270
- }
271
- else {
272
- this.log.debug({ method, path, durationMs }, "caddy request completed");
273
- resolve(output);
274
- }
275
- });
276
- });
277
- }
278
- async seedCaddyConfig() {
279
- const maxWait = 30000;
280
- const interval = 2000;
281
- let waited = 0;
282
- this.log.debug("waiting for proxy container");
283
- while (waited < maxWait) {
284
- try {
285
- await this.findProxyContainer();
286
- break;
287
- }
288
- catch (err) {
289
- this.log.debug({ err, waited, maxWait }, "proxy container not ready");
290
- await new Promise((r) => setTimeout(r, interval));
291
- waited += interval;
292
- }
293
- }
294
- const baseConfig = {
295
- logging: {
296
- logs: {
297
- default: {
298
- level: "INFO",
299
- },
300
- },
301
- },
302
- apps: {
303
- http: {
304
- servers: {
305
- main: {
306
- listen: [":443", ":80"],
307
- routes: [],
308
- logs: {},
309
- },
310
- },
311
- },
312
- },
313
- };
314
- const maxAttempts = 5;
315
- for (let attempt = 0; attempt < maxAttempts; attempt++) {
316
- try {
317
- this.log.debug({ attempt: attempt + 1, max: maxAttempts }, "seeding caddy config");
318
- await this.caddyApiRequest("POST", "/load", baseConfig);
319
- this.log.debug("caddy config seeded");
320
- return;
321
- }
322
- catch (err) {
323
- this.log.debug({ err, attempt: attempt + 1, max: maxAttempts }, "seed attempt failed");
324
- if (attempt >= Math.floor(maxAttempts * 0.8)) {
325
- this.log.warn({ attempt: attempt + 1, max: maxAttempts }, "seed retry nearing limit");
326
- }
327
- await new Promise((r) => setTimeout(r, 2000));
328
- }
329
- }
330
- this.log.error("caddy config seed exhausted retries");
331
- throw new Error("Failed to seed Caddy config after retries");
332
- }
333
- async addRoute(resourceId, route, handler) {
334
- this.log.debug({ resourceId, route }, "adding route");
335
- const caddyRoute = buildCaddyRoute(resourceId, route, handler);
336
- // Delete existing route for idempotency
337
- try {
338
- await this.caddyApiRequest("DELETE", `/id/vyft-${resourceId}`);
339
- }
340
- catch (err) {
341
- this.log.debug({ err, resourceId }, "idempotent route delete failed");
342
- }
343
- const maxAttempts = 5;
344
- for (let attempt = 0; attempt < maxAttempts; attempt++) {
345
- try {
346
- await this.caddyApiRequest("POST", "/config/apps/http/servers/main/routes", caddyRoute);
347
- this.log.debug({ resourceId, route }, "route added");
348
- return;
349
- }
350
- catch (err) {
351
- this.log.debug({ err, resourceId, attempt: attempt + 1, max: maxAttempts }, "route add attempt failed");
352
- if (attempt === maxAttempts - 1) {
353
- this.log.error({ resourceId, route }, "route add exhausted retries");
354
- throw new Error(`Failed to add route for ${resourceId}`);
355
- }
356
- await new Promise((r) => setTimeout(r, 2000));
357
- }
358
- }
359
- }
360
- async removeRoute(resourceId) {
361
- this.log.debug({ resourceId }, "removing route");
362
- try {
363
- await this.caddyApiRequest("DELETE", `/id/vyft-${resourceId}`);
364
- this.log.debug({ resourceId }, "route removed");
365
- }
366
- catch (err) {
367
- this.log.debug({ err, resourceId }, "route removal failed (may not exist)");
368
- }
108
+ this.log.info({ durationMs }, "infrastructure ready");
369
109
  }
370
110
  async listManagedResources() {
371
111
  const resources = [];
@@ -781,9 +521,9 @@ export class DockerClient {
781
521
  }
782
522
  if (config.route) {
783
523
  const port = config.port || 3000;
784
- await this.addRoute(service.id, config.route, {
785
- handler: "reverse_proxy",
786
- upstreams: [{ dial: `${service.id}:${port}` }],
524
+ await this.proxy.addRoute(service.id, config.route, {
525
+ host: service.id,
526
+ port,
787
527
  });
788
528
  }
789
529
  const durationMs = Math.round(performance.now() - start);
@@ -805,10 +545,27 @@ export class DockerClient {
805
545
  return false;
806
546
  }
807
547
  }
548
+ async waitForHealthy(id, timeoutMs = 120000) {
549
+ const start = performance.now();
550
+ const interval = 2000;
551
+ this.log.debug({ resourceId: id }, "waiting for service to be healthy");
552
+ while (performance.now() - start < timeoutMs) {
553
+ const tasks = await this.docker.listTasks({
554
+ filters: { service: [id], "desired-state": ["running"] },
555
+ });
556
+ const healthy = tasks.some((t) => t.Status.State === "running");
557
+ if (healthy) {
558
+ this.log.debug({ resourceId: id }, "service is healthy");
559
+ return;
560
+ }
561
+ await new Promise((r) => setTimeout(r, interval));
562
+ }
563
+ throw new Error(`Service ${id} did not become healthy within ${timeoutMs}ms`);
564
+ }
808
565
  async removeService(id) {
809
566
  const start = performance.now();
810
567
  this.log.debug({ resourceId: id }, "removing service");
811
- await this.removeRoute(id);
568
+ await this.proxy.removeRoute(id);
812
569
  await this.docker.getService(id).remove();
813
570
  const maxWait = 60000;
814
571
  const interval = 2000;
@@ -901,13 +658,13 @@ export class DockerClient {
901
658
  "vyft.managed": "true",
902
659
  "vyft.project": this.project,
903
660
  };
904
- const command = static_.config.spa
905
- ? [
661
+ const command = static_.config.spa === false
662
+ ? ["caddy", "file-server", "--root", "/srv", "--listen", ":80"]
663
+ : [
906
664
  "sh",
907
665
  "-c",
908
666
  "printf ':80 {\\nroot * /srv\\ntry_files {path} /index.html\\nfile_server\\n}\\n' > /etc/caddy/Caddyfile && caddy run --config /etc/caddy/Caddyfile --adapter caddyfile",
909
- ]
910
- : ["caddy", "file-server", "--root", "/srv", "--listen", ":80"];
667
+ ];
911
668
  const serviceSpec = {
912
669
  Name: static_.id,
913
670
  Labels: serviceLabels,
@@ -945,9 +702,9 @@ export class DockerClient {
945
702
  this.log.debug({ resourceId: static_.id }, "creating caddy service for static site");
946
703
  await this.docker.createService(serviceSpec);
947
704
  }
948
- await this.addRoute(static_.id, static_.config.route, {
949
- handler: "reverse_proxy",
950
- upstreams: [{ dial: `${static_.id}:80` }],
705
+ await this.proxy.addRoute(static_.id, static_.config.route, {
706
+ host: static_.id,
707
+ port: 80,
951
708
  });
952
709
  const durationMs = Math.round(performance.now() - start);
953
710
  this.log.info({ resourceId: static_.id, route: static_.config.route, durationMs }, existing ? "site updated" : "site created");
@@ -956,7 +713,7 @@ export class DockerClient {
956
713
  async removeStatic(id) {
957
714
  const start = performance.now();
958
715
  this.log.debug({ resourceId: id }, "removing static site");
959
- await this.removeRoute(id);
716
+ await this.proxy.removeRoute(id);
960
717
  await this.docker.getService(id).remove();
961
718
  const maxWait = 30000;
962
719
  const interval = 2000;
@@ -989,10 +746,81 @@ export class DockerClient {
989
746
  this.log.info({ resourceId: id, durationMs }, "site removed");
990
747
  log.step(`Removed ${id}`);
991
748
  }
992
- async proxyLogs(options) {
993
- const container = await this.findProxyContainer();
749
+ async *serviceLogs(options) {
750
+ const allServices = await this.docker.listServices({
751
+ filters: JSON.stringify({
752
+ label: ["vyft.managed=true", `vyft.project=${this.project}`],
753
+ }),
754
+ });
755
+ const prefix = `${this.project}-`;
756
+ let matched = allServices;
757
+ if (options.services && options.services.length > 0) {
758
+ const filter = new Set(options.services);
759
+ matched = allServices.filter((svc) => {
760
+ const name = svc.Spec?.Name || "";
761
+ const short = name.startsWith(prefix)
762
+ ? name.slice(prefix.length)
763
+ : name;
764
+ return filter.has(short) || filter.has(name);
765
+ });
766
+ }
767
+ if (matched.length === 0) {
768
+ log.warn("No matching services found");
769
+ return;
770
+ }
994
771
  const tail = options.tail ?? 100;
995
- if (options.follow) {
772
+ // Collect all containers with their short service name
773
+ const targets = [];
774
+ for (const svc of matched) {
775
+ const name = svc.Spec?.Name || "";
776
+ const short = name.startsWith(prefix) ? name.slice(prefix.length) : name;
777
+ const containers = await this.docker.listContainers({
778
+ filters: JSON.stringify({
779
+ label: [`com.docker.swarm.service.name=${name}`],
780
+ }),
781
+ });
782
+ for (const cInfo of containers) {
783
+ targets.push({ container: this.docker.getContainer(cInfo.Id), short });
784
+ }
785
+ }
786
+ if (!options.follow) {
787
+ for (const { container, short } of targets) {
788
+ const buf = await container.logs({
789
+ stdout: true,
790
+ stderr: true,
791
+ follow: false,
792
+ tail,
793
+ timestamps: true,
794
+ });
795
+ let offset = 0;
796
+ while (offset + 8 <= buf.length) {
797
+ const type = buf[offset];
798
+ const size = buf.readUInt32BE(offset + 4);
799
+ offset += 8;
800
+ const text = buf.subarray(offset, offset + size).toString();
801
+ yield {
802
+ service: short,
803
+ stream: type === 2 ? "stderr" : "stdout",
804
+ text,
805
+ };
806
+ offset += size;
807
+ }
808
+ }
809
+ return;
810
+ }
811
+ // Follow mode: merge all container streams concurrently
812
+ const buffer = [];
813
+ let notify = null;
814
+ let active = targets.length;
815
+ const push = (entry) => {
816
+ buffer.push(entry);
817
+ if (notify) {
818
+ const n = notify;
819
+ notify = null;
820
+ n();
821
+ }
822
+ };
823
+ for (const { container, short } of targets) {
996
824
  const stream = await container.logs({
997
825
  stdout: true,
998
826
  stderr: true,
@@ -1000,38 +828,38 @@ export class DockerClient {
1000
828
  tail,
1001
829
  timestamps: true,
1002
830
  });
1003
- const stdoutStream = new PassThrough();
1004
- const stderrStream = new PassThrough();
1005
- stdoutStream.pipe(process.stdout);
1006
- stderrStream.pipe(process.stderr);
1007
- this.docker.modem.demuxStream(stream, stdoutStream, stderrStream);
1008
- await new Promise((resolve) => {
1009
- stream.on("end", resolve);
1010
- });
1011
- }
1012
- else {
1013
- const buf = await container.logs({
1014
- stdout: true,
1015
- stderr: true,
1016
- follow: false,
1017
- tail,
1018
- timestamps: true,
831
+ const stdoutPT = new PassThrough();
832
+ const stderrPT = new PassThrough();
833
+ stdoutPT.on("data", (chunk) => {
834
+ for (const line of chunk.toString().split("\n")) {
835
+ if (line)
836
+ push({ service: short, stream: "stdout", text: line });
837
+ }
1019
838
  });
1020
- // Demux the multiplexed buffer: each frame has an 8-byte header
1021
- // [stream_type(1), padding(3), size(4)] followed by the payload.
1022
- let offset = 0;
1023
- while (offset + 8 <= buf.length) {
1024
- const type = buf[offset];
1025
- const size = buf.readUInt32BE(offset + 4);
1026
- offset += 8;
1027
- const payload = buf.subarray(offset, offset + size);
1028
- if (type === 2) {
1029
- process.stderr.write(payload);
839
+ stderrPT.on("data", (chunk) => {
840
+ for (const line of chunk.toString().split("\n")) {
841
+ if (line)
842
+ push({ service: short, stream: "stderr", text: line });
1030
843
  }
1031
- else {
1032
- process.stdout.write(payload);
844
+ });
845
+ this.docker.modem.demuxStream(stream, stdoutPT, stderrPT);
846
+ stream.on("end", () => {
847
+ active--;
848
+ if (notify && active === 0) {
849
+ const n = notify;
850
+ notify = null;
851
+ n();
1033
852
  }
1034
- offset += size;
853
+ });
854
+ }
855
+ while (active > 0 || buffer.length > 0) {
856
+ if (buffer.length === 0) {
857
+ await new Promise((r) => {
858
+ notify = r;
859
+ });
860
+ }
861
+ while (buffer.length > 0) {
862
+ yield buffer.shift();
1035
863
  }
1036
864
  }
1037
865
  }
package/dist/index.d.ts CHANGED
@@ -2,5 +2,7 @@ export { interpolate } from "./interpolate.js";
2
2
  export type { EnvValue, HealthCheckConfig, Interpolation, Reference, Resource, ResourceLimits, ResourceType, Secret, SecretConfig, Service, ServiceConfig, Site, SiteConfig, Volume, VolumeConfig, } from "./resource.js";
3
3
  export { isInterpolation, isReference, isSecret, validateId, validateRoute, } from "./resource.js";
4
4
  export type { Runtime } from "./runtime.js";
5
+ export type { BackupConfig, Bucket, Minio, MinioConfig, Mongo, MongoConfig, Mysql, MysqlConfig, Nats, NatsConfig, Postgres, PostgresConfig, Rabbitmq, RabbitmqConfig, Redis, RedisConfig, Storage, StorageConfig, } from "./services/index.js";
5
6
  export type { RuntimeMeta, RuntimeRef } from "./symbols.js";
6
7
  export { VYFT_RUNTIME } from "./symbols.js";
8
+ export declare const service: (id: string, config: import("./swarm/types.js").SwarmServiceConfig) => import("./resource.js").Service, secret: (id: string, config?: import("./resource.js").SecretConfig) => import("./resource.js").Secret, volume: (id: string, config?: import("./resource.js").VolumeConfig) => import("./resource.js").Volume, site: (id: string, config: import("./resource.js").SiteConfig) => import("./resource.js").Site, postgres: (id: string, config?: import("./index.js").PostgresConfig) => import("./index.js").Postgres, mysql: (id: string, config?: import("./index.js").MysqlConfig) => import("./index.js").Mysql, redis: (id: string, config?: import("./index.js").RedisConfig) => import("./index.js").Redis, rabbitmq: (id: string, config?: import("./index.js").RabbitmqConfig) => import("./index.js").Rabbitmq, nats: (id: string, config?: import("./index.js").NatsConfig) => import("./index.js").Nats, mongo: (id: string, config?: import("./index.js").MongoConfig) => import("./index.js").Mongo, minio: (id: string, config?: import("./index.js").MinioConfig) => import("./index.js").Minio, storage: (id: string, config?: import("./index.js").StorageConfig) => import("./index.js").Storage;
package/dist/index.js CHANGED
@@ -1,3 +1,6 @@
1
1
  export { interpolate } from "./interpolate.js";
2
2
  export { isInterpolation, isReference, isSecret, validateId, validateRoute, } from "./resource.js";
3
3
  export { VYFT_RUNTIME } from "./symbols.js";
4
+ import { createFactories } from "./swarm/factories.js";
5
+ const factories = createFactories({});
6
+ export const { service, secret, volume, site, postgres, mysql, redis, rabbitmq, nats, mongo, minio, storage, } = factories;
package/dist/init.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { execSync } from "node:child_process";
1
2
  import { mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
2
3
  import path from "node:path";
3
4
  import { fileURLToPath } from "node:url";
@@ -90,11 +91,27 @@ export async function init(directory) {
90
91
  await writeFile(destPath, content);
91
92
  log.step(relativePath);
92
93
  }
94
+ const ua = process.env.npm_config_user_agent ?? "";
95
+ const pm = ua.startsWith("pnpm")
96
+ ? "pnpm"
97
+ : ua.startsWith("bun")
98
+ ? "bun"
99
+ : "npm";
100
+ const install = await confirm({
101
+ message: `Install dependencies with ${pm}?`,
102
+ });
103
+ if (isCancel(install))
104
+ return;
105
+ if (install) {
106
+ log.step(`Running ${pm} install...`);
107
+ execSync(`${pm} install`, { cwd: targetDir, stdio: "inherit" });
108
+ }
93
109
  outro("Project created");
94
110
  console.log();
95
111
  console.log("Next steps:");
96
112
  console.log(` cd ${path.relative(process.cwd(), targetDir)}`);
97
- console.log(" pnpm install");
98
- console.log(" pnpm dev");
113
+ if (!install)
114
+ console.log(` ${pm} install`);
115
+ console.log(` ${pm} dev`);
99
116
  console.log();
100
117
  }
@@ -1,2 +1,13 @@
1
1
  import type { Interpolation, Reference } from "./resource.js";
2
+ /**
3
+ * Tagged template literal for building strings that contain {@link Reference} values
4
+ * (e.g. secrets) which are resolved at deploy time.
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * const connStr = interpolate`postgres://user:${dbPassword}@${db.host}:5432/app`;
9
+ * ```
10
+ *
11
+ * @throws If none of the interpolated values are a {@link Reference}.
12
+ */
2
13
  export declare function interpolate(strings: TemplateStringsArray, ...values: Array<Reference | string>): Interpolation;
@@ -1,4 +1,15 @@
1
1
  import { isReference } from "./resource.js";
2
+ /**
3
+ * Tagged template literal for building strings that contain {@link Reference} values
4
+ * (e.g. secrets) which are resolved at deploy time.
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * const connStr = interpolate`postgres://user:${dbPassword}@${db.host}:5432/app`;
9
+ * ```
10
+ *
11
+ * @throws If none of the interpolated values are a {@link Reference}.
12
+ */
2
13
  export function interpolate(strings, ...values) {
3
14
  const hasRef = values.some(isReference);
4
15
  if (!hasRef) {
@@ -0,0 +1,16 @@
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 ADDED
File without changes