onveloz 0.0.0-beta.15 → 0.0.0-beta.17

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 (2) hide show
  1. package/dist/index.mjs +553 -123
  2. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -29,7 +29,6 @@ const BUILD_TIMEOUT_MS = 600 * 1e3;
29
29
  const DEPLOY_TIMEOUT_MS = 300 * 1e3;
30
30
  const DATABASE_PROVISION_TIMEOUT_MS = 300 * 1e3;
31
31
  const DATABASE_WAITING_ON_PROVIDER_AFTER_MS = 60 * 1e3;
32
- const DATABASE_HEALTH_POLL_INTERVAL_MS = 60 * 1e3;
33
32
  const DATABASE_ENGINES = [
34
33
  "postgresql",
35
34
  "mysql",
@@ -52,7 +51,6 @@ const DATABASE_ENGINE_DEFAULTS = {
52
51
  defaultVersion: "7"
53
52
  }
54
53
  };
55
- const DEFAULT_SLEEP_CHECK_INTERVAL_MS = 300 * 1e3;
56
54
 
57
55
  //#endregion
58
56
  //#region ../../packages/config/veloz-config.ts
@@ -65,17 +63,22 @@ const PackageManagerSchema = z$1.enum([
65
63
  "bun",
66
64
  "auto"
67
65
  ]);
66
+ const BuildMethodSchema = z$1.enum(["nixpacks", "dockerfile"]);
68
67
  const BuildConfigSchema = z$1.object({
68
+ method: BuildMethodSchema.default("nixpacks").optional(),
69
69
  command: z$1.string().nullable().optional(),
70
70
  nodeVersion: z$1.string().regex(/^[0-9]+(\.[0-9]+){0,2}(\.x)?$/).default("20").optional(),
71
71
  nixpkgsArchive: z$1.string().regex(/^[a-f0-9]{40}$/).optional(),
72
72
  packageManager: PackageManagerSchema.default("auto").optional(),
73
73
  installCommand: z$1.string().nullable().optional(),
74
74
  outputDir: z$1.string().nullable().optional(),
75
- aptPackages: z$1.array(z$1.string().regex(/^[a-z0-9][a-z0-9.+-]+$/, "Nome de pacote inválido")).optional()
75
+ aptPackages: z$1.array(z$1.string().regex(/^[a-z0-9][a-z0-9.+-]+$/, "Nome de pacote inválido")).optional(),
76
+ dockerfile: z$1.string().optional(),
77
+ context: z$1.string().optional()
76
78
  });
77
79
  const RuntimeConfigSchema = z$1.object({
78
80
  command: z$1.string().nullable().optional(),
81
+ preStartCommand: z$1.string().nullable().optional(),
79
82
  port: z$1.number().min(1).max(65535).default(3e3).optional(),
80
83
  fsGroup: z$1.number().int().min(0).max(65534).default(1001).optional(),
81
84
  healthCheck: z$1.object({
@@ -111,6 +114,20 @@ const VolumeConfigSchema = z$1.object({
111
114
  ].some((p) => value === p || value.startsWith(p + "/")), "Caminho de montagem não permitido por segurança"),
112
115
  sizeGb: z$1.number().int().min(10).max(100).optional().default(10)
113
116
  });
117
+ const DatabaseResourcesSchema = z$1.object({
118
+ cpu: z$1.string().regex(/^[0-9]+(\.[0-9]+)?|[0-9]+m$/).default("500m").optional(),
119
+ memory: z$1.string().regex(/^[0-9]+(Mi|Gi)$/).default("512Mi").optional()
120
+ });
121
+ const PoolerConfigSchema = z$1.object({
122
+ enabled: z$1.boolean().default(false),
123
+ poolMode: z$1.enum([
124
+ "transaction",
125
+ "session",
126
+ "statement"
127
+ ]).default("transaction").optional(),
128
+ defaultPoolSize: z$1.number().int().min(1).max(200).default(20).optional(),
129
+ maxClientConn: z$1.number().int().min(1).max(1e4).default(100).optional()
130
+ });
114
131
  const DatabaseConfigSchema = z$1.object({
115
132
  id: z$1.string().optional(),
116
133
  name: z$1.string().optional(),
@@ -121,10 +138,12 @@ const DatabaseConfigSchema = z$1.object({
121
138
  ]),
122
139
  version: z$1.string().optional(),
123
140
  storage: z$1.string().regex(/^[0-9]+(Gi)$/).default("10Gi").optional(),
141
+ resources: DatabaseResourcesSchema.optional(),
142
+ pooler: PoolerConfigSchema.optional(),
124
143
  fromTemplate: z$1.string().optional()
125
144
  });
126
145
  const ServiceConfigSchema = z$1.object({
127
- id: z$1.string(),
146
+ id: z$1.string().optional(),
128
147
  name: z$1.string(),
129
148
  type: ServiceTypeSchema.default("web"),
130
149
  root: z$1.string().default(".").optional(),
@@ -542,7 +561,9 @@ async function pollForToken(authClient, deviceCode, interval) {
542
561
  let pollingInterval = interval;
543
562
  const maxAttempts = Math.ceil(300 / pollingInterval);
544
563
  for (let i = 0; i < maxAttempts; i++) {
545
- await new Promise((r) => setTimeout(r, pollingInterval * 1e3));
564
+ await new Promise((r) => {
565
+ setTimeout(r, pollingInterval * 1e3);
566
+ });
546
567
  try {
547
568
  const { data, error } = await authClient.device.token({
548
569
  grant_type: "urn:ietf:params:oauth:grant-type:device_code",
@@ -795,6 +816,10 @@ function throwNotFound(flag, entries) {
795
816
  * 3. Default from `veloz use` → remembered choice
796
817
  * 4. Interactive prompt → last resort
797
818
  */
819
+ function requireServiceId(service) {
820
+ if (!service.id) throw new Error(`Serviço "${service.name}" não possui ID. Execute 'veloz deploy' para vincular o serviço.`);
821
+ return service;
822
+ }
798
823
  async function resolveService(serviceFlag) {
799
824
  const config = requireConfig();
800
825
  const entries = Object.entries(config.services);
@@ -805,7 +830,7 @@ async function resolveService(serviceFlag) {
805
830
  const [key, service] = found;
806
831
  return {
807
832
  key,
808
- service: mergeServiceWithDefaults(service, config.defaults),
833
+ service: requireServiceId(mergeServiceWithDefaults(service, config.defaults)),
809
834
  config
810
835
  };
811
836
  }
@@ -813,7 +838,7 @@ async function resolveService(serviceFlag) {
813
838
  const [key, service] = entries[0];
814
839
  return {
815
840
  key,
816
- service: mergeServiceWithDefaults(service, config.defaults),
841
+ service: requireServiceId(mergeServiceWithDefaults(service, config.defaults)),
817
842
  config
818
843
  };
819
844
  }
@@ -822,7 +847,7 @@ async function resolveService(serviceFlag) {
822
847
  const service = config.services[defaultKey];
823
848
  return {
824
849
  key: defaultKey,
825
- service: mergeServiceWithDefaults(service, config.defaults),
850
+ service: requireServiceId(mergeServiceWithDefaults(service, config.defaults)),
826
851
  config
827
852
  };
828
853
  }
@@ -832,7 +857,7 @@ async function resolveService(serviceFlag) {
832
857
  })));
833
858
  return {
834
859
  key: selectedKey,
835
- service: mergeServiceWithDefaults(config.services[selectedKey], config.defaults),
860
+ service: requireServiceId(mergeServiceWithDefaults(config.services[selectedKey], config.defaults)),
836
861
  config
837
862
  };
838
863
  }
@@ -1437,6 +1462,7 @@ function printServiceConfig(service) {
1437
1462
  console.log(` ${chalk.bold("Root Dir:")} ${formatValue(service.rootDirectory || "/")}`);
1438
1463
  console.log(` ${chalk.bold("Build Command:")} ${formatValue(service.buildCommand)}`);
1439
1464
  console.log(` ${chalk.bold("Start Command:")} ${formatValue(service.startCommand)}`);
1465
+ console.log(` ${chalk.bold("Pre-Start Cmd:")} ${formatValue(service.preStartCommand)}`);
1440
1466
  console.log(` ${chalk.bold("Porta:")} ${formatValue(service.port)}`);
1441
1467
  console.log(` ${chalk.bold("Instâncias:")} ${formatValue(service.instanceCount)}`);
1442
1468
  console.log(` ${chalk.bold("CPU Limit:")} ${formatValue(service.cpuLimit)}`);
@@ -1454,6 +1480,7 @@ configGroup.command("show", {
1454
1480
  rootDirectory: z.string().nullable(),
1455
1481
  buildCommand: z.string().nullable(),
1456
1482
  startCommand: z.string().nullable(),
1483
+ preStartCommand: z.string().nullable(),
1457
1484
  port: z.number().nullable(),
1458
1485
  instanceCount: z.number().nullable(),
1459
1486
  cpuLimit: z.string().nullable(),
@@ -1482,6 +1509,7 @@ configGroup.command("show", {
1482
1509
  rootDirectory: svc.rootDirectory ?? null,
1483
1510
  buildCommand: svc.buildCommand ?? null,
1484
1511
  startCommand: svc.startCommand ?? null,
1512
+ preStartCommand: svc.preStartCommand ?? null,
1485
1513
  port: svc.port ?? null,
1486
1514
  instanceCount: svc.instanceCount ?? null,
1487
1515
  cpuLimit: svc.cpuLimit ?? null,
@@ -1496,6 +1524,7 @@ configGroup.command("set", {
1496
1524
  name: z.string().optional().describe("Nome do serviço"),
1497
1525
  build: z.string().optional().describe("Comando de build"),
1498
1526
  start: z.string().optional().describe("Comando de start"),
1527
+ preStart: z.string().optional().describe("Comando executado antes de iniciar o serviço (ex: migrations)"),
1499
1528
  port: z.string().optional().describe("Porta do serviço"),
1500
1529
  root: z.string().optional().describe("Diretório raiz"),
1501
1530
  instances: z.string().optional().describe("Número de instâncias"),
@@ -1519,6 +1548,7 @@ configGroup.command("set", {
1519
1548
  if (c.options.name) updates.name = c.options.name;
1520
1549
  if (c.options.build !== void 0) updates.buildCommand = c.options.build === "none" ? null : c.options.build;
1521
1550
  if (c.options.start !== void 0) updates.startCommand = c.options.start === "none" ? null : c.options.start;
1551
+ if (c.options.preStart !== void 0) updates.preStartCommand = c.options.preStart === "none" ? null : c.options.preStart;
1522
1552
  if (c.options.port) updates.port = parseInt(c.options.port, 10);
1523
1553
  if (c.options.root !== void 0) updates.rootDirectory = c.options.root === "/" ? null : c.options.root;
1524
1554
  if (c.options.instances) updates.instanceCount = parseInt(c.options.instances, 10);
@@ -1567,6 +1597,8 @@ configGroup.command("edit", {
1567
1597
  if (buildCmd) updates.buildCommand = buildCmd === "none" ? null : buildCmd;
1568
1598
  const startCmd = await prompt(`Start command ${chalk.dim(`(${svc.startCommand || "—"})`)}: `);
1569
1599
  if (startCmd) updates.startCommand = startCmd === "none" ? null : startCmd;
1600
+ const preStartCmd = await prompt(`Pre-start command ${chalk.dim(`(${svc.preStartCommand || "—"})`)}: `);
1601
+ if (preStartCmd) updates.preStartCommand = preStartCmd === "none" ? null : preStartCmd;
1570
1602
  const port = await prompt(`Porta ${chalk.dim(`(${svc.port})`)}: `);
1571
1603
  if (port) updates.port = parseInt(port, 10);
1572
1604
  const rootDir = await prompt(`Diretório raiz ${chalk.dim(`(${svc.rootDirectory || "/"})`)}: `);
@@ -1598,6 +1630,7 @@ configGroup.command("reset", {
1598
1630
  options: z.object({
1599
1631
  build: z.boolean().default(false).describe("Resetar comando de build"),
1600
1632
  start: z.boolean().default(false).describe("Resetar comando de start"),
1633
+ preStart: z.boolean().default(false).describe("Resetar comando de pre-start"),
1601
1634
  all: z.boolean().default(false).describe("Resetar todas as configurações opcionais"),
1602
1635
  service: z.string().optional().describe("Serviço alvo (chave ou nome)")
1603
1636
  }),
@@ -1608,13 +1641,15 @@ configGroup.command("reset", {
1608
1641
  if (c.options.all) {
1609
1642
  updates.buildCommand = null;
1610
1643
  updates.startCommand = null;
1644
+ updates.preStartCommand = null;
1611
1645
  updates.rootDirectory = null;
1612
1646
  } else {
1613
1647
  if (c.options.build) updates.buildCommand = null;
1614
1648
  if (c.options.start) updates.startCommand = null;
1649
+ if (c.options.preStart) updates.preStartCommand = null;
1615
1650
  }
1616
1651
  if (Object.keys(updates).length === 0) {
1617
- warn("Especifique o que resetar: --build, --start, ou --all");
1652
+ warn("Especifique o que resetar: --build, --start, --pre-start, ou --all");
1618
1653
  return;
1619
1654
  }
1620
1655
  await withSpinner({
@@ -1847,7 +1882,10 @@ dbGroup.command("create", {
1847
1882
  name: z.string().optional().describe("Nome do banco de dados"),
1848
1883
  engine: z.string().optional().describe("Engine (postgresql, mysql, redis)"),
1849
1884
  engineVersion: z.string().optional().describe("Versão do engine"),
1850
- storage: z.string().optional().describe("Armazenamento (ex: 10Gi, 20Gi)")
1885
+ storage: z.string().optional().describe("Armazenamento (ex: 10Gi, 20Gi)"),
1886
+ cpu: z.string().optional().describe("Limite de CPU (ex: 500m, 1)"),
1887
+ memory: z.string().optional().describe("Limite de memória (ex: 512Mi, 1Gi)"),
1888
+ pooler: z.boolean().optional().describe("Habilitar PgBouncer (apenas PostgreSQL)")
1851
1889
  }),
1852
1890
  async run(c) {
1853
1891
  const projectId = getProjectId$1();
@@ -1896,7 +1934,10 @@ dbGroup.command("create", {
1896
1934
  name,
1897
1935
  engine: validEngine,
1898
1936
  engineVersion: version,
1899
- storage
1937
+ storage,
1938
+ cpuLimit: c.options.cpu,
1939
+ memoryLimit: c.options.memory,
1940
+ poolerEnabled: c.options.pooler
1900
1941
  })
1901
1942
  });
1902
1943
  success(`Banco de dados ${chalk.bold(db.name)} criado! Provisionando...`);
@@ -1908,7 +1949,12 @@ dbGroup.command("create", {
1908
1949
  id: db.id,
1909
1950
  engine: validEngine,
1910
1951
  version: version ?? void 0,
1911
- storage: storage ?? void 0
1952
+ storage: storage ?? void 0,
1953
+ ...c.options.cpu || c.options.memory ? { resources: {
1954
+ ...c.options.cpu && { cpu: c.options.cpu },
1955
+ ...c.options.memory && { memory: c.options.memory }
1956
+ } } : {},
1957
+ ...c.options.pooler ? { pooler: { enabled: true } } : {}
1912
1958
  };
1913
1959
  config.databases = updatedDatabases;
1914
1960
  config.updated = (/* @__PURE__ */ new Date()).toISOString();
@@ -2219,7 +2265,7 @@ function registerLink(cli$1) {
2219
2265
  },
2220
2266
  services: services.map(([key, service]) => ({
2221
2267
  key,
2222
- id: service.id,
2268
+ id: service.id ?? "",
2223
2269
  name: service.name,
2224
2270
  type: service.type
2225
2271
  }))
@@ -2256,13 +2302,16 @@ function detectFramework(pkgJsonStr, pm) {
2256
2302
  ...pkg.dependencies,
2257
2303
  ...pkg.devDependencies
2258
2304
  };
2305
+ const scripts = pkg.scripts ?? {};
2259
2306
  const hasReact = !!allDeps["react"];
2307
+ const buildCmd = scripts.build ? pmRun(pm, "build") : null;
2308
+ const startCmd = scripts.start ? pmRun(pm, "start") : null;
2260
2309
  if (allDeps["next"]) return {
2261
2310
  name: "nextjs",
2262
2311
  label: "Next.js",
2263
2312
  type: "WEB",
2264
- buildCommand: pmRun(pm, "build"),
2265
- startCommand: pmRun(pm, "start"),
2313
+ buildCommand: buildCmd ?? pmRun(pm, "build"),
2314
+ startCommand: startCmd ?? pmRun(pm, "start"),
2266
2315
  outputDir: ".next",
2267
2316
  port: 3e3
2268
2317
  };
@@ -2270,8 +2319,8 @@ function detectFramework(pkgJsonStr, pm) {
2270
2319
  name: "nuxt",
2271
2320
  label: "Nuxt",
2272
2321
  type: "WEB",
2273
- buildCommand: pmRun(pm, "build"),
2274
- startCommand: pmRun(pm, "start"),
2322
+ buildCommand: buildCmd ?? pmRun(pm, "build"),
2323
+ startCommand: startCmd ?? pmRun(pm, "start"),
2275
2324
  outputDir: ".output",
2276
2325
  port: 3e3
2277
2326
  };
@@ -2279,8 +2328,8 @@ function detectFramework(pkgJsonStr, pm) {
2279
2328
  name: "remix",
2280
2329
  label: "Remix",
2281
2330
  type: "WEB",
2282
- buildCommand: pmRun(pm, "build"),
2283
- startCommand: pmRun(pm, "start"),
2331
+ buildCommand: buildCmd ?? pmRun(pm, "build"),
2332
+ startCommand: startCmd ?? pmRun(pm, "start"),
2284
2333
  outputDir: "build",
2285
2334
  port: 3e3
2286
2335
  };
@@ -2288,7 +2337,7 @@ function detectFramework(pkgJsonStr, pm) {
2288
2337
  name: "astro",
2289
2338
  label: "Astro",
2290
2339
  type: "STATIC",
2291
- buildCommand: pmRun(pm, "build"),
2340
+ buildCommand: buildCmd ?? pmRun(pm, "build"),
2292
2341
  startCommand: null,
2293
2342
  outputDir: "dist",
2294
2343
  port: 3e3
@@ -2297,8 +2346,8 @@ function detectFramework(pkgJsonStr, pm) {
2297
2346
  name: "sveltekit",
2298
2347
  label: "SvelteKit",
2299
2348
  type: "WEB",
2300
- buildCommand: pmRun(pm, "build"),
2301
- startCommand: pmRun(pm, "preview"),
2349
+ buildCommand: buildCmd ?? pmRun(pm, "build"),
2350
+ startCommand: scripts.preview ? pmRun(pm, "preview") : startCmd ?? pmRun(pm, "start"),
2302
2351
  outputDir: "build",
2303
2352
  port: 3e3
2304
2353
  };
@@ -2306,7 +2355,7 @@ function detectFramework(pkgJsonStr, pm) {
2306
2355
  name: "gatsby",
2307
2356
  label: "Gatsby",
2308
2357
  type: "STATIC",
2309
- buildCommand: pmRun(pm, "build"),
2358
+ buildCommand: buildCmd ?? pmRun(pm, "build"),
2310
2359
  startCommand: null,
2311
2360
  outputDir: "public",
2312
2361
  port: 3e3
@@ -2315,8 +2364,8 @@ function detectFramework(pkgJsonStr, pm) {
2315
2364
  name: "angular",
2316
2365
  label: "Angular",
2317
2366
  type: "WEB",
2318
- buildCommand: pmRun(pm, "build"),
2319
- startCommand: pmRun(pm, "start"),
2367
+ buildCommand: buildCmd ?? pmRun(pm, "build"),
2368
+ startCommand: startCmd ?? pmRun(pm, "start"),
2320
2369
  outputDir: "dist",
2321
2370
  port: 4200
2322
2371
  };
@@ -2324,8 +2373,8 @@ function detectFramework(pkgJsonStr, pm) {
2324
2373
  name: "hono",
2325
2374
  label: "Hono",
2326
2375
  type: "WEB",
2327
- buildCommand: pmRun(pm, "build"),
2328
- startCommand: pmRun(pm, "start"),
2376
+ buildCommand: buildCmd,
2377
+ startCommand: startCmd,
2329
2378
  outputDir: null,
2330
2379
  port: 3e3
2331
2380
  };
@@ -2333,8 +2382,8 @@ function detectFramework(pkgJsonStr, pm) {
2333
2382
  name: "express",
2334
2383
  label: "Express",
2335
2384
  type: "WEB",
2336
- buildCommand: pmRun(pm, "build"),
2337
- startCommand: pmRun(pm, "start"),
2385
+ buildCommand: buildCmd,
2386
+ startCommand: startCmd,
2338
2387
  outputDir: null,
2339
2388
  port: 3e3
2340
2389
  };
@@ -2342,8 +2391,8 @@ function detectFramework(pkgJsonStr, pm) {
2342
2391
  name: "fastify",
2343
2392
  label: "Fastify",
2344
2393
  type: "WEB",
2345
- buildCommand: pmRun(pm, "build"),
2346
- startCommand: pmRun(pm, "start"),
2394
+ buildCommand: buildCmd,
2395
+ startCommand: startCmd,
2347
2396
  outputDir: null,
2348
2397
  port: 3e3
2349
2398
  };
@@ -2351,8 +2400,8 @@ function detectFramework(pkgJsonStr, pm) {
2351
2400
  name: "nestjs",
2352
2401
  label: "NestJS",
2353
2402
  type: "WEB",
2354
- buildCommand: pmRun(pm, "build"),
2355
- startCommand: pmRun(pm, "start:prod"),
2403
+ buildCommand: buildCmd ?? pmRun(pm, "build"),
2404
+ startCommand: scripts["start:prod"] ? pmRun(pm, "start:prod") : startCmd ?? pmRun(pm, "start:prod"),
2356
2405
  outputDir: "dist",
2357
2406
  port: 3e3
2358
2407
  };
@@ -2360,7 +2409,7 @@ function detectFramework(pkgJsonStr, pm) {
2360
2409
  name: hasReact ? "vite-react" : "vite",
2361
2410
  label: hasReact ? "Vite + React" : "Vite",
2362
2411
  type: "STATIC",
2363
- buildCommand: pmRun(pm, "build"),
2412
+ buildCommand: buildCmd ?? pmRun(pm, "build"),
2364
2413
  startCommand: null,
2365
2414
  outputDir: "dist",
2366
2415
  port: 3e3
@@ -2369,17 +2418,17 @@ function detectFramework(pkgJsonStr, pm) {
2369
2418
  name: "cra",
2370
2419
  label: "Create React App",
2371
2420
  type: "STATIC",
2372
- buildCommand: pmRun(pm, "build"),
2421
+ buildCommand: buildCmd ?? pmRun(pm, "build"),
2373
2422
  startCommand: null,
2374
2423
  outputDir: "build",
2375
2424
  port: 3e3
2376
2425
  };
2377
- if (pkg.scripts?.build || pkg.scripts?.start) return {
2426
+ if (buildCmd || startCmd) return {
2378
2427
  name: "node",
2379
2428
  label: "Node.js",
2380
2429
  type: "WEB",
2381
- buildCommand: pkg.scripts?.build ? pmRun(pm, "build") : "",
2382
- startCommand: pkg.scripts?.start ? pmRun(pm, "start") : "node index.js",
2430
+ buildCommand: buildCmd ?? "",
2431
+ startCommand: startCmd,
2383
2432
  outputDir: "dist",
2384
2433
  port: 3e3
2385
2434
  };
@@ -2453,7 +2502,7 @@ function analyzeRepo(files) {
2453
2502
  name: appName,
2454
2503
  path: appPath,
2455
2504
  framework: appFramework,
2456
- usesNodeFs: sourceEntries.some(([filePath$1, fileContent]) => filePath$1.startsWith(`${appPath}/`) && usesNodeFs(fileContent))
2505
+ usesNodeFs: sourceEntries.some(([srcPath, fileContent]) => srcPath.startsWith(`${appPath}/`) && usesNodeFs(fileContent))
2457
2506
  });
2458
2507
  }
2459
2508
  return {
@@ -2610,10 +2659,14 @@ async function withRetry(fn, maxRetries = 3) {
2610
2659
  const rateLimit = isRateLimitError(error);
2611
2660
  if (rateLimit) {
2612
2661
  const waitMs = Math.min(rateLimit.retryAfterMs, 3e4);
2613
- await new Promise((r) => setTimeout(r, waitMs));
2662
+ await new Promise((r) => {
2663
+ setTimeout(r, waitMs);
2664
+ });
2614
2665
  } else {
2615
2666
  const delay = Math.min(1e3 * Math.pow(2, attempt), 1e4);
2616
- await new Promise((r) => setTimeout(r, delay));
2667
+ await new Promise((r) => {
2668
+ setTimeout(r, delay);
2669
+ });
2617
2670
  }
2618
2671
  }
2619
2672
  throw new Error("Max retries exceeded");
@@ -2623,7 +2676,7 @@ async function withRetry(fn, maxRetries = 3) {
2623
2676
  //#region src/lib/deploy-constants.ts
2624
2677
  const statusLabels = {
2625
2678
  QUEUED: "Na fila",
2626
- BUILDING: "Construindo",
2679
+ BUILDING: "Compilando",
2627
2680
  BUILD_FAILED: "Falha na construção",
2628
2681
  DEPLOYING: "Implantando",
2629
2682
  LIVE: "Ativo",
@@ -2724,8 +2777,7 @@ function renderProgress(progressMap, prevLineCount) {
2724
2777
  if (nonEmptyLines.length > 0) {
2725
2778
  const tail = nonEmptyLines.slice(-3);
2726
2779
  for (const line of tail) {
2727
- const truncated = line.substring(0, 80) + (line.length > 80 ? "..." : "");
2728
- process.stdout.write(` ${chalk.dim(truncated)}\n`);
2780
+ process.stdout.write(` ${chalk.dim(line)}\n`);
2729
2781
  lineCount++;
2730
2782
  }
2731
2783
  } else if (progress.status === "BUILDING") {
@@ -2811,7 +2863,9 @@ async function deployServicesInParallel(services) {
2811
2863
  if (isTTY) lineCount = renderProgress(progressMap, lineCount);
2812
2864
  const streamPromises = activeDeployments.map(async ({ service, deploymentId }) => {
2813
2865
  try {
2814
- await new Promise((resolve$1) => setTimeout(resolve$1, 1e3));
2866
+ await new Promise((resolve$1) => {
2867
+ setTimeout(resolve$1, 1e3);
2868
+ });
2815
2869
  const stream = await client.logs.streamBuildLogs({ deploymentId });
2816
2870
  for await (const event of stream) {
2817
2871
  const progress = progressMap.get(service.serviceId);
@@ -2960,6 +3014,310 @@ function getFailureHints(status) {
2960
3014
  default: return ["Execute 'veloz logs -f' para mais detalhes."];
2961
3015
  }
2962
3016
  }
3017
+ /** Raw BuildKit line: `#N content` */
3018
+ const BUILDKIT_PREFIX_RE = /^#(\d+)\s+(.*)/;
3019
+ /** Docker build step: `[stage step/total] COMMAND` */
3020
+ const DOCKER_STEP_RE = /^\[(\S+)\s+(\d+)\/(\d+)\]\s+(.+)$/;
3021
+ /** Platform timestamp: `[2026-03-23T02:37:13.795Z] message` */
3022
+ const TIMESTAMP_RE = /^\[(\d{4}-\d{2}-\d{2}T[\d:.]+Z)\]\s+(.+)$/;
3023
+ /** DONE marker: `DONE 4.2s` */
3024
+ const DONE_RE = /^DONE\s+([\d.]+s?)$/;
3025
+ /** Timing prefix: `0.543 actual content` */
3026
+ const TIMING_RE = /^(\d+\.\d+)\s+(.*)/;
3027
+ /** Infrastructure / deploy orchestration messages to hide from user output */
3028
+ const HIDDEN_MESSAGES = [
3029
+ /^Ensuring namespace\b/i,
3030
+ /^Updating deployment\b/i,
3031
+ /^Syncing ingress\b/i,
3032
+ /^Waiting for rollout\b/i,
3033
+ /^Deploy complete\b/i,
3034
+ /^→\s+https?:\/\//,
3035
+ /\bnamespace\b.*\bsvc\.cluster\.local\b/i,
3036
+ /\bpod\b/i,
3037
+ /\bkubernetes\b/i,
3038
+ /\bk8s\b/i,
3039
+ /\brollout\b/i
3040
+ ];
3041
+ function isHiddenMessage(text) {
3042
+ return HIDDEN_MESSAGES.some((p) => p.test(text));
3043
+ }
3044
+ /**
3045
+ * Try to extract a human-readable message from a structured JSON log line.
3046
+ * Returns null if the line is not JSON or has no message.
3047
+ */
3048
+ function parseJsonLog(text) {
3049
+ if (!text.startsWith("{")) return null;
3050
+ try {
3051
+ const parsed = JSON.parse(text);
3052
+ if (typeof parsed === "object" && parsed !== null && typeof parsed.msg === "string") return parsed.msg;
3053
+ } catch {}
3054
+ return null;
3055
+ }
3056
+ /** Clean a display line — parse JSON, filter infra */
3057
+ function cleanDisplayLine(text) {
3058
+ if (isHiddenMessage(text)) return null;
3059
+ const jsonMsg = parseJsonLog(text);
3060
+ if (jsonMsg !== null) {
3061
+ if (isHiddenMessage(jsonMsg)) return null;
3062
+ return jsonMsg;
3063
+ }
3064
+ return text;
3065
+ }
3066
+ function parseBuildLine(raw) {
3067
+ const trimmed = raw.trim();
3068
+ const bkMatch = BUILDKIT_PREFIX_RE.exec(trimmed);
3069
+ if (bkMatch) {
3070
+ const stepNum = parseInt(bkMatch[1], 10);
3071
+ const content = bkMatch[2];
3072
+ if (content.trim() === "CACHED") return {
3073
+ kind: "cached",
3074
+ stepNum
3075
+ };
3076
+ const doneMatch = DONE_RE.exec(content);
3077
+ if (doneMatch) return {
3078
+ kind: "done",
3079
+ stepNum,
3080
+ duration: doneMatch[1]
3081
+ };
3082
+ const stepMatch = DOCKER_STEP_RE.exec(content);
3083
+ if (stepMatch) return {
3084
+ kind: "step",
3085
+ stepNum,
3086
+ stage: stepMatch[1],
3087
+ step: parseInt(stepMatch[2], 10),
3088
+ total: parseInt(stepMatch[3], 10),
3089
+ command: stepMatch[4]
3090
+ };
3091
+ const timingMatch = TIMING_RE.exec(content);
3092
+ if (timingMatch) {
3093
+ const text = timingMatch[2];
3094
+ if (text.trim()) return {
3095
+ kind: "output",
3096
+ stepNum,
3097
+ text
3098
+ };
3099
+ return {
3100
+ kind: "other",
3101
+ text: ""
3102
+ };
3103
+ }
3104
+ if (content.trim()) return {
3105
+ kind: "output",
3106
+ stepNum,
3107
+ text: content
3108
+ };
3109
+ return {
3110
+ kind: "other",
3111
+ text: ""
3112
+ };
3113
+ }
3114
+ const tsMatch = TIMESTAMP_RE.exec(trimmed);
3115
+ if (tsMatch) return {
3116
+ kind: "platform",
3117
+ message: tsMatch[2]
3118
+ };
3119
+ return {
3120
+ kind: "other",
3121
+ text: trimmed
3122
+ };
3123
+ }
3124
+ const BAR_WIDTH = 20;
3125
+ const BRAND = chalk.rgb(255, 77, 0);
3126
+ function renderProgressBar(filled, total, allCached, allDone) {
3127
+ const ratio = total > 0 ? filled / total : 0;
3128
+ const filledChars = Math.round(ratio * BAR_WIDTH);
3129
+ const emptyChars = BAR_WIDTH - filledChars;
3130
+ const counter = `${filled}/${total}`;
3131
+ if (allCached) return `${BRAND("━".repeat(BAR_WIDTH))} ${BRAND(`${counter} ◆ cached`)}`;
3132
+ if (allDone) return `${chalk.green("━".repeat(BAR_WIDTH))} ${chalk.green(`${counter} ✓`)}`;
3133
+ return chalk.cyan("━".repeat(filledChars)) + chalk.dim("─".repeat(emptyChars)) + ` ${chalk.dim(counter)}`;
3134
+ }
3135
+ const SPINNER_FRAMES = [
3136
+ "⠋",
3137
+ "⠙",
3138
+ "⠹",
3139
+ "⠸",
3140
+ "⠼",
3141
+ "⠴",
3142
+ "⠦",
3143
+ "⠧",
3144
+ "⠇",
3145
+ "⠏"
3146
+ ];
3147
+ /**
3148
+ * Dashboard-style renderer for compact TTY mode.
3149
+ * Redraws the entire build progress block on each update.
3150
+ * Includes an integrated spinner that animates via setInterval.
3151
+ */
3152
+ var BuildProgressRenderer = class {
3153
+ stages = /* @__PURE__ */ new Map();
3154
+ stageOrder = [];
3155
+ platformMessages = [];
3156
+ renderLineCount = 0;
3157
+ phase = "waiting";
3158
+ runtimeHeaderPrinted = false;
3159
+ serviceName;
3160
+ spinnerFrame = 0;
3161
+ spinnerInterval = null;
3162
+ spinnerText = "Aguardando início do build...";
3163
+ /** External ora spinner reference — used to pause/resume during runtime log output */
3164
+ externalSpinner = null;
3165
+ constructor(serviceName) {
3166
+ this.serviceName = serviceName;
3167
+ this.startSpinner();
3168
+ }
3169
+ setExternalSpinner(spinner$1) {
3170
+ this.externalSpinner = spinner$1;
3171
+ }
3172
+ startSpinner() {
3173
+ if (this.spinnerInterval) return;
3174
+ this.spinnerInterval = setInterval(() => {
3175
+ this.spinnerFrame = (this.spinnerFrame + 1) % SPINNER_FRAMES.length;
3176
+ this.render();
3177
+ }, 80);
3178
+ }
3179
+ stopSpinner() {
3180
+ if (this.spinnerInterval) {
3181
+ clearInterval(this.spinnerInterval);
3182
+ this.spinnerInterval = null;
3183
+ }
3184
+ }
3185
+ setBuilding() {
3186
+ this.phase = "building";
3187
+ this.spinnerText = "Compilando...";
3188
+ }
3189
+ switchToRuntime() {
3190
+ if (this.phase === "runtime") return;
3191
+ for (const stage of this.stages.values()) if (stage.steps.size > 0 && stage.steps.size < stage.total) stage.total = stage.steps.size;
3192
+ this.stopSpinner();
3193
+ this.render();
3194
+ this.renderLineCount = 0;
3195
+ this.phase = "runtime";
3196
+ }
3197
+ processLine(raw) {
3198
+ const trimmed = raw.trim();
3199
+ if (!trimmed) return;
3200
+ if (this.phase === "runtime") {
3201
+ this.printRuntimeLine(trimmed);
3202
+ return;
3203
+ }
3204
+ if (this.phase === "waiting") {
3205
+ this.phase = "building";
3206
+ this.spinnerText = "Compilando...";
3207
+ }
3208
+ const parsed = parseBuildLine(trimmed);
3209
+ switch (parsed.kind) {
3210
+ case "step": {
3211
+ let stage = this.stages.get(parsed.stage);
3212
+ if (!stage) {
3213
+ stage = {
3214
+ name: parsed.stage,
3215
+ total: parsed.total,
3216
+ steps: /* @__PURE__ */ new Map(),
3217
+ stepNumMap: /* @__PURE__ */ new Map(),
3218
+ cachedStepNums: /* @__PURE__ */ new Set(),
3219
+ doneStepNums: /* @__PURE__ */ new Set()
3220
+ };
3221
+ this.stages.set(parsed.stage, stage);
3222
+ this.stageOrder.push(parsed.stage);
3223
+ }
3224
+ stage.steps.set(parsed.step, parsed.command);
3225
+ stage.stepNumMap.set(parsed.stepNum, parsed.step);
3226
+ stage.total = Math.max(stage.total, parsed.total);
3227
+ break;
3228
+ }
3229
+ case "cached":
3230
+ for (const stage of this.stages.values()) if (stage.stepNumMap.has(parsed.stepNum)) {
3231
+ stage.cachedStepNums.add(parsed.stepNum);
3232
+ break;
3233
+ }
3234
+ break;
3235
+ case "done":
3236
+ for (const stage of this.stages.values()) if (stage.stepNumMap.has(parsed.stepNum)) {
3237
+ stage.doneStepNums.add(parsed.stepNum);
3238
+ break;
3239
+ }
3240
+ break;
3241
+ case "platform": {
3242
+ const cleaned = cleanDisplayLine(parsed.message);
3243
+ if (cleaned) this.platformMessages.push(cleaned);
3244
+ break;
3245
+ }
3246
+ case "output":
3247
+ case "other": break;
3248
+ }
3249
+ this.render();
3250
+ }
3251
+ printRuntimeLine(line) {
3252
+ const parsed = parseBuildLine(line);
3253
+ let text = null;
3254
+ if (parsed.kind === "platform") text = parsed.message;
3255
+ else if (parsed.kind === "other" && parsed.text) text = parsed.text;
3256
+ else if (parsed.kind === "output") text = parsed.text;
3257
+ if (!text) return;
3258
+ if (isHiddenMessage(text)) return;
3259
+ if (this.externalSpinner) this.externalSpinner.clear();
3260
+ if (!this.runtimeHeaderPrinted) {
3261
+ this.runtimeHeaderPrinted = true;
3262
+ console.log(chalk.cyan.bold(`\n RUNTIME`));
3263
+ }
3264
+ console.log(` ${text}`);
3265
+ if (this.externalSpinner) this.externalSpinner.render();
3266
+ }
3267
+ isStageComplete(stage) {
3268
+ return stage.steps.size >= stage.total;
3269
+ }
3270
+ render() {
3271
+ if (this.renderLineCount > 0) process.stdout.write(`\x1b[${this.renderLineCount}A\x1b[J`);
3272
+ let lines = 0;
3273
+ const label = this.serviceName ? `BUILD ${chalk.dim(`(${this.serviceName})`)}` : "BUILD";
3274
+ process.stdout.write(`${chalk.cyan.bold(` ${label}`)}\n`);
3275
+ lines++;
3276
+ for (const msg of this.platformMessages) {
3277
+ process.stdout.write(` ${msg}\n`);
3278
+ lines++;
3279
+ }
3280
+ if (this.stageOrder.length > 0) {
3281
+ process.stdout.write("\n");
3282
+ lines++;
3283
+ }
3284
+ const maxNameLen = Math.max(...this.stageOrder.map((n) => n.length), 4);
3285
+ for (let i = 0; i < this.stageOrder.length; i++) {
3286
+ const stageName = this.stageOrder[i];
3287
+ const stage = this.stages.get(stageName);
3288
+ const complete = this.isStageComplete(stage);
3289
+ const allCached = complete && stage.cachedStepNums.size === stage.steps.size;
3290
+ const allDone = complete && !allCached;
3291
+ const bar = renderProgressBar(stage.steps.size, stage.total, allCached, allDone);
3292
+ const paddedName = chalk.bold(stageName.padEnd(maxNameLen));
3293
+ process.stdout.write(` ${paddedName} ${bar}\n`);
3294
+ lines++;
3295
+ const sortedSteps = [...stage.steps.entries()].sort((a, b) => a[0] - b[0]);
3296
+ for (const [stepNum, command] of sortedSteps) {
3297
+ let stepStatus = "";
3298
+ for (const [bkNum, dockerStep] of stage.stepNumMap.entries()) if (dockerStep === stepNum) {
3299
+ if (stage.cachedStepNums.has(bkNum)) stepStatus = ` ${BRAND("◆")}`;
3300
+ else if (stage.doneStepNums.has(bkNum)) stepStatus = ` ${chalk.green("✓")}`;
3301
+ break;
3302
+ }
3303
+ process.stdout.write(` ${command}${stepStatus}\n`);
3304
+ lines++;
3305
+ }
3306
+ if (i < this.stageOrder.length - 1) {
3307
+ process.stdout.write("\n");
3308
+ lines++;
3309
+ }
3310
+ }
3311
+ if (this.spinnerInterval) {
3312
+ if (!(this.stageOrder.length > 0 && this.stageOrder.every((name) => this.isStageComplete(this.stages.get(name))))) {
3313
+ const frame = SPINNER_FRAMES[this.spinnerFrame];
3314
+ process.stdout.write(`\n ${chalk.cyan(frame)} ${this.spinnerText}\n`);
3315
+ lines += 2;
3316
+ }
3317
+ }
3318
+ this.renderLineCount = lines;
3319
+ }
3320
+ };
2963
3321
  async function streamDeploymentLogs(deploymentId, serviceId, serviceName) {
2964
3322
  const client = await getClient();
2965
3323
  const isVerbose = process.env.VELOZ_VERBOSE === "true";
@@ -2968,12 +3326,10 @@ async function streamDeploymentLogs(deploymentId, serviceId, serviceName) {
2968
3326
  const isGHA = !mcp && process.env.GITHUB_ACTIONS === "true";
2969
3327
  const allLogLines = [];
2970
3328
  let buildSpinner = null;
3329
+ let renderer = null;
2971
3330
  if (mcp) log(serviceName ? `[deploy] Build: ${serviceName}` : "[deploy] Build iniciando...");
2972
3331
  else if (isGHA) startGroup(serviceName ? `Build: ${serviceName}` : "Build");
2973
- else if (isTTY && !isVerbose) buildSpinner = ora({
2974
- text: "Aguardando início do build...",
2975
- color: "cyan"
2976
- }).start();
3332
+ else if (isTTY && !isVerbose) renderer = new BuildProgressRenderer(serviceName);
2977
3333
  else if (isTTY) {
2978
3334
  const header = serviceName ? `Build: ${chalk.bold(serviceName)}` : "Build";
2979
3335
  console.log(chalk.cyan(`\n${header}`));
@@ -2989,19 +3345,24 @@ async function streamDeploymentLogs(deploymentId, serviceId, serviceName) {
2989
3345
  const label = statusLabels[event.content] ?? event.content;
2990
3346
  finalStatus = event.content;
2991
3347
  if (mcp) log(`[deploy] Status: ${label}`);
2992
- else if (isTTY && !isVerbose) {
2993
- if (buildSpinner) {
2994
- if (event.content === "BUILDING") buildSpinner.text = "Construindo...";
2995
- else if (event.content === "DEPLOYING") {
2996
- buildSpinner.succeed("Build concluído");
2997
- buildSpinner = ora({
2998
- text: "Publicando...",
2999
- color: "cyan"
3000
- }).start();
3001
- } else if (event.content === "LIVE") {
3348
+ else if (renderer) {
3349
+ if (event.content === "BUILDING") renderer.setBuilding();
3350
+ else if (event.content === "DEPLOYING") {
3351
+ renderer.switchToRuntime();
3352
+ buildSpinner = ora({
3353
+ text: "Publicando...",
3354
+ color: "cyan"
3355
+ }).start();
3356
+ renderer.setExternalSpinner(buildSpinner);
3357
+ } else if (event.content === "LIVE") {
3358
+ if (buildSpinner) {
3359
+ renderer.setExternalSpinner(null);
3002
3360
  buildSpinner.succeed("Publicado");
3003
3361
  buildSpinner = null;
3004
- } else if (TERMINAL_STATUSES.has(event.content) && event.content !== "LIVE") {
3362
+ }
3363
+ } else if (TERMINAL_STATUSES.has(event.content) && event.content !== "LIVE") {
3364
+ if (buildSpinner) {
3365
+ renderer.setExternalSpinner(null);
3005
3366
  buildSpinner.fail(label);
3006
3367
  buildSpinner = null;
3007
3368
  }
@@ -3015,16 +3376,11 @@ async function streamDeploymentLogs(deploymentId, serviceId, serviceName) {
3015
3376
  allLogLines.push(...lines);
3016
3377
  if (mcp) {
3017
3378
  for (const line of lines) if (line.trim()) log(`[build] ${line.trim()}`);
3018
- } else if (isTTY && !isVerbose) for (const line of lines) {
3019
- const trimmed = line.trim();
3020
- if (trimmed) {
3021
- const display = trimmed.length > 60 ? trimmed.substring(0, 57) + "..." : trimmed;
3022
- if (buildSpinner) buildSpinner.text = display;
3023
- }
3024
- }
3379
+ } else if (renderer) for (const line of lines) renderer.processLine(line);
3025
3380
  else for (const line of lines) if (line.trim()) process.stdout.write(` ${line}\n`);
3026
3381
  }
3027
3382
  } catch {
3383
+ if (renderer) renderer.stopSpinner();
3028
3384
  if (buildSpinner) {
3029
3385
  buildSpinner.stop();
3030
3386
  buildSpinner = null;
@@ -3038,6 +3394,7 @@ async function streamDeploymentLogs(deploymentId, serviceId, serviceName) {
3038
3394
  } catch {}
3039
3395
  }
3040
3396
  if (isGHA) endGroup();
3397
+ if (renderer) renderer.stopSpinner();
3041
3398
  const urls = finalStatus === "LIVE" ? await fetchDeployUrls(client, serviceId) : [];
3042
3399
  if (finalStatus === "LIVE") {
3043
3400
  if (buildSpinner) {
@@ -3056,7 +3413,7 @@ async function streamDeploymentLogs(deploymentId, serviceId, serviceName) {
3056
3413
  if (mcp) {
3057
3414
  log(`✗ Deploy finalizou: ${label}`);
3058
3415
  for (const hint of hints) log(` → ${hint}`);
3059
- } else if (isTTY && allLogLines.length > 0) {
3416
+ } else if (isTTY && !renderer && allLogLines.length > 0) {
3060
3417
  console.log();
3061
3418
  console.log(chalk.red(` ${"─".repeat(50)}`));
3062
3419
  console.log(chalk.red.bold(" Logs de build:"));
@@ -3150,7 +3507,7 @@ const LOGO_LINES = [
3150
3507
  ];
3151
3508
  const BRAND_COLOR = "#FF4D00";
3152
3509
  function getVersion() {
3153
- return "0.0.0-beta.15";
3510
+ return "0.0.0-beta.17";
3154
3511
  }
3155
3512
  function printBanner(subtitle) {
3156
3513
  const version = getVersion();
@@ -3178,22 +3535,29 @@ function resolveServiceConf(velozConfig, serviceId) {
3178
3535
  if (!velozConfig) return void 0;
3179
3536
  for (const [, conf] of Object.entries(velozConfig.services)) if (conf.id === serviceId) {
3180
3537
  const merged = mergeServiceWithDefaults(conf, velozConfig.defaults);
3538
+ const build = merged.build;
3539
+ const isDockerfile = build?.method === "dockerfile";
3181
3540
  return {
3182
3541
  type: merged.type?.toUpperCase(),
3183
3542
  branch: merged.branch,
3184
- buildCommand: merged.build?.command ?? void 0,
3543
+ buildCommand: build?.command ?? void 0,
3185
3544
  startCommand: merged.runtime?.command ?? void 0,
3545
+ preStartCommand: merged.runtime?.preStartCommand ?? void 0,
3186
3546
  port: merged.runtime?.port ?? void 0,
3187
3547
  rootDirectory: merged.root,
3548
+ docker: isDockerfile ? {
3549
+ dockerfile: build.dockerfile ?? "Dockerfile",
3550
+ context: build.context ?? merged.root ?? "."
3551
+ } : void 0,
3188
3552
  instanceCount: merged.resources?.instances ?? void 0,
3189
3553
  cpuLimit: merged.resources?.cpu ?? void 0,
3190
3554
  memoryLimit: merged.resources?.memory ?? void 0,
3191
3555
  healthCheckPath: merged.runtime?.healthCheck?.path ?? null,
3192
- aptPackages: merged.build?.aptPackages ?? void 0,
3193
- nodeVersion: merged.build?.nodeVersion ?? void 0,
3194
- nixpkgsArchive: merged.build?.nixpkgsArchive ?? void 0,
3195
- packageManager: merged.build?.packageManager,
3196
- installCommand: merged.build?.installCommand ?? void 0,
3556
+ aptPackages: build?.aptPackages ?? void 0,
3557
+ nodeVersion: build?.nodeVersion ?? void 0,
3558
+ nixpkgsArchive: build?.nixpkgsArchive ?? void 0,
3559
+ packageManager: build?.packageManager,
3560
+ installCommand: build?.installCommand ?? void 0,
3197
3561
  volumes: merged.volumes ?? void 0
3198
3562
  };
3199
3563
  }
@@ -3622,7 +3986,7 @@ async function autoUpdate() {
3622
3986
  if (process.env.VELOZ_MCP === "true") return;
3623
3987
  const pm = detectPackageManager();
3624
3988
  if (!pm) return;
3625
- const currentVersion = "0.0.0-beta.15";
3989
+ const currentVersion = "0.0.0-beta.17";
3626
3990
  const latestVersion = await fetchLatestVersion();
3627
3991
  if (!latestVersion || latestVersion === currentVersion) return;
3628
3992
  const installCmd = getInstallCommand(pm, latestVersion);
@@ -3698,7 +4062,13 @@ async function provisionDatabases(config, opts) {
3698
4062
  name: key,
3699
4063
  engine: dbConfig.engine,
3700
4064
  engineVersion: dbConfig.version,
3701
- storage: dbConfig.storage
4065
+ storage: dbConfig.storage,
4066
+ cpuLimit: dbConfig.resources?.cpu,
4067
+ memoryLimit: dbConfig.resources?.memory,
4068
+ poolerEnabled: dbConfig.pooler?.enabled,
4069
+ poolerPoolMode: dbConfig.pooler?.poolMode,
4070
+ poolerDefaultPoolSize: dbConfig.pooler?.defaultPoolSize,
4071
+ poolerMaxClientConn: dbConfig.pooler?.maxClientConn
3702
4072
  })
3703
4073
  });
3704
4074
  success(`Banco de dados "${key}" criado (provisionando...).`);
@@ -3733,9 +4103,13 @@ function getDatabaseUrlHints(config) {
3733
4103
  const databases = config.databases ?? {};
3734
4104
  const entries = Object.entries(databases);
3735
4105
  if (entries.length === 0) return [];
3736
- return entries.filter(([_, db]) => db.engine === "postgresql" || db.engine === "mysql").map(([key]) => {
3737
- return `${`${key.toUpperCase().replace(/-/g, "_")}_DATABASE_URL`} será injetado automaticamente de "${key}"`;
3738
- });
4106
+ const hints = [];
4107
+ for (const [key, db] of entries) if (db.engine === "postgresql" || db.engine === "mysql") {
4108
+ const prefix = key.toUpperCase().replace(/-/g, "_");
4109
+ hints.push(`${prefix}_DATABASE_URL será injetado automaticamente de "${key}"`);
4110
+ if (db.pooler?.enabled && db.engine === "postgresql") hints.push(`${prefix}_POOLER_URL será injetado automaticamente (PgBouncer)`);
4111
+ }
4112
+ return hints;
3739
4113
  }
3740
4114
 
3741
4115
  //#endregion
@@ -3752,6 +4126,14 @@ const SERVICE_TYPE_LABELS = {
3752
4126
  * the server will generate one with nixpacks.
3753
4127
  */
3754
4128
  function prepareExtraFiles(_detection, serviceConfig) {
4129
+ if (serviceConfig?.docker) {
4130
+ const dockerfilePath = resolve(process.cwd(), serviceConfig.docker.dockerfile);
4131
+ if (!existsSync(dockerfilePath)) throw new Error(`Dockerfile não encontrado: ${serviceConfig.docker.dockerfile}`);
4132
+ return [{
4133
+ name: "Dockerfile",
4134
+ content: readFileSync(dockerfilePath, "utf-8")
4135
+ }];
4136
+ }
3755
4137
  if (existsSync(resolve(process.cwd(), "Dockerfile"))) return [];
3756
4138
  const rootDir = serviceConfig?.rootDirectory || ".";
3757
4139
  const serviceDockerfilePath = resolve(process.cwd(), rootDir, "Dockerfile");
@@ -3789,21 +4171,18 @@ async function computeExtraFilesForServices(services) {
3789
4171
  return results;
3790
4172
  }
3791
4173
  async function triggerDeploy(serviceId, serviceName, preDetection) {
3792
- const spinUpload = spinner(serviceName ? `Fazendo upload ${chalk.bold(serviceName)}...` : "Fazendo upload do código...");
3793
4174
  const sizeInBytes = await calculateDirectorySize(process.cwd());
3794
4175
  const sizeMB = Math.round(sizeInBytes / (1024 * 1024) * 10) / 10;
3795
- if (sizeMB > 5) spinUpload.text = `Fazendo upload (${sizeMB} MB)...`;
3796
4176
  const client = await getClient();
3797
- const serviceConf = resolveServiceConf(loadConfig(), serviceId);
4177
+ const velozConfig = loadConfig();
4178
+ const serviceConf = resolveServiceConf(velozConfig, serviceId);
3798
4179
  const detection = preDetection ?? detectLocalRepo();
3799
4180
  warnIfEphemeralFsDetected(detection, serviceConf, serviceName ?? void 0);
3800
4181
  const extraFiles = prepareExtraFiles(detection, serviceConf);
3801
4182
  const warnings = runPreDeployChecks(serviceConf?.rootDirectory || ".");
3802
- if (warnings.length > 0) {
3803
- spinUpload.stop();
3804
- printDeployWarnings(warnings);
3805
- spinUpload.start();
3806
- }
4183
+ if (warnings.length > 0) printDeployWarnings(warnings);
4184
+ const spinUpload = spinner(serviceName ? `Fazendo upload ${chalk.bold(serviceName)}...` : "Fazendo upload do código...");
4185
+ if (sizeMB > 5) spinUpload.text = `Fazendo upload (${sizeMB} MB)...`;
3807
4186
  spinUpload.text = "Iniciando deploy...";
3808
4187
  const deployment = await withRetry(() => client.deployments.create({
3809
4188
  serviceId,
@@ -3816,7 +4195,12 @@ async function triggerDeploy(serviceId, serviceName, preDetection) {
3816
4195
  setupSigintHandler();
3817
4196
  trackDeployment(deployment.id);
3818
4197
  try {
3819
- return await streamDeploymentLogs(deployment.id, serviceId, serviceName);
4198
+ const result = await streamDeploymentLogs(deployment.id, serviceId, serviceName);
4199
+ if (result.status === "LIVE" && velozConfig?.project?.id) {
4200
+ const dashUrl = `${process.env.VELOZ_WEB_URL || "https://app.onveloz.com"}/projetos/${velozConfig.project.id}`;
4201
+ info(`Dashboard: ${chalk.dim(dashUrl)}`);
4202
+ }
4203
+ return result;
3820
4204
  } finally {
3821
4205
  untrackDeployment(deployment.id);
3822
4206
  }
@@ -3876,15 +4260,56 @@ async function maybeConfigurePersistentVolume(serviceConfig, detection, opts, se
3876
4260
  async function findServicesFromConfig() {
3877
4261
  const config = loadConfig();
3878
4262
  if (!config) return [];
4263
+ const missingIds = Object.entries(config.services).filter(([_, svc]) => !svc.id);
4264
+ if (missingIds.length > 0 && config.project.id) {
4265
+ const client = await getClient();
4266
+ const remoteServices = await withRetry(() => client.services.list({ projectId: config.project.id }));
4267
+ let configUpdated = false;
4268
+ for (const [key, serviceConfig] of missingIds) {
4269
+ const match = remoteServices.find((rs) => rs.name.toLowerCase() === serviceConfig.name.toLowerCase() || rs.name.toLowerCase() === key.toLowerCase());
4270
+ if (match) {
4271
+ serviceConfig.id = match.id;
4272
+ config.services[key] = serviceConfig;
4273
+ configUpdated = true;
4274
+ info(`Serviço "${serviceConfig.name}" vinculado (ID: ${match.id})`);
4275
+ } else {
4276
+ info(`Serviço "${serviceConfig.name}" não encontrado no projeto.`);
4277
+ if (await promptConfirm(`Criar serviço "${serviceConfig.name}" no projeto "${config.project.name}"?`)) {
4278
+ const branch = getGitBranch();
4279
+ const serviceType = serviceConfig.type?.toUpperCase() ?? "WEB";
4280
+ serviceConfig.id = (await withSpinner({
4281
+ text: `Criando serviço "${serviceConfig.name}"...`,
4282
+ fn: () => withRetry(() => client.services.create({
4283
+ projectId: config.project.id,
4284
+ name: serviceConfig.name,
4285
+ type: serviceType,
4286
+ branch,
4287
+ rootDirectory: serviceConfig.root ?? "."
4288
+ }))
4289
+ })).id;
4290
+ config.services[key] = serviceConfig;
4291
+ configUpdated = true;
4292
+ success(`Serviço "${serviceConfig.name}" criado.`);
4293
+ }
4294
+ }
4295
+ }
4296
+ if (configUpdated) {
4297
+ saveConfig(config);
4298
+ info(`Arquivo ${getConfigFileName()} atualizado com IDs dos serviços.`);
4299
+ }
4300
+ }
3879
4301
  const services = [];
3880
- for (const [key, serviceConfig] of Object.entries(config.services)) services.push({
3881
- path: resolve(process.cwd(), serviceConfig.root ?? "."),
3882
- serviceId: serviceConfig.id,
3883
- projectId: config.project.id,
3884
- serviceName: serviceConfig.name,
3885
- projectName: config.project.name,
3886
- key
3887
- });
4302
+ for (const [key, serviceConfig] of Object.entries(config.services)) {
4303
+ if (!serviceConfig.id) continue;
4304
+ services.push({
4305
+ path: resolve(process.cwd(), serviceConfig.root ?? "."),
4306
+ serviceId: serviceConfig.id,
4307
+ projectId: config.project.id,
4308
+ serviceName: serviceConfig.name,
4309
+ projectName: config.project.name,
4310
+ key
4311
+ });
4312
+ }
3888
4313
  return services;
3889
4314
  }
3890
4315
  function readLocalFile(path) {
@@ -4063,6 +4488,7 @@ async function createServiceFlow(projectId, projectName, repoName, opts = {}) {
4063
4488
  const allApps = detection.monorepoApps.map((a) => ({
4064
4489
  name: a.name,
4065
4490
  root: a.path,
4491
+ type: a.framework?.type ?? "WEB",
4066
4492
  framework: a.framework?.name ?? null,
4067
4493
  buildCommand: a.framework?.buildCommand ?? null,
4068
4494
  startCommand: a.framework?.startCommand ?? null,
@@ -4128,7 +4554,7 @@ async function createServiceFlow(projectId, projectName, repoName, opts = {}) {
4128
4554
  fn: () => withRetry(() => client.services.create({
4129
4555
  projectId,
4130
4556
  name: app.name,
4131
- type: "WEB",
4557
+ type: app.type,
4132
4558
  branch,
4133
4559
  rootDirectory: app.root,
4134
4560
  buildCommand: app.buildCommand ?? void 0,
@@ -4341,6 +4767,7 @@ async function addServiceFlow(existingConfig, opts) {
4341
4767
  const availableApps = detection.monorepoApps.map((a) => ({
4342
4768
  name: a.name,
4343
4769
  root: a.path,
4770
+ type: a.framework?.type ?? "WEB",
4344
4771
  framework: a.framework?.name ?? null,
4345
4772
  buildCommand: a.framework?.buildCommand ?? null,
4346
4773
  startCommand: a.framework?.startCommand ?? null,
@@ -4408,7 +4835,7 @@ async function addServiceFlow(existingConfig, opts) {
4408
4835
  fn: () => withRetry(() => client.services.create({
4409
4836
  projectId,
4410
4837
  name: app.name,
4411
- type: "WEB",
4838
+ type: app.type,
4412
4839
  branch,
4413
4840
  rootDirectory: app.root,
4414
4841
  buildCommand: app.buildCommand ?? void 0,
@@ -4640,7 +5067,8 @@ async function cliDeployFlow(opts) {
4640
5067
  const available = configuredServices.map((s) => ` • ${s.key} (${s.serviceName})`).join("\n");
4641
5068
  throw new Error(`Serviço '${opts.service}' não encontrado.\n\nServiços disponíveis:\n${available}`);
4642
5069
  }
4643
- return await triggerDeploy(found.serviceId, found.serviceName);
5070
+ await triggerDeploy(found.serviceId, found.serviceName);
5071
+ return;
4644
5072
  }
4645
5073
  if (opts.all || opts.yes || configuredServices.length === 1) {
4646
5074
  if (configuredServices.length > 1 && !opts.yes) {
@@ -4655,25 +5083,25 @@ async function cliDeployFlow(opts) {
4655
5083
  return;
4656
5084
  }
4657
5085
  }
4658
- if (configuredServices.length === 1) return await triggerDeploy(configuredServices[0].serviceId, configuredServices[0].serviceName);
4659
- else return await deployServicesInParallel(await computeExtraFilesForServices(configuredServices));
4660
- } else {
4661
- console.log(chalk.bold("\nServiços disponíveis:\n"));
4662
- const selectedServiceIds = await promptMultiSelect("Quais serviços deseja fazer deploy?", configuredServices.map((s) => {
4663
- const relPath = relative(process.cwd(), s.path) || ".";
4664
- return {
4665
- label: `${s.serviceName} ${chalk.dim(`(${relPath})`)}`,
4666
- value: s.serviceId
4667
- };
4668
- }));
4669
- const selectedServices = configuredServices.filter((s) => selectedServiceIds.includes(s.serviceId));
4670
- if (selectedServices.length === 0) {
4671
- info("Nenhum serviço selecionado.");
4672
- return;
4673
- }
4674
- if (selectedServices.length === 1) return await triggerDeploy(selectedServices[0].serviceId, selectedServices[0].serviceName);
4675
- else return await deployServicesInParallel(await computeExtraFilesForServices(selectedServices));
5086
+ if (configuredServices.length === 1) await triggerDeploy(configuredServices[0].serviceId, configuredServices[0].serviceName);
5087
+ else await deployServicesInParallel(await computeExtraFilesForServices(configuredServices));
5088
+ return;
4676
5089
  }
5090
+ console.log(chalk.bold("\nServiços disponíveis:\n"));
5091
+ const selectedServiceIds = await promptMultiSelect("Quais serviços deseja fazer deploy?", configuredServices.map((s) => {
5092
+ const relPath = relative(process.cwd(), s.path) || ".";
5093
+ return {
5094
+ label: `${s.serviceName} ${chalk.dim(`(${relPath})`)}`,
5095
+ value: s.serviceId
5096
+ };
5097
+ }));
5098
+ const selectedServices = configuredServices.filter((s) => selectedServiceIds.includes(s.serviceId));
5099
+ if (selectedServices.length === 0) {
5100
+ info("Nenhum serviço selecionado.");
5101
+ return;
5102
+ }
5103
+ if (selectedServices.length === 1) await triggerDeploy(selectedServices[0].serviceId, selectedServices[0].serviceName);
5104
+ else await deployServicesInParallel(await computeExtraFilesForServices(selectedServices));
4677
5105
  }
4678
5106
  if (!isGitRepo()) throw new Error("Este diretório não é um repositório git. Inicialize com `git init` e adicione um remote.");
4679
5107
  info("Detectando repositório git...");
@@ -4728,7 +5156,8 @@ async function cliDeployFlow(opts) {
4728
5156
  info(`Arquivo ${getConfigFileName()} criado na raiz do projeto.`);
4729
5157
  const freshConfig = loadConfig();
4730
5158
  if (freshConfig) await provisionDatabases(freshConfig, { yes: opts.yes ?? false });
4731
- return await triggerDeploy(serviceId$1, void 0, detection);
5159
+ await triggerDeploy(serviceId$1, void 0, detection);
5160
+ return;
4732
5161
  }
4733
5162
  if (project && project.services.length === 0) {
4734
5163
  info(`Projeto encontrado: ${chalk.bold(project.name)}`);
@@ -4739,7 +5168,8 @@ async function cliDeployFlow(opts) {
4739
5168
  });
4740
5169
  const freshConfig = loadConfig();
4741
5170
  if (freshConfig) await provisionDatabases(freshConfig, { yes: opts.yes ?? false });
4742
- return await triggerDeploy(serviceId$1);
5171
+ await triggerDeploy(serviceId$1);
5172
+ return;
4743
5173
  }
4744
5174
  info("Projeto não encontrado. Vamos criar um novo.");
4745
5175
  const projectName = opts.yes ? remote.repo : await prompt(`Nome do projeto: ${chalk.dim(`(${remote.repo})`)}`) || remote.repo;
@@ -4758,7 +5188,7 @@ async function cliDeployFlow(opts) {
4758
5188
  });
4759
5189
  const newConfig = loadConfig();
4760
5190
  if (newConfig) await provisionDatabases(newConfig, { yes: opts.yes ?? false });
4761
- return await triggerDeploy(serviceId);
5191
+ await triggerDeploy(serviceId);
4762
5192
  }
4763
5193
 
4764
5194
  //#endregion
@@ -5072,7 +5502,7 @@ async function pruneRemovedEntries(config, existingConfig, services, databases)
5072
5502
  //#region src/index.ts
5073
5503
  if (process.argv.includes("--mcp")) process.env.VELOZ_MCP = "true";
5074
5504
  const cli = Cli.create("veloz", {
5075
- version: "0.0.0-beta.15",
5505
+ version: "0.0.0-beta.17",
5076
5506
  description: "CLI da plataforma Veloz — deploy rápido para o Brasil",
5077
5507
  env: z.object({ VELOZ_ENV: z.string().optional().describe("Ambiente alvo (ex: preview, staging)") })
5078
5508
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "onveloz",
3
- "version": "0.0.0-beta.15",
3
+ "version": "0.0.0-beta.17",
4
4
  "description": "CLI da plataforma Veloz — deploy rápido para o Brasil",
5
5
  "keywords": [
6
6
  "brasil",