onveloz 0.0.0-beta.26 → 0.0.0-beta.27

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.
@@ -27,6 +27,25 @@ function buildStageOrder(steps) {
27
27
  }
28
28
  return order;
29
29
  }
30
+ /** Matches the pre-start log section header written by the reconciler. */
31
+ const PRE_START_HEADER_RE = /^──\s*Logs do comando pre-start/;
32
+ /** Matches the pre-start unavailable message. */
33
+ const PRE_START_UNAVAILABLE_RE = /^──\s*Logs do comando pre-start indisponíveis/;
34
+ /** Matches the crash log section header written by the reconciler. */
35
+ const CRASH_LOG_HEADER_RE = /^──\s*Logs do container \(crash\)/;
36
+ /** Matches the crash logs unavailable message. */
37
+ const CRASH_LOG_UNAVAILABLE_RE = /^──\s*Logs do container indisponíveis/;
38
+ /** Lines that mark the beginning of the deploy/rollout phase. */
39
+ const DEPLOY_PHASE_MARKERS = [
40
+ /^Realizando deploy/,
41
+ /^Atualizando serviço/,
42
+ /^Criando serviço/,
43
+ /^Waiting for rollout/,
44
+ /^Updating deployment/,
45
+ /^Creating K8s deployment/
46
+ ];
47
+ /** Lines that indicate the pre-start command is starting. */
48
+ const PRE_START_MARKER_RE = /^Executando comando pre-start:/;
30
49
  const RUNTIME_LINE_PATTERNS = [
31
50
  /^[\u26A0\u{1F680}]/u,
32
51
  /^\{.*"(?:level|msg|pid)"/,
@@ -40,6 +59,14 @@ const RUNTIME_LINE_PATTERNS = [
40
59
  function isRuntimeLine(content) {
41
60
  return RUNTIME_LINE_PATTERNS.some((p) => p.test(content.trim()));
42
61
  }
62
+ function isPreStartLine(content) {
63
+ const trimmed = content.trim();
64
+ return PRE_START_MARKER_RE.test(trimmed) || PRE_START_HEADER_RE.test(trimmed) || PRE_START_UNAVAILABLE_RE.test(trimmed);
65
+ }
66
+ function isDeployPhaseLine(content) {
67
+ const trimmed = content.trim();
68
+ return DEPLOY_PHASE_MARKERS.some((p) => p.test(trimmed)) || CRASH_LOG_HEADER_RE.test(trimmed) || CRASH_LOG_UNAVAILABLE_RE.test(trimmed);
69
+ }
43
70
  function parseBuildSteps(rawText) {
44
71
  const rawLines = rawText.split("\n");
45
72
  let currentPhase = 0;
@@ -172,22 +199,61 @@ function parseBuildSteps(rawText) {
172
199
  generalCount++;
173
200
  const generalStep = allSteps[entry.origIdx];
174
201
  if (generalCount === totalGenerals && totalGenerals > 1) {
202
+ const finalizationLines = [];
203
+ const preStartLines = [];
175
204
  const deployLines = [];
176
205
  const healthLines = [];
177
- let hitRuntime = false;
206
+ let phase = "finalization";
207
+ let inCrashLogBlock = false;
178
208
  for (const line of generalStep.lines) {
179
- if (!hitRuntime && isRuntimeLine(line.content)) hitRuntime = true;
180
- if (hitRuntime) healthLines.push(line);
181
- else deployLines.push(line);
209
+ const content = line.content.trim();
210
+ if (CRASH_LOG_HEADER_RE.test(content) || PRE_START_HEADER_RE.test(content)) inCrashLogBlock = true;
211
+ if (phase === "finalization" && isPreStartLine(content)) phase = "prestart";
212
+ else if ((phase === "finalization" || phase === "prestart") && isDeployPhaseLine(content)) phase = "deploy";
213
+ else if ((phase === "finalization" || phase === "deploy") && !inCrashLogBlock && isRuntimeLine(content)) phase = "health";
214
+ switch (phase) {
215
+ case "finalization":
216
+ finalizationLines.push(line);
217
+ break;
218
+ case "prestart":
219
+ preStartLines.push(line);
220
+ break;
221
+ case "deploy":
222
+ deployLines.push(line);
223
+ break;
224
+ case "health":
225
+ healthLines.push(line);
226
+ break;
227
+ }
182
228
  }
183
- if (deployLines.length > 0) result.push({
229
+ const hasPreStartError = preStartLines.some((l) => /pre-start falhou|FAILED/i.test(l.content));
230
+ const hasDeployError = deployLines.some((l) => /DEPLOY FAILED/i.test(l.content));
231
+ if (finalizationLines.length > 0) result.push({
184
232
  stepNumber: null,
185
233
  title: "Finalização",
186
234
  duration: null,
187
235
  status: "done",
236
+ lines: finalizationLines,
237
+ phase: generalStep.phase,
238
+ startedAt: finalizationLines[0]?.timestamp ?? generalStep.startedAt
239
+ });
240
+ if (preStartLines.length > 0) result.push({
241
+ stepNumber: null,
242
+ title: "Pre-start",
243
+ duration: null,
244
+ status: hasPreStartError ? "error" : "done",
245
+ lines: preStartLines,
246
+ phase: generalStep.phase,
247
+ startedAt: preStartLines[0]?.timestamp ?? null
248
+ });
249
+ if (deployLines.length > 0) result.push({
250
+ stepNumber: null,
251
+ title: "Deploy",
252
+ duration: null,
253
+ status: hasDeployError ? "error" : "done",
188
254
  lines: deployLines,
189
255
  phase: generalStep.phase,
190
- startedAt: deployLines[0]?.timestamp ?? generalStep.startedAt
256
+ startedAt: deployLines[0]?.timestamp ?? null
191
257
  });
192
258
  if (healthLines.length > 0) result.push({
193
259
  stepNumber: null,
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { Cli, middleware, z } from "incur";
3
- import { exec, execFile, execSync } from "node:child_process";
3
+ import { exec, execSync } from "node:child_process";
4
4
  import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, statSync, unlinkSync, writeFileSync } from "node:fs";
5
5
  import { basename, dirname, join, relative, resolve } from "node:path";
6
6
  import { z as z$1 } from "zod";
@@ -9,11 +9,11 @@ import { RPCLink } from "@orpc/client/fetch";
9
9
  import { createHash } from "node:crypto";
10
10
  import { homedir, platform, tmpdir } from "node:os";
11
11
  import chalk from "chalk";
12
+ import { createAuthClient } from "better-auth/client";
13
+ import { deviceAuthorizationClient } from "better-auth/client/plugins";
12
14
  import * as readline from "node:readline";
13
15
  import { createInterface } from "node:readline";
14
16
  import ora from "ora";
15
- import { createAuthClient } from "better-auth/client";
16
- import { deviceAuthorizationClient } from "better-auth/client/plugins";
17
17
  import net from "node:net";
18
18
  import WebSocket from "ws";
19
19
  import { link, mkdir, mkdtemp, readdir, rm, stat } from "node:fs/promises";
@@ -761,13 +761,17 @@ async function promptMultiSelect(question, options) {
761
761
  }
762
762
  return indices.map((i) => options[i].value);
763
763
  }
764
+ function openBrowser(url) {
765
+ return new Promise((resolve$1) => {
766
+ const os = platform();
767
+ exec(os === "darwin" ? `open "${url}"` : os === "win32" ? `start "" "${url}"` : `xdg-open "${url}"`, (error) => {
768
+ resolve$1(!error);
769
+ });
770
+ });
771
+ }
764
772
 
765
773
  //#endregion
766
774
  //#region src/commands/login.ts
767
- function openBrowser$1(url) {
768
- const os = platform();
769
- exec(os === "darwin" ? `open "${url}"` : os === "win32" ? `start "" "${url}"` : `xdg-open "${url}"`, () => {});
770
- }
771
775
  const CLIENT_ID = "veloz-cli";
772
776
  async function performLogin(apiUrl) {
773
777
  const config = loadConfig$1();
@@ -789,10 +793,15 @@ async function performLogin(apiUrl) {
789
793
  console.log();
790
794
  console.log(chalk.white(` Código de verificação: ${chalk.bold.cyan(data.user_code)}`));
791
795
  console.log();
792
- console.log(chalk.dim(" Se o navegador não abrir, acesse manualmente:"));
793
- console.log(chalk.dim(` ${verificationUrl}`));
796
+ if (!await openBrowser(verificationUrl)) {
797
+ info("Não foi possível abrir o navegador. Acesse manualmente:");
798
+ console.log();
799
+ console.log(` ${chalk.bold(verificationUrl)}`);
800
+ } else {
801
+ console.log(chalk.dim(" Se o navegador não abrir, acesse manualmente:"));
802
+ console.log(chalk.dim(` ${verificationUrl}`));
803
+ }
794
804
  console.log();
795
- openBrowser$1(verificationUrl);
796
805
  const pollSpinner = spinner("Aguardando autorização no navegador...");
797
806
  const token = await pollForToken(authClient, data.device_code, data.interval || 5);
798
807
  if (!token) {
@@ -1179,7 +1188,7 @@ envGroup.command("list", {
1179
1188
  serviceName: z.string(),
1180
1189
  vars: z.array(z.object({
1181
1190
  key: z.string(),
1182
- maskedValue: z.string(),
1191
+ value: z.string(),
1183
1192
  updatedAt: z.string()
1184
1193
  }))
1185
1194
  })),
@@ -1202,12 +1211,15 @@ envGroup.command("list", {
1202
1211
  });
1203
1212
  continue;
1204
1213
  }
1205
- for (const v of envVars) console.log(` ${chalk.bold(v.key)} ${chalk.dim(v.maskedValue)} ${new Date(v.updatedAt).toLocaleDateString("pt-BR")}`);
1214
+ for (const v of envVars) {
1215
+ const displayValue = v.value ?? v.maskedValue;
1216
+ console.log(` ${chalk.bold(v.key)} ${chalk.dim(displayValue)} ${new Date(v.updatedAt).toLocaleDateString("pt-BR")}`);
1217
+ }
1206
1218
  result.push({
1207
1219
  serviceName: service.name,
1208
1220
  vars: envVars.map((v) => ({
1209
1221
  key: v.key,
1210
- maskedValue: v.maskedValue,
1222
+ value: v.value ?? v.maskedValue,
1211
1223
  updatedAt: v.updatedAt instanceof Date ? v.updatedAt.toISOString() : String(v.updatedAt)
1212
1224
  }))
1213
1225
  });
@@ -1379,7 +1391,7 @@ envGroup.command("export", {
1379
1391
  options: z.object({ service: z.string().optional().describe("Serviço alvo (chave ou nome)") }),
1380
1392
  output: z.object({ vars: z.array(z.object({
1381
1393
  key: z.string(),
1382
- maskedValue: z.string()
1394
+ value: z.string()
1383
1395
  })) }),
1384
1396
  async run(c) {
1385
1397
  const serviceId = await resolveServiceId(c.options.service);
@@ -1392,19 +1404,21 @@ envGroup.command("export", {
1392
1404
  info("Nenhuma variável de ambiente para exportar.");
1393
1405
  return { vars: [] };
1394
1406
  }
1395
- const envContent = envVars.map((v) => `${v.key}=${v.maskedValue}`).join("\n");
1407
+ const isDecrypted = envVars.some((v) => v.value !== null && v.value !== void 0);
1408
+ const envContent = envVars.map((v) => `${v.key}=${v.value ?? v.maskedValue}`).join("\n");
1396
1409
  if (c.args.arquivo) {
1397
1410
  writeFileSync(resolve(process.cwd(), c.args.arquivo), envContent + "\n", "utf-8");
1398
1411
  success(`Variáveis exportadas para ${chalk.bold(c.args.arquivo)}`);
1399
- console.log(chalk.dim("Nota: Valores estão mascarados por segurança."));
1412
+ if (!isDecrypted) console.log(chalk.dim("Nota: Valores estão mascarados por segurança."));
1400
1413
  } else {
1401
- console.log(chalk.bold("\n# Variáveis de Ambiente (valores mascarados)\n"));
1414
+ if (isDecrypted) console.log(chalk.bold("\n# Variáveis de Ambiente\n"));
1415
+ else console.log(chalk.bold("\n# Variáveis de Ambiente (valores mascarados)\n"));
1402
1416
  console.log(envContent);
1403
1417
  console.log();
1404
1418
  }
1405
1419
  return { vars: envVars.map((v) => ({
1406
1420
  key: v.key,
1407
- maskedValue: v.maskedValue
1421
+ value: v.value ?? v.maskedValue
1408
1422
  })) };
1409
1423
  }
1410
1424
  });
@@ -3985,12 +3999,6 @@ logsGroup.command("query-help", {
3985
3999
 
3986
4000
  //#endregion
3987
4001
  //#region src/commands/github.ts
3988
- function openBrowser(url) {
3989
- const os = platform();
3990
- execFile(os === "darwin" ? "open" : os === "win32" ? "start" : "xdg-open", os === "win32" ? ["", url] : [url], (error) => {
3991
- if (error) warn(`Não foi possível abrir o navegador: ${error.message}`);
3992
- });
3993
- }
3994
4002
  const githubGroup = Cli.create("github", { description: "Gerenciar integração com GitHub" });
3995
4003
  githubGroup.command("setup", {
3996
4004
  description: "Configurar GitHub App para deploy automático via webhook",
@@ -4102,10 +4110,18 @@ githubGroup.command("setup", {
4102
4110
  projectId
4103
4111
  };
4104
4112
  }
4105
- openBrowser(installation.installUrl);
4113
+ const opened = await openBrowser(installation.installUrl);
4106
4114
  console.log();
4107
- info("Aguardando instalação do GitHub App...");
4108
- console.log(chalk.dim(" Instale o app no navegador e volte aqui."));
4115
+ if (opened) {
4116
+ info("Aguardando instalação do GitHub App...");
4117
+ console.log(chalk.dim(" Instale o app no navegador e volte aqui."));
4118
+ } else {
4119
+ info("Não foi possível abrir o navegador. Abra o link abaixo manualmente:");
4120
+ console.log();
4121
+ console.log(` ${chalk.bold(installation.installUrl)}`);
4122
+ console.log();
4123
+ console.log(chalk.dim(" Instale o app e volte aqui."));
4124
+ }
4109
4125
  console.log();
4110
4126
  const pollSpinner = spinner("Aguardando instalação...");
4111
4127
  const maxAttempts = 60;
@@ -4258,6 +4274,25 @@ const TERMINAL_STATUSES = new Set([
4258
4274
 
4259
4275
  //#endregion
4260
4276
  //#region src/lib/deploy-stream.ts
4277
+ const DASHBOARD_BASE = process.env.VELOZ_WEB_URL || "https://app.onveloz.com";
4278
+ /**
4279
+ * Fetch deployment details from the API and enrich a DeployStreamResult
4280
+ * with metadata useful for agents (failReason, commit info, duration, dashboard link).
4281
+ */
4282
+ async function enrichResult(client, result) {
4283
+ try {
4284
+ const d = await client.deployments.get({ deploymentId: result.deploymentId });
4285
+ const service = await client.services.get({ serviceId: d.serviceId }).catch(() => null);
4286
+ const projectId = service ? service.projectId : null;
4287
+ result.failReason = d.failReason ?? null;
4288
+ result.errorSummary = d.errorSummary ?? null;
4289
+ result.commitSha = d.commitSha ?? null;
4290
+ result.branch = d.branch ?? null;
4291
+ if (d.startedAt && d.finishedAt) result.durationSeconds = Math.round((new Date(d.finishedAt).getTime() - new Date(d.startedAt).getTime()) / 1e3);
4292
+ if (projectId) result.dashboardUrl = `${DASHBOARD_BASE}/projetos/${projectId}/servicos/${d.serviceId}/deploys/${d.id}`;
4293
+ } catch {}
4294
+ return result;
4295
+ }
4261
4296
  async function fetchDeployUrls$1(client, serviceId) {
4262
4297
  try {
4263
4298
  return (await client.domains.list({ serviceId })).map((d) => `https://${d.domain}`);
@@ -4397,7 +4432,7 @@ async function streamDeploymentLogs(deploymentId, serviceId, serviceName) {
4397
4432
  const isTTY = !mcp && process.stdout.isTTY;
4398
4433
  const isGHA = !mcp && process.env.GITHUB_ACTIONS === "true";
4399
4434
  if (isTTY && !isVerbose) {
4400
- const { renderDeployTUI } = await import("./deploy-tui-DGRYRsf_.mjs");
4435
+ const { renderDeployTUI } = await import("./deploy-tui-D_pzq4Tu.mjs");
4401
4436
  return renderDeployTUI(deploymentId, serviceId, serviceName);
4402
4437
  }
4403
4438
  const allLogLines = [];
@@ -4470,13 +4505,13 @@ async function streamDeploymentLogs(deploymentId, serviceId, serviceName) {
4470
4505
  }
4471
4506
  if (!mcp) process.exit(1);
4472
4507
  }
4473
- return {
4508
+ return enrichResult(client, {
4474
4509
  deploymentId,
4475
4510
  status: finalStatus,
4476
4511
  logs: allLogLines.filter((l) => l.trim()),
4477
4512
  urls,
4478
4513
  serviceName
4479
- };
4514
+ });
4480
4515
  }
4481
4516
 
4482
4517
  //#endregion
@@ -5283,7 +5318,7 @@ const LOGO_LINES = [
5283
5318
  ];
5284
5319
  const BRAND_COLOR = "#FF4D00";
5285
5320
  function getVersion() {
5286
- return "0.0.0-beta.26";
5321
+ return "0.0.0-beta.27";
5287
5322
  }
5288
5323
  function printBanner(subtitle) {
5289
5324
  const version = getVersion();
@@ -6011,32 +6046,6 @@ function checkDockerfileLockFiles(basePath) {
6011
6046
  * and public/ and does not simplify the setup on this platform.
6012
6047
  */
6013
6048
  /**
6014
- * Prisma needs `prisma generate` in the build step.
6015
- * Without it, the Prisma client won't be generated and the app will crash.
6016
- */
6017
- function checkPrismaGenerate(basePath) {
6018
- const pkgPath = resolve(basePath, "package.json");
6019
- if (!existsSync(pkgPath)) return null;
6020
- try {
6021
- const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
6022
- const allDeps = {
6023
- ...pkg.dependencies,
6024
- ...pkg.devDependencies
6025
- };
6026
- if (!("prisma" in allDeps) && !("@prisma/client" in allDeps)) return null;
6027
- const scripts = pkg.scripts || {};
6028
- const buildScript = scripts.build || "";
6029
- const postinstall = scripts.postinstall || "";
6030
- if (buildScript.includes("prisma generate") || postinstall.includes("prisma generate") || buildScript.includes("prisma db push")) return null;
6031
- return {
6032
- message: "Prisma detectado mas prisma generate nao esta no build/postinstall",
6033
- hint: "Adicione \"prisma generate\" ao postinstall ou build → onveloz.com/docs/best-practices#prisma--prisma-generate-no-build"
6034
- };
6035
- } catch {
6036
- return null;
6037
- }
6038
- }
6039
- /**
6040
6049
  * Detect if the app hardcodes a port that doesn't match the configured port.
6041
6050
  * Common issue: app listens on 8080 but service port is 3000.
6042
6051
  */
@@ -6270,7 +6279,6 @@ function runPreDeployChecks(basePath = ".") {
6270
6279
  const checks = [
6271
6280
  checkNitroPreset,
6272
6281
  checkDockerfileLockFiles,
6273
- checkPrismaGenerate,
6274
6282
  checkPortMismatch,
6275
6283
  checkEnvFileCommitted,
6276
6284
  checkSvelteKitAdapter,
@@ -6333,24 +6341,50 @@ async function fetchLatestVersion() {
6333
6341
  return null;
6334
6342
  }
6335
6343
  }
6336
- async function autoUpdate() {
6337
- if (process.env.VELOZ_MCP === "true") return;
6338
- const pm = detectPackageManager();
6339
- if (!pm) return;
6340
- const currentVersion = "0.0.0-beta.26";
6341
- const latestVersion = await fetchLatestVersion();
6342
- if (!latestVersion || latestVersion === currentVersion) return;
6343
- const installCmd = getInstallCommand(pm, latestVersion);
6344
- const spin = spinner(`Atualizando CLI ${chalk.dim(currentVersion)} → ${chalk.bold(latestVersion)}...`);
6344
+ function getCurrentVersion() {
6345
+ return "0.0.0-beta.27";
6346
+ }
6347
+ /**
6348
+ * Install a specific CLI version. Returns true on success.
6349
+ */
6350
+ function installVersion(pm, version) {
6351
+ const installCmd = getInstallCommand(pm, version);
6352
+ const spin = spinner(`Atualizando CLI ${chalk.dim(getCurrentVersion())} → ${chalk.bold(version)}...`);
6345
6353
  try {
6346
6354
  execSync(installCmd, { stdio: "ignore" });
6347
6355
  spin.stop();
6348
- console.log(chalk.green(`\n✓ CLI atualizada: ${chalk.bold(latestVersion)}\n`));
6356
+ console.log(chalk.green(`\n✓ CLI atualizada: ${chalk.bold(version)}\n`));
6357
+ return true;
6349
6358
  } catch {
6350
6359
  spin.stop();
6351
6360
  console.log(chalk.yellow(`\n⚠ Não foi possível atualizar automaticamente. Execute manualmente:\n ${installCmd}\n`));
6361
+ return false;
6352
6362
  }
6353
6363
  }
6364
+ /**
6365
+ * Re-exec the CLI with the same args so the new binary runs.
6366
+ * Used after auto-update during `veloz deploy`.
6367
+ */
6368
+ function reExec() {
6369
+ execSync(`veloz ${process.argv.slice(2).join(" ")}`, {
6370
+ stdio: "inherit",
6371
+ env: {
6372
+ ...process.env,
6373
+ VELOZ_SKIP_UPDATE: "true"
6374
+ }
6375
+ });
6376
+ process.exit(0);
6377
+ }
6378
+ async function autoUpdate() {
6379
+ if (process.env.VELOZ_MCP === "true") return;
6380
+ if (process.env.VELOZ_SKIP_UPDATE === "true") return;
6381
+ const pm = detectPackageManager();
6382
+ if (!pm) return;
6383
+ const currentVersion = getCurrentVersion();
6384
+ const latestVersion = await fetchLatestVersion();
6385
+ if (!latestVersion || latestVersion === currentVersion) return;
6386
+ if (installVersion(pm, latestVersion)) reExec();
6387
+ }
6354
6388
 
6355
6389
  //#endregion
6356
6390
  //#region src/lib/deploy-databases.ts
@@ -6595,7 +6629,6 @@ function prepareServicesForDeploy(services) {
6595
6629
  }
6596
6630
  return services.map((svc) => {
6597
6631
  const serviceConf = resolveServiceConf(velozConfig, svc.serviceId);
6598
- warnIfEphemeralFsDetected(detectLocalRepo(serviceConf?.rootDirectory || "."), serviceConf, svc.serviceName);
6599
6632
  return {
6600
6633
  serviceId: svc.serviceId,
6601
6634
  serviceName: svc.serviceName,
@@ -6604,13 +6637,12 @@ function prepareServicesForDeploy(services) {
6604
6637
  };
6605
6638
  });
6606
6639
  }
6607
- async function triggerDeploy(serviceId, serviceName, preDetection) {
6640
+ async function triggerDeploy(serviceId, serviceName) {
6608
6641
  const sizeInBytes = await calculateDirectorySize(process.cwd());
6609
6642
  const sizeMB = Math.round(sizeInBytes / (1024 * 1024) * 10) / 10;
6610
6643
  const client = await getClient();
6611
6644
  const velozConfig = loadConfig();
6612
6645
  const serviceConf = resolveServiceConf(velozConfig, serviceId);
6613
- warnIfEphemeralFsDetected(preDetection ?? detectLocalRepo(), serviceConf, serviceName ?? void 0);
6614
6646
  const warnings = runPreDeployChecks(serviceConf?.rootDirectory || ".");
6615
6647
  if (warnings.length > 0) printDeployWarnings(warnings);
6616
6648
  const spinUpload = spinner(serviceName ? `Fazendo upload ${chalk.bold(serviceName)}...` : "Fazendo upload do código...");
@@ -6637,25 +6669,6 @@ async function triggerDeploy(serviceId, serviceName, preDetection) {
6637
6669
  untrackDeployment(deployment.id);
6638
6670
  }
6639
6671
  }
6640
- function warnIfEphemeralFsDetected(detection, serviceConf, serviceLabel) {
6641
- if (!detection.usesNodeFs || (serviceConf?.volumes?.length ?? 0) > 0) return;
6642
- warn(`Uso de fs/node:fs detectado${serviceLabel ? ` no serviço ${chalk.bold(serviceLabel)}` : ""}. O filesystem do container é efêmero; configure um volume em veloz.json ou use 'veloz volumes create'.`);
6643
- }
6644
- async function maybeConfigurePersistentVolume(serviceConfig, detection, opts, serviceLabel) {
6645
- if (!detection.usesNodeFs || (serviceConfig.volumes?.length ?? 0) > 0) return;
6646
- if (opts.yes) return;
6647
- if (!await promptConfirm(`Deseja adicionar um volume persistente para ${serviceLabel}?`, true)) return;
6648
- const name = await prompt(`Nome do volume ${chalk.dim("(data)")}:`) || "data";
6649
- const mountPath = await prompt(`Mount path ${chalk.dim("(/data)")}:`) || "/data";
6650
- const sizeInput = await prompt(`Tamanho em GB ${chalk.dim("(10)")}:`);
6651
- const parsedSize = Number.parseInt(sizeInput || "10", 10);
6652
- serviceConfig.volumes = [{
6653
- name,
6654
- mountPath,
6655
- sizeGb: Number.isInteger(parsedSize) && parsedSize >= 10 ? parsedSize : 10
6656
- }];
6657
- info(`Volume persistente configurado para ${serviceLabel}.`);
6658
- }
6659
6672
  async function findServicesFromConfig() {
6660
6673
  const config = loadConfig();
6661
6674
  if (!config) return [];
@@ -6951,7 +6964,7 @@ async function createServiceFlow(projectId, projectName, repoName, opts = {}) {
6951
6964
  if (newPort) app.port = parseInt(newPort, 10) || app.port;
6952
6965
  }
6953
6966
  }
6954
- let config$1 = {
6967
+ let config = {
6955
6968
  version: "1.0",
6956
6969
  project: {
6957
6970
  id: projectId,
@@ -6982,7 +6995,7 @@ async function createServiceFlow(projectId, projectName, repoName, opts = {}) {
6982
6995
  service: service$1,
6983
6996
  app
6984
6997
  });
6985
- config$1.services[app.root] = {
6998
+ config.services[app.root] = {
6986
6999
  id: service$1.id,
6987
7000
  name: service$1.name,
6988
7001
  type: "web",
@@ -6994,21 +7007,20 @@ async function createServiceFlow(projectId, projectName, repoName, opts = {}) {
6994
7007
  port: app.port ?? 3e3
6995
7008
  }
6996
7009
  };
6997
- await maybeConfigurePersistentVolume(config$1.services[app.root], detectLocalRepo(app.root), opts, app.name);
6998
7010
  }
6999
- saveConfig(config$1);
7011
+ saveConfig(config);
7000
7012
  info(`Arquivo ${getConfigFileName()} criado na raiz do projeto.`);
7001
7013
  if (!opts.yes) for (const { service: service$1, app } of createdServices) {
7002
7014
  console.log(chalk.cyan(`\n── Configurando variáveis: ${app.name} ──\n`));
7003
7015
  const serviceDetection = detectLocalRepo(app.root);
7004
- await promptEnvVars(service$1.id, serviceDetection.envVars.map((v) => v.key), config$1);
7016
+ await promptEnvVars(service$1.id, serviceDetection.envVars.map((v) => v.key), config);
7005
7017
  }
7006
- config$1 = await provisionDatabases(config$1, { yes: opts.yes ?? false });
7018
+ config = await provisionDatabases(config, { yes: opts.yes ?? false });
7007
7019
  await deployServices(createdServices.map(({ service: service$1, app }) => ({
7008
7020
  serviceId: service$1.id,
7009
7021
  serviceName: app.name,
7010
7022
  projectId,
7011
- serviceConfig: resolveServiceConf(config$1, service$1.id)
7023
+ serviceConfig: resolveServiceConf(config, service$1.id)
7012
7024
  })), {
7013
7025
  projectRoot: process.cwd(),
7014
7026
  output: createDeployOutput()
@@ -7072,7 +7084,7 @@ async function createServiceFlow(projectId, projectName, repoName, opts = {}) {
7072
7084
  });
7073
7085
  success(`Serviço criado: ${chalk.bold(service.name)}`);
7074
7086
  if (!opts.yes) await promptEnvVars(service.id, detection.envVars.map((v) => v.key), null);
7075
- const config = {
7087
+ saveConfig({
7076
7088
  version: "1.0",
7077
7089
  project: {
7078
7090
  id: projectId,
@@ -7094,9 +7106,7 @@ async function createServiceFlow(projectId, projectName, repoName, opts = {}) {
7094
7106
  }
7095
7107
  } },
7096
7108
  created: (/* @__PURE__ */ new Date()).toISOString()
7097
- };
7098
- await maybeConfigurePersistentVolume(config.services.main, detection, opts, service.name);
7099
- saveConfig(config);
7109
+ });
7100
7110
  info(`Arquivo ${getConfigFileName()} criado na raiz do projeto.`);
7101
7111
  return service.id;
7102
7112
  }
@@ -7286,7 +7296,6 @@ async function addServiceFlow(existingConfig, opts) {
7286
7296
  port: app.port ?? 3e3
7287
7297
  }
7288
7298
  };
7289
- await maybeConfigurePersistentVolume(updatedConfig$1.services[configKey], detectLocalRepo(app.root), opts, serviceName);
7290
7299
  }
7291
7300
  const newServices = {};
7292
7301
  for (const { app } of createdServices) {
@@ -7401,7 +7410,6 @@ async function addServiceFlow(existingConfig, opts) {
7401
7410
  },
7402
7411
  updated: (/* @__PURE__ */ new Date()).toISOString()
7403
7412
  };
7404
- await maybeConfigurePersistentVolume(updatedConfig.services[serviceKey], detection, opts, service.name);
7405
7413
  const newServiceEntry = updatedConfig.services[serviceKey];
7406
7414
  patchConfig((raw) => {
7407
7415
  raw.services[serviceKey] = newServiceEntry;
@@ -7435,7 +7443,7 @@ async function resolveOrgIfNeeded(nonInteractive) {
7435
7443
  }
7436
7444
  function registerDeploy(cli$1) {
7437
7445
  cli$1.command("deploy", {
7438
- description: "Fazer deploy do serviço",
7446
+ description: "Fazer deploy do serviço. Returns streamed progress events. The last event (type='result') contains: deploymentId, status (LIVE/BUILD_FAILED/DEPLOY_FAILED/FAILED/CANCELLED), urls, failReason, errorSummary, commitSha, branch, durationSeconds, dashboardUrl, and logs.",
7439
7447
  middleware: [requireAuth],
7440
7448
  options: z.object({
7441
7449
  all: z.boolean().optional().describe("Deploy todos os serviços do monorepo"),
@@ -7642,7 +7650,6 @@ async function cliDeployFlow(opts) {
7642
7650
  serviceName = project.services.find((s) => s.id === serviceId$1)?.name ?? "";
7643
7651
  }
7644
7652
  const selectedService = project.services.find((s) => s.id === serviceId$1);
7645
- const detection = detectLocalRepo();
7646
7653
  const detectedType = selectedService?.type?.toLowerCase() ?? "web";
7647
7654
  saveConfig({
7648
7655
  version: "1.0",
@@ -7662,7 +7669,7 @@ async function cliDeployFlow(opts) {
7662
7669
  info(`Arquivo ${getConfigFileName()} criado na raiz do projeto.`);
7663
7670
  const freshConfig = loadConfig();
7664
7671
  if (freshConfig) await provisionDatabases(freshConfig, { yes: opts.yes ?? false });
7665
- await triggerDeploy(serviceId$1, void 0, detection);
7672
+ await triggerDeploy(serviceId$1);
7666
7673
  return;
7667
7674
  }
7668
7675
  if (project && project.services.length === 0) {
@@ -7955,13 +7962,64 @@ async function pruneRemovedEntries(config, existingConfig, services, databases)
7955
7962
  return config;
7956
7963
  }
7957
7964
 
7965
+ //#endregion
7966
+ //#region src/commands/update.ts
7967
+ function registerUpdate(cli$1) {
7968
+ cli$1.command("update", {
7969
+ description: "Atualizar a CLI para a versão mais recente",
7970
+ output: z.object({
7971
+ updated: z.boolean(),
7972
+ version: z.string().nullable()
7973
+ }),
7974
+ async run() {
7975
+ const pm = detectPackageManager();
7976
+ if (!pm) {
7977
+ warn("Instalação de desenvolvimento detectada. Use 'pnpm cli:install' para atualizar.");
7978
+ return {
7979
+ updated: false,
7980
+ version: null
7981
+ };
7982
+ }
7983
+ const currentVersion = getCurrentVersion();
7984
+ info(`Versão atual: ${chalk.bold(currentVersion)}`);
7985
+ const latestVersion = await fetchLatestVersion();
7986
+ if (!latestVersion) {
7987
+ warn("Não foi possível verificar a versão mais recente.");
7988
+ return {
7989
+ updated: false,
7990
+ version: null
7991
+ };
7992
+ }
7993
+ if (latestVersion === currentVersion) {
7994
+ info(`Você já está na versão mais recente ${chalk.bold(currentVersion)}.`);
7995
+ return {
7996
+ updated: false,
7997
+ version: currentVersion
7998
+ };
7999
+ }
8000
+ if (!installVersion(pm, latestVersion)) {
8001
+ console.log(chalk.dim(` Execute manualmente: ${getInstallCommand(pm, latestVersion)}`));
8002
+ return {
8003
+ updated: false,
8004
+ version: null
8005
+ };
8006
+ }
8007
+ return {
8008
+ updated: true,
8009
+ version: latestVersion
8010
+ };
8011
+ }
8012
+ });
8013
+ }
8014
+
7958
8015
  //#endregion
7959
8016
  //#region src/index.ts
7960
8017
  if (process.argv.includes("--mcp")) process.env.VELOZ_MCP = "true";
7961
8018
  const cli = Cli.create("veloz", {
7962
- version: "0.0.0-beta.26",
8019
+ version: "0.0.0-beta.27",
7963
8020
  description: "CLI da plataforma Veloz — deploy rápido para o Brasil",
7964
- env: z.object({ VELOZ_ENV: z.string().optional().describe("Ambiente alvo (ex: preview, staging)") })
8021
+ env: z.object({ VELOZ_ENV: z.string().optional().describe("Ambiente alvo (ex: preview, staging)") }),
8022
+ mcp: { command: "npx -y onveloz --mcp" }
7965
8023
  });
7966
8024
  cli.use(async (c, next) => {
7967
8025
  const env = c.env.VELOZ_ENV;
@@ -8033,6 +8091,7 @@ registerDeploy(cli);
8033
8091
  registerUse(cli);
8034
8092
  registerWhoami(cli);
8035
8093
  registerPull(cli);
8094
+ registerUpdate(cli);
8036
8095
  cli.serve();
8037
8096
 
8038
8097
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "onveloz",
3
- "version": "0.0.0-beta.26",
3
+ "version": "0.0.0-beta.27",
4
4
  "description": "CLI da plataforma Veloz — deploy rápido para o Brasil",
5
5
  "keywords": [
6
6
  "brasil",