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
|
|
206
|
+
let phase = "finalization";
|
|
207
|
+
let inCrashLogBlock = false;
|
|
178
208
|
for (const line of generalStep.lines) {
|
|
179
|
-
|
|
180
|
-
if (
|
|
181
|
-
|
|
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
|
-
|
|
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 ??
|
|
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,
|
|
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
|
-
|
|
793
|
-
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
4108
|
-
|
|
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-
|
|
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.
|
|
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
|
-
|
|
6337
|
-
|
|
6338
|
-
|
|
6339
|
-
|
|
6340
|
-
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
const installCmd = getInstallCommand(pm,
|
|
6344
|
-
const spin = spinner(`Atualizando CLI ${chalk.dim(
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
7016
|
+
await promptEnvVars(service$1.id, serviceDetection.envVars.map((v) => v.key), config);
|
|
7005
7017
|
}
|
|
7006
|
-
config
|
|
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
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|