onveloz 0.0.0-beta.26 → 0.0.0-beta.28
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";
|
|
@@ -237,6 +237,192 @@ function resolveServiceSizeFromResources(cpu, memory) {
|
|
|
237
237
|
for (const [key, tier] of Object.entries(SERVICE_SIZES)) if (tier.cpu === cpu && tier.memory === memory) return key;
|
|
238
238
|
return null;
|
|
239
239
|
}
|
|
240
|
+
/** All known flag keys. Used for validation and UI rendering. */
|
|
241
|
+
const PROJECT_FLAG_DEFINITIONS = {
|
|
242
|
+
cloudflareSaas: {
|
|
243
|
+
label: "Cloudflare for SaaS",
|
|
244
|
+
description: "SSL de domínios customizados via Cloudflare (em vez de Let's Encrypt)"
|
|
245
|
+
},
|
|
246
|
+
cloudflareTunnel: {
|
|
247
|
+
label: "Cloudflare Tunnel",
|
|
248
|
+
description: "Roteamento de tráfego via túnel Cloudflare (em vez de IP direto). Domínios customizados apontam para proxy.onveloz.com"
|
|
249
|
+
},
|
|
250
|
+
buildkit: {
|
|
251
|
+
label: "BuildKit",
|
|
252
|
+
description: "Build de imagens via BuildKit no cluster (em vez de Depot externo). Mais controle e sem dependência de serviço externo."
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
const PROJECT_FLAG_KEYS = Object.keys(PROJECT_FLAG_DEFINITIONS);
|
|
256
|
+
/**
|
|
257
|
+
* Allowlist of user-configurable PostgreSQL parameters with validation bounds.
|
|
258
|
+
* PGTune-relevant params + commonly tuned settings.
|
|
259
|
+
*/
|
|
260
|
+
const PG_CONFIGURABLE_PARAMS = {
|
|
261
|
+
shared_buffers: {
|
|
262
|
+
type: "integer",
|
|
263
|
+
unit: "MB",
|
|
264
|
+
min: 16,
|
|
265
|
+
max: 16384,
|
|
266
|
+
description: "Memória dedicada para cache de dados do PostgreSQL",
|
|
267
|
+
requiresRestart: true
|
|
268
|
+
},
|
|
269
|
+
effective_cache_size: {
|
|
270
|
+
type: "integer",
|
|
271
|
+
unit: "MB",
|
|
272
|
+
min: 64,
|
|
273
|
+
max: 65536,
|
|
274
|
+
description: "Estimativa de memória disponível para cache do sistema operacional",
|
|
275
|
+
requiresRestart: false
|
|
276
|
+
},
|
|
277
|
+
work_mem: {
|
|
278
|
+
type: "integer",
|
|
279
|
+
unit: "MB",
|
|
280
|
+
min: 1,
|
|
281
|
+
max: 2048,
|
|
282
|
+
description: "Memória por operação de ordenação/hash",
|
|
283
|
+
requiresRestart: false
|
|
284
|
+
},
|
|
285
|
+
maintenance_work_mem: {
|
|
286
|
+
type: "integer",
|
|
287
|
+
unit: "MB",
|
|
288
|
+
min: 16,
|
|
289
|
+
max: 2048,
|
|
290
|
+
description: "Memória para operações de manutenção (VACUUM, CREATE INDEX)",
|
|
291
|
+
requiresRestart: false
|
|
292
|
+
},
|
|
293
|
+
wal_buffers: {
|
|
294
|
+
type: "integer",
|
|
295
|
+
unit: "MB",
|
|
296
|
+
min: 1,
|
|
297
|
+
max: 64,
|
|
298
|
+
description: "Memória para buffers de WAL",
|
|
299
|
+
requiresRestart: true
|
|
300
|
+
},
|
|
301
|
+
min_wal_size: {
|
|
302
|
+
type: "integer",
|
|
303
|
+
unit: "MB",
|
|
304
|
+
min: 32,
|
|
305
|
+
max: 4096,
|
|
306
|
+
description: "Tamanho mínimo de WAL antes de reciclar",
|
|
307
|
+
requiresRestart: false
|
|
308
|
+
},
|
|
309
|
+
max_wal_size: {
|
|
310
|
+
type: "integer",
|
|
311
|
+
unit: "MB",
|
|
312
|
+
min: 64,
|
|
313
|
+
max: 16384,
|
|
314
|
+
description: "Tamanho máximo de WAL antes de forçar checkpoint",
|
|
315
|
+
requiresRestart: false
|
|
316
|
+
},
|
|
317
|
+
max_connections: {
|
|
318
|
+
type: "integer",
|
|
319
|
+
min: 20,
|
|
320
|
+
max: 1e3,
|
|
321
|
+
description: "Número máximo de conexões simultâneas",
|
|
322
|
+
requiresRestart: true
|
|
323
|
+
},
|
|
324
|
+
random_page_cost: {
|
|
325
|
+
type: "real",
|
|
326
|
+
min: .1,
|
|
327
|
+
max: 10,
|
|
328
|
+
description: "Custo estimado de leitura aleatória de página (SSD ≈ 1.1, HDD ≈ 4.0)",
|
|
329
|
+
requiresRestart: false
|
|
330
|
+
},
|
|
331
|
+
effective_io_concurrency: {
|
|
332
|
+
type: "integer",
|
|
333
|
+
min: 0,
|
|
334
|
+
max: 1e3,
|
|
335
|
+
description: "Número de operações de I/O simultâneas (SSD ≈ 200)",
|
|
336
|
+
requiresRestart: false
|
|
337
|
+
},
|
|
338
|
+
max_worker_processes: {
|
|
339
|
+
type: "integer",
|
|
340
|
+
min: 1,
|
|
341
|
+
max: 128,
|
|
342
|
+
description: "Número máximo de processos worker",
|
|
343
|
+
requiresRestart: true
|
|
344
|
+
},
|
|
345
|
+
max_parallel_workers: {
|
|
346
|
+
type: "integer",
|
|
347
|
+
min: 0,
|
|
348
|
+
max: 128,
|
|
349
|
+
description: "Número máximo de workers paralelos",
|
|
350
|
+
requiresRestart: false
|
|
351
|
+
},
|
|
352
|
+
max_parallel_workers_per_gather: {
|
|
353
|
+
type: "integer",
|
|
354
|
+
min: 0,
|
|
355
|
+
max: 64,
|
|
356
|
+
description: "Número máximo de workers por operação Gather",
|
|
357
|
+
requiresRestart: false
|
|
358
|
+
},
|
|
359
|
+
max_parallel_maintenance_workers: {
|
|
360
|
+
type: "integer",
|
|
361
|
+
min: 0,
|
|
362
|
+
max: 64,
|
|
363
|
+
description: "Número máximo de workers para manutenção paralela",
|
|
364
|
+
requiresRestart: false
|
|
365
|
+
},
|
|
366
|
+
checkpoint_completion_target: {
|
|
367
|
+
type: "real",
|
|
368
|
+
min: .1,
|
|
369
|
+
max: 1,
|
|
370
|
+
description: "Fração do intervalo de checkpoint para completar a escrita",
|
|
371
|
+
requiresRestart: false
|
|
372
|
+
},
|
|
373
|
+
autovacuum_max_workers: {
|
|
374
|
+
type: "integer",
|
|
375
|
+
min: 1,
|
|
376
|
+
max: 16,
|
|
377
|
+
description: "Número máximo de processos autovacuum",
|
|
378
|
+
requiresRestart: true
|
|
379
|
+
},
|
|
380
|
+
autovacuum_naptime: {
|
|
381
|
+
type: "integer",
|
|
382
|
+
unit: "s",
|
|
383
|
+
min: 1,
|
|
384
|
+
max: 86400,
|
|
385
|
+
description: "Intervalo entre execuções do autovacuum",
|
|
386
|
+
requiresRestart: false
|
|
387
|
+
},
|
|
388
|
+
log_min_duration_statement: {
|
|
389
|
+
type: "integer",
|
|
390
|
+
unit: "ms",
|
|
391
|
+
min: -1,
|
|
392
|
+
max: 6e5,
|
|
393
|
+
description: "Log de queries mais lentas que N ms (-1 desabilita)",
|
|
394
|
+
requiresRestart: false
|
|
395
|
+
},
|
|
396
|
+
log_statement: {
|
|
397
|
+
type: "enum",
|
|
398
|
+
enumValues: [
|
|
399
|
+
"none",
|
|
400
|
+
"ddl",
|
|
401
|
+
"mod",
|
|
402
|
+
"all"
|
|
403
|
+
],
|
|
404
|
+
description: "Tipos de statements que são logados",
|
|
405
|
+
requiresRestart: false
|
|
406
|
+
},
|
|
407
|
+
statement_timeout: {
|
|
408
|
+
type: "integer",
|
|
409
|
+
unit: "ms",
|
|
410
|
+
min: 0,
|
|
411
|
+
max: 864e5,
|
|
412
|
+
description: "Timeout para execução de statements (0 desabilita)",
|
|
413
|
+
requiresRestart: false
|
|
414
|
+
},
|
|
415
|
+
idle_in_transaction_session_timeout: {
|
|
416
|
+
type: "integer",
|
|
417
|
+
unit: "ms",
|
|
418
|
+
min: 0,
|
|
419
|
+
max: 864e5,
|
|
420
|
+
description: "Timeout para sessões idle em transação (0 desabilita)",
|
|
421
|
+
requiresRestart: false
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
/** All user-configurable param keys. */
|
|
425
|
+
const PG_CONFIGURABLE_PARAM_KEYS = Object.keys(PG_CONFIGURABLE_PARAMS);
|
|
240
426
|
|
|
241
427
|
//#endregion
|
|
242
428
|
//#region ../../packages/config/veloz-config.ts
|
|
@@ -761,13 +947,17 @@ async function promptMultiSelect(question, options) {
|
|
|
761
947
|
}
|
|
762
948
|
return indices.map((i) => options[i].value);
|
|
763
949
|
}
|
|
950
|
+
function openBrowser(url) {
|
|
951
|
+
return new Promise((resolve$1) => {
|
|
952
|
+
const os = platform();
|
|
953
|
+
exec(os === "darwin" ? `open "${url}"` : os === "win32" ? `start "" "${url}"` : `xdg-open "${url}"`, (error) => {
|
|
954
|
+
resolve$1(!error);
|
|
955
|
+
});
|
|
956
|
+
});
|
|
957
|
+
}
|
|
764
958
|
|
|
765
959
|
//#endregion
|
|
766
960
|
//#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
961
|
const CLIENT_ID = "veloz-cli";
|
|
772
962
|
async function performLogin(apiUrl) {
|
|
773
963
|
const config = loadConfig$1();
|
|
@@ -789,10 +979,15 @@ async function performLogin(apiUrl) {
|
|
|
789
979
|
console.log();
|
|
790
980
|
console.log(chalk.white(` Código de verificação: ${chalk.bold.cyan(data.user_code)}`));
|
|
791
981
|
console.log();
|
|
792
|
-
|
|
793
|
-
|
|
982
|
+
if (!await openBrowser(verificationUrl)) {
|
|
983
|
+
info("Não foi possível abrir o navegador. Acesse manualmente:");
|
|
984
|
+
console.log();
|
|
985
|
+
console.log(` ${chalk.bold(verificationUrl)}`);
|
|
986
|
+
} else {
|
|
987
|
+
console.log(chalk.dim(" Se o navegador não abrir, acesse manualmente:"));
|
|
988
|
+
console.log(chalk.dim(` ${verificationUrl}`));
|
|
989
|
+
}
|
|
794
990
|
console.log();
|
|
795
|
-
openBrowser$1(verificationUrl);
|
|
796
991
|
const pollSpinner = spinner("Aguardando autorização no navegador...");
|
|
797
992
|
const token = await pollForToken(authClient, data.device_code, data.interval || 5);
|
|
798
993
|
if (!token) {
|
|
@@ -1179,7 +1374,7 @@ envGroup.command("list", {
|
|
|
1179
1374
|
serviceName: z.string(),
|
|
1180
1375
|
vars: z.array(z.object({
|
|
1181
1376
|
key: z.string(),
|
|
1182
|
-
|
|
1377
|
+
value: z.string(),
|
|
1183
1378
|
updatedAt: z.string()
|
|
1184
1379
|
}))
|
|
1185
1380
|
})),
|
|
@@ -1202,12 +1397,15 @@ envGroup.command("list", {
|
|
|
1202
1397
|
});
|
|
1203
1398
|
continue;
|
|
1204
1399
|
}
|
|
1205
|
-
for (const v of envVars)
|
|
1400
|
+
for (const v of envVars) {
|
|
1401
|
+
const displayValue = v.value ?? v.maskedValue;
|
|
1402
|
+
console.log(` ${chalk.bold(v.key)} ${chalk.dim(displayValue)} ${new Date(v.updatedAt).toLocaleDateString("pt-BR")}`);
|
|
1403
|
+
}
|
|
1206
1404
|
result.push({
|
|
1207
1405
|
serviceName: service.name,
|
|
1208
1406
|
vars: envVars.map((v) => ({
|
|
1209
1407
|
key: v.key,
|
|
1210
|
-
|
|
1408
|
+
value: v.value ?? v.maskedValue,
|
|
1211
1409
|
updatedAt: v.updatedAt instanceof Date ? v.updatedAt.toISOString() : String(v.updatedAt)
|
|
1212
1410
|
}))
|
|
1213
1411
|
});
|
|
@@ -1379,7 +1577,7 @@ envGroup.command("export", {
|
|
|
1379
1577
|
options: z.object({ service: z.string().optional().describe("Serviço alvo (chave ou nome)") }),
|
|
1380
1578
|
output: z.object({ vars: z.array(z.object({
|
|
1381
1579
|
key: z.string(),
|
|
1382
|
-
|
|
1580
|
+
value: z.string()
|
|
1383
1581
|
})) }),
|
|
1384
1582
|
async run(c) {
|
|
1385
1583
|
const serviceId = await resolveServiceId(c.options.service);
|
|
@@ -1392,19 +1590,21 @@ envGroup.command("export", {
|
|
|
1392
1590
|
info("Nenhuma variável de ambiente para exportar.");
|
|
1393
1591
|
return { vars: [] };
|
|
1394
1592
|
}
|
|
1395
|
-
const
|
|
1593
|
+
const isDecrypted = envVars.some((v) => v.value !== null && v.value !== void 0);
|
|
1594
|
+
const envContent = envVars.map((v) => `${v.key}=${v.value ?? v.maskedValue}`).join("\n");
|
|
1396
1595
|
if (c.args.arquivo) {
|
|
1397
1596
|
writeFileSync(resolve(process.cwd(), c.args.arquivo), envContent + "\n", "utf-8");
|
|
1398
1597
|
success(`Variáveis exportadas para ${chalk.bold(c.args.arquivo)}`);
|
|
1399
|
-
console.log(chalk.dim("Nota: Valores estão mascarados por segurança."));
|
|
1598
|
+
if (!isDecrypted) console.log(chalk.dim("Nota: Valores estão mascarados por segurança."));
|
|
1400
1599
|
} else {
|
|
1401
|
-
console.log(chalk.bold("\n# Variáveis de Ambiente
|
|
1600
|
+
if (isDecrypted) console.log(chalk.bold("\n# Variáveis de Ambiente\n"));
|
|
1601
|
+
else console.log(chalk.bold("\n# Variáveis de Ambiente (valores mascarados)\n"));
|
|
1402
1602
|
console.log(envContent);
|
|
1403
1603
|
console.log();
|
|
1404
1604
|
}
|
|
1405
1605
|
return { vars: envVars.map((v) => ({
|
|
1406
1606
|
key: v.key,
|
|
1407
|
-
|
|
1607
|
+
value: v.value ?? v.maskedValue
|
|
1408
1608
|
})) };
|
|
1409
1609
|
}
|
|
1410
1610
|
});
|
|
@@ -1784,6 +1984,54 @@ volumesGroup.command("sync", {
|
|
|
1784
1984
|
|
|
1785
1985
|
//#endregion
|
|
1786
1986
|
//#region src/commands/config.ts
|
|
1987
|
+
/**
|
|
1988
|
+
* Sync server-side config updates back to veloz.json.
|
|
1989
|
+
* Maps server field names to veloz.json service config paths.
|
|
1990
|
+
*/
|
|
1991
|
+
function syncConfigToVelozJson(serviceKey, updates) {
|
|
1992
|
+
try {
|
|
1993
|
+
if (!loadConfig()) return;
|
|
1994
|
+
patchConfig((raw) => {
|
|
1995
|
+
const svc = raw.services[serviceKey];
|
|
1996
|
+
if (!svc) return;
|
|
1997
|
+
for (const [field, value] of Object.entries(updates)) switch (field) {
|
|
1998
|
+
case "name":
|
|
1999
|
+
svc.name = value;
|
|
2000
|
+
break;
|
|
2001
|
+
case "buildCommand":
|
|
2002
|
+
if (!svc.build) svc.build = {};
|
|
2003
|
+
svc.build.command = value;
|
|
2004
|
+
break;
|
|
2005
|
+
case "startCommand":
|
|
2006
|
+
if (!svc.runtime) svc.runtime = {};
|
|
2007
|
+
svc.runtime.command = value;
|
|
2008
|
+
break;
|
|
2009
|
+
case "preStartCommand":
|
|
2010
|
+
if (!svc.runtime) svc.runtime = {};
|
|
2011
|
+
svc.runtime.preStartCommand = value;
|
|
2012
|
+
break;
|
|
2013
|
+
case "port":
|
|
2014
|
+
if (!svc.runtime) svc.runtime = {};
|
|
2015
|
+
svc.runtime.port = value;
|
|
2016
|
+
break;
|
|
2017
|
+
case "rootDirectory":
|
|
2018
|
+
svc.root = value ?? ".";
|
|
2019
|
+
break;
|
|
2020
|
+
case "instanceCount":
|
|
2021
|
+
if (!svc.resources) svc.resources = {};
|
|
2022
|
+
svc.resources.instances = value;
|
|
2023
|
+
break;
|
|
2024
|
+
case "size":
|
|
2025
|
+
if (!svc.resources) svc.resources = {};
|
|
2026
|
+
svc.resources.size = value;
|
|
2027
|
+
break;
|
|
2028
|
+
case "branch":
|
|
2029
|
+
svc.branch = value;
|
|
2030
|
+
break;
|
|
2031
|
+
}
|
|
2032
|
+
});
|
|
2033
|
+
} catch {}
|
|
2034
|
+
}
|
|
1787
2035
|
function formatValue(value) {
|
|
1788
2036
|
if (value === null || value === void 0 || value === "") return chalk.dim("—");
|
|
1789
2037
|
return chalk.cyan(String(value));
|
|
@@ -1874,7 +2122,8 @@ configGroup.command("set", {
|
|
|
1874
2122
|
instances: "i"
|
|
1875
2123
|
},
|
|
1876
2124
|
async run(c) {
|
|
1877
|
-
const
|
|
2125
|
+
const { key: serviceKey, service } = await resolveService(c.options.service);
|
|
2126
|
+
const serviceId = service.id;
|
|
1878
2127
|
const client = await getClient();
|
|
1879
2128
|
const updates = {};
|
|
1880
2129
|
if (c.options.name) updates.name = c.options.name;
|
|
@@ -1898,6 +2147,7 @@ configGroup.command("set", {
|
|
|
1898
2147
|
...updates
|
|
1899
2148
|
})
|
|
1900
2149
|
});
|
|
2150
|
+
syncConfigToVelozJson(serviceKey, updates);
|
|
1901
2151
|
success("Configurações atualizadas com sucesso!");
|
|
1902
2152
|
console.log(chalk.dim("\nValores atualizados:"));
|
|
1903
2153
|
for (const [key, value] of Object.entries(updates)) {
|
|
@@ -1914,7 +2164,8 @@ configGroup.command("edit", {
|
|
|
1914
2164
|
middleware: [requireAuth],
|
|
1915
2165
|
options: z.object({ service: z.string().optional().describe("Serviço alvo (chave ou nome)") }),
|
|
1916
2166
|
async run(c) {
|
|
1917
|
-
const
|
|
2167
|
+
const { key: serviceKey, service: resolved } = await resolveService(c.options.service);
|
|
2168
|
+
const serviceId = resolved.id;
|
|
1918
2169
|
const client = await getClient();
|
|
1919
2170
|
const svc = await withSpinner({
|
|
1920
2171
|
text: "Buscando configurações atuais...",
|
|
@@ -1955,6 +2206,7 @@ configGroup.command("edit", {
|
|
|
1955
2206
|
...updates
|
|
1956
2207
|
})
|
|
1957
2208
|
});
|
|
2209
|
+
syncConfigToVelozJson(serviceKey, updates);
|
|
1958
2210
|
success("Configurações atualizadas com sucesso!");
|
|
1959
2211
|
info("Execute 'veloz deploy' para aplicar as mudanças.");
|
|
1960
2212
|
return { updated: updates };
|
|
@@ -1973,7 +2225,8 @@ configGroup.command("reset", {
|
|
|
1973
2225
|
}),
|
|
1974
2226
|
async run(c) {
|
|
1975
2227
|
requireMcpConfirmation(c.options.userConfirmation, "resetar configurações para os padrões");
|
|
1976
|
-
const
|
|
2228
|
+
const { key: serviceKey, service } = await resolveService(c.options.service);
|
|
2229
|
+
const serviceId = service.id;
|
|
1977
2230
|
const client = await getClient();
|
|
1978
2231
|
const updates = {};
|
|
1979
2232
|
if (c.options.all) {
|
|
@@ -1997,6 +2250,7 @@ configGroup.command("reset", {
|
|
|
1997
2250
|
...updates
|
|
1998
2251
|
})
|
|
1999
2252
|
});
|
|
2253
|
+
syncConfigToVelozJson(serviceKey, updates);
|
|
2000
2254
|
success("Configurações resetadas para os padrões!");
|
|
2001
2255
|
info("Execute 'veloz deploy' para aplicar as mudanças.");
|
|
2002
2256
|
return { reset: Object.keys(updates) };
|
|
@@ -2707,6 +2961,116 @@ dbGroup.command("tunnel", {
|
|
|
2707
2961
|
});
|
|
2708
2962
|
}
|
|
2709
2963
|
});
|
|
2964
|
+
dbGroup.command("config", {
|
|
2965
|
+
description: "Ver ou alterar configuração do PostgreSQL",
|
|
2966
|
+
middleware: [requireAuth],
|
|
2967
|
+
args: z.object({ name: z.string().describe("Nome ou ID do banco de dados") }),
|
|
2968
|
+
options: z.object({
|
|
2969
|
+
set: z.string().optional().describe("Definir parâmetro (formato: chave=valor)"),
|
|
2970
|
+
reset: z.boolean().optional().describe("Resetar para configuração auto-tuned"),
|
|
2971
|
+
params: z.boolean().optional().describe("Listar parâmetros configuráveis"),
|
|
2972
|
+
userConfirmation: z.string().optional()
|
|
2973
|
+
}),
|
|
2974
|
+
output: z.object({
|
|
2975
|
+
params: z.array(z.object({
|
|
2976
|
+
name: z.string(),
|
|
2977
|
+
type: z.string(),
|
|
2978
|
+
unit: z.string().optional(),
|
|
2979
|
+
requiresRestart: z.boolean(),
|
|
2980
|
+
description: z.string()
|
|
2981
|
+
})).optional(),
|
|
2982
|
+
effective: z.record(z.string(), z.string()).optional(),
|
|
2983
|
+
userOverrides: z.record(z.string(), z.string()).optional(),
|
|
2984
|
+
updated: z.boolean().optional(),
|
|
2985
|
+
key: z.string().optional(),
|
|
2986
|
+
value: z.string().optional(),
|
|
2987
|
+
requiresRestart: z.boolean().optional(),
|
|
2988
|
+
reset: z.boolean().optional()
|
|
2989
|
+
}),
|
|
2990
|
+
async run(c) {
|
|
2991
|
+
const db = await resolveDatabaseByName(getProjectId(), c.args.name);
|
|
2992
|
+
const client = await getClient();
|
|
2993
|
+
if (c.options.params) {
|
|
2994
|
+
log(chalk.bold("\n Parâmetros configuráveis:\n"));
|
|
2995
|
+
log(` ${chalk.bold("Parâmetro".padEnd(40))}${chalk.bold("Tipo".padEnd(12))}${chalk.bold("Reinício?")}`);
|
|
2996
|
+
log(chalk.dim(" " + "─".repeat(65)));
|
|
2997
|
+
const params = Object.entries(PG_CONFIGURABLE_PARAMS).map(([key, def]) => {
|
|
2998
|
+
const typeLabel = def.unit ? `${def.type} (${def.unit})` : def.type;
|
|
2999
|
+
const restartLabel = def.requiresRestart ? chalk.yellow("Sim") : chalk.green("Não");
|
|
3000
|
+
log(` ${key.padEnd(40)}${typeLabel.padEnd(12)}${restartLabel}`);
|
|
3001
|
+
return {
|
|
3002
|
+
name: key,
|
|
3003
|
+
type: def.type,
|
|
3004
|
+
unit: def.unit,
|
|
3005
|
+
requiresRestart: def.requiresRestart,
|
|
3006
|
+
description: def.description
|
|
3007
|
+
};
|
|
3008
|
+
});
|
|
3009
|
+
log("");
|
|
3010
|
+
return { params };
|
|
3011
|
+
}
|
|
3012
|
+
if (db.engine !== "postgresql") throw new Error("Configuração personalizada só é suportada para PostgreSQL.");
|
|
3013
|
+
if (c.options.reset) {
|
|
3014
|
+
requireMcpConfirmation(c.options.userConfirmation, `resetar configuração do banco de dados "${db.name}" para auto-tuned`);
|
|
3015
|
+
if (!c.options.userConfirmation && isInteractive()) {
|
|
3016
|
+
if (!await promptConfirm(`Resetar toda a configuração personalizada de "${db.name}" para auto-tuned?`, false)) {
|
|
3017
|
+
info("Operação cancelada.");
|
|
3018
|
+
return {};
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
const result = await withSpinner({
|
|
3022
|
+
text: "Resetando configuração...",
|
|
3023
|
+
fn: () => client.databases.resetConfig({ serviceId: db.id })
|
|
3024
|
+
});
|
|
3025
|
+
success("Configuração resetada para auto-tuned.");
|
|
3026
|
+
info("O banco de dados será reiniciado para aplicar as alterações.");
|
|
3027
|
+
return {
|
|
3028
|
+
reset: true,
|
|
3029
|
+
effective: result.effective
|
|
3030
|
+
};
|
|
3031
|
+
}
|
|
3032
|
+
if (c.options.set) {
|
|
3033
|
+
const eqIndex = c.options.set.indexOf("=");
|
|
3034
|
+
if (eqIndex === -1) throw new Error("Formato inválido. Use: --set chave=valor (ex: --set shared_buffers=512MB)");
|
|
3035
|
+
const key = c.options.set.slice(0, eqIndex).trim();
|
|
3036
|
+
const value = c.options.set.slice(eqIndex + 1).trim();
|
|
3037
|
+
const result = await withSpinner({
|
|
3038
|
+
text: `Atualizando ${key}...`,
|
|
3039
|
+
fn: () => client.databases.updateConfig({
|
|
3040
|
+
serviceId: db.id,
|
|
3041
|
+
config: { [key]: value }
|
|
3042
|
+
})
|
|
3043
|
+
});
|
|
3044
|
+
success(`${key} = ${value}`);
|
|
3045
|
+
if (result.requiresRestart) warn("Esta alteração requer reinício do banco de dados para ter efeito.");
|
|
3046
|
+
return {
|
|
3047
|
+
updated: true,
|
|
3048
|
+
key,
|
|
3049
|
+
value,
|
|
3050
|
+
requiresRestart: result.requiresRestart
|
|
3051
|
+
};
|
|
3052
|
+
}
|
|
3053
|
+
const config = await withSpinner({
|
|
3054
|
+
text: "Carregando configuração...",
|
|
3055
|
+
fn: () => client.databases.getConfig({ serviceId: db.id })
|
|
3056
|
+
});
|
|
3057
|
+
log(chalk.bold(`\n Configuração PostgreSQL — ${db.name}\n`));
|
|
3058
|
+
log(` ${chalk.bold("Parâmetro".padEnd(40))}${chalk.bold("Valor".padEnd(20))}${chalk.bold("Origem")}`);
|
|
3059
|
+
log(chalk.dim(" " + "─".repeat(70)));
|
|
3060
|
+
for (const [key, value] of Object.entries(config.effective)) {
|
|
3061
|
+
const isOverride = key in config.userOverrides;
|
|
3062
|
+
const originLabel = isOverride ? chalk.cyan("personalizado") : chalk.dim("auto-tuned");
|
|
3063
|
+
const valueLabel = isOverride ? chalk.cyan(value) : value;
|
|
3064
|
+
log(` ${key.padEnd(40)}${String(valueLabel).padEnd(20)}${originLabel}`);
|
|
3065
|
+
}
|
|
3066
|
+
if (Object.keys(config.userOverrides).length > 0) log(chalk.dim(`\n ${Object.keys(config.userOverrides).length} parâmetro(s) personalizado(s)`));
|
|
3067
|
+
log("");
|
|
3068
|
+
return {
|
|
3069
|
+
effective: config.effective,
|
|
3070
|
+
userOverrides: config.userOverrides
|
|
3071
|
+
};
|
|
3072
|
+
}
|
|
3073
|
+
});
|
|
2710
3074
|
|
|
2711
3075
|
//#endregion
|
|
2712
3076
|
//#region src/commands/template.ts
|
|
@@ -3985,12 +4349,6 @@ logsGroup.command("query-help", {
|
|
|
3985
4349
|
|
|
3986
4350
|
//#endregion
|
|
3987
4351
|
//#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
4352
|
const githubGroup = Cli.create("github", { description: "Gerenciar integração com GitHub" });
|
|
3995
4353
|
githubGroup.command("setup", {
|
|
3996
4354
|
description: "Configurar GitHub App para deploy automático via webhook",
|
|
@@ -4102,10 +4460,18 @@ githubGroup.command("setup", {
|
|
|
4102
4460
|
projectId
|
|
4103
4461
|
};
|
|
4104
4462
|
}
|
|
4105
|
-
openBrowser(installation.installUrl);
|
|
4463
|
+
const opened = await openBrowser(installation.installUrl);
|
|
4106
4464
|
console.log();
|
|
4107
|
-
|
|
4108
|
-
|
|
4465
|
+
if (opened) {
|
|
4466
|
+
info("Aguardando instalação do GitHub App...");
|
|
4467
|
+
console.log(chalk.dim(" Instale o app no navegador e volte aqui."));
|
|
4468
|
+
} else {
|
|
4469
|
+
info("Não foi possível abrir o navegador. Abra o link abaixo manualmente:");
|
|
4470
|
+
console.log();
|
|
4471
|
+
console.log(` ${chalk.bold(installation.installUrl)}`);
|
|
4472
|
+
console.log();
|
|
4473
|
+
console.log(chalk.dim(" Instale o app e volte aqui."));
|
|
4474
|
+
}
|
|
4109
4475
|
console.log();
|
|
4110
4476
|
const pollSpinner = spinner("Aguardando instalação...");
|
|
4111
4477
|
const maxAttempts = 60;
|
|
@@ -4258,6 +4624,25 @@ const TERMINAL_STATUSES = new Set([
|
|
|
4258
4624
|
|
|
4259
4625
|
//#endregion
|
|
4260
4626
|
//#region src/lib/deploy-stream.ts
|
|
4627
|
+
const DASHBOARD_BASE = process.env.VELOZ_WEB_URL || "https://app.onveloz.com";
|
|
4628
|
+
/**
|
|
4629
|
+
* Fetch deployment details from the API and enrich a DeployStreamResult
|
|
4630
|
+
* with metadata useful for agents (failReason, commit info, duration, dashboard link).
|
|
4631
|
+
*/
|
|
4632
|
+
async function enrichResult(client, result) {
|
|
4633
|
+
try {
|
|
4634
|
+
const d = await client.deployments.get({ deploymentId: result.deploymentId });
|
|
4635
|
+
const service = await client.services.get({ serviceId: d.serviceId }).catch(() => null);
|
|
4636
|
+
const projectId = service ? service.projectId : null;
|
|
4637
|
+
result.failReason = d.failReason ?? null;
|
|
4638
|
+
result.errorSummary = d.errorSummary ?? null;
|
|
4639
|
+
result.commitSha = d.commitSha ?? null;
|
|
4640
|
+
result.branch = d.branch ?? null;
|
|
4641
|
+
if (d.startedAt && d.finishedAt) result.durationSeconds = Math.round((new Date(d.finishedAt).getTime() - new Date(d.startedAt).getTime()) / 1e3);
|
|
4642
|
+
if (projectId) result.dashboardUrl = `${DASHBOARD_BASE}/projetos/${projectId}/servicos/${d.serviceId}/deploys/${d.id}`;
|
|
4643
|
+
} catch {}
|
|
4644
|
+
return result;
|
|
4645
|
+
}
|
|
4261
4646
|
async function fetchDeployUrls$1(client, serviceId) {
|
|
4262
4647
|
try {
|
|
4263
4648
|
return (await client.domains.list({ serviceId })).map((d) => `https://${d.domain}`);
|
|
@@ -4397,7 +4782,7 @@ async function streamDeploymentLogs(deploymentId, serviceId, serviceName) {
|
|
|
4397
4782
|
const isTTY = !mcp && process.stdout.isTTY;
|
|
4398
4783
|
const isGHA = !mcp && process.env.GITHUB_ACTIONS === "true";
|
|
4399
4784
|
if (isTTY && !isVerbose) {
|
|
4400
|
-
const { renderDeployTUI } = await import("./deploy-tui-
|
|
4785
|
+
const { renderDeployTUI } = await import("./deploy-tui-D_pzq4Tu.mjs");
|
|
4401
4786
|
return renderDeployTUI(deploymentId, serviceId, serviceName);
|
|
4402
4787
|
}
|
|
4403
4788
|
const allLogLines = [];
|
|
@@ -4470,13 +4855,13 @@ async function streamDeploymentLogs(deploymentId, serviceId, serviceName) {
|
|
|
4470
4855
|
}
|
|
4471
4856
|
if (!mcp) process.exit(1);
|
|
4472
4857
|
}
|
|
4473
|
-
return {
|
|
4858
|
+
return enrichResult(client, {
|
|
4474
4859
|
deploymentId,
|
|
4475
4860
|
status: finalStatus,
|
|
4476
4861
|
logs: allLogLines.filter((l) => l.trim()),
|
|
4477
4862
|
urls,
|
|
4478
4863
|
serviceName
|
|
4479
|
-
};
|
|
4864
|
+
});
|
|
4480
4865
|
}
|
|
4481
4866
|
|
|
4482
4867
|
//#endregion
|
|
@@ -5017,6 +5402,18 @@ function extractEnvVars(files) {
|
|
|
5017
5402
|
}
|
|
5018
5403
|
return vars;
|
|
5019
5404
|
}
|
|
5405
|
+
/**
|
|
5406
|
+
* Extract the major Node.js version from a package.json string's `engines.node` field.
|
|
5407
|
+
* Handles ranges like `>=22`, `^20`, `~18.0`, `22.x`, `20`, `>=22.0.0`.
|
|
5408
|
+
* Returns the major version string (e.g. "22") or undefined if not parseable.
|
|
5409
|
+
*/
|
|
5410
|
+
function detectNodeVersion(pkgJsonStr) {
|
|
5411
|
+
const pkg = safeParsePkg(pkgJsonStr);
|
|
5412
|
+
if (!pkg?.engines?.node) return void 0;
|
|
5413
|
+
const match = pkg.engines.node.trim().match(/(\d+)/);
|
|
5414
|
+
if (!match) return void 0;
|
|
5415
|
+
return match[1];
|
|
5416
|
+
}
|
|
5020
5417
|
function analyzeRepo(files) {
|
|
5021
5418
|
const packageManager = detectPackageManager$1(files);
|
|
5022
5419
|
const envVars = extractEnvVars(files);
|
|
@@ -5034,6 +5431,7 @@ function analyzeRepo(files) {
|
|
|
5034
5431
|
const appPath = filePath.replace("/package.json", "");
|
|
5035
5432
|
const appName = nested.name || appPath.split("/").pop() || appPath;
|
|
5036
5433
|
let appFramework = detectFramework(content, packageManager);
|
|
5434
|
+
if (!appFramework && !nested.scripts?.start) continue;
|
|
5037
5435
|
if (appFramework && rootPkg) {
|
|
5038
5436
|
if (rootPkg.scripts?.[`build:${appName}`]) appFramework = {
|
|
5039
5437
|
...appFramework,
|
|
@@ -5051,7 +5449,8 @@ function analyzeRepo(files) {
|
|
|
5051
5449
|
name: appName,
|
|
5052
5450
|
path: appPath,
|
|
5053
5451
|
framework: appFramework,
|
|
5054
|
-
usesNodeFs: sourceEntries.some(([srcPath, fileContent]) => srcPath.startsWith(`${appPath}/`) && usesNodeFs(fileContent))
|
|
5452
|
+
usesNodeFs: sourceEntries.some(([srcPath, fileContent]) => srcPath.startsWith(`${appPath}/`) && usesNodeFs(fileContent)),
|
|
5453
|
+
nodeVersion: detectNodeVersion(content)
|
|
5055
5454
|
});
|
|
5056
5455
|
}
|
|
5057
5456
|
return {
|
|
@@ -5060,7 +5459,8 @@ function analyzeRepo(files) {
|
|
|
5060
5459
|
envVars,
|
|
5061
5460
|
isMonorepo,
|
|
5062
5461
|
monorepoApps,
|
|
5063
|
-
usesNodeFs: sourceEntries.some(([, content]) => usesNodeFs(content))
|
|
5462
|
+
usesNodeFs: sourceEntries.some(([, content]) => usesNodeFs(content)),
|
|
5463
|
+
nodeVersion: rootPkgContent ? detectNodeVersion(rootPkgContent) : void 0
|
|
5064
5464
|
};
|
|
5065
5465
|
}
|
|
5066
5466
|
|
|
@@ -5283,7 +5683,7 @@ const LOGO_LINES = [
|
|
|
5283
5683
|
];
|
|
5284
5684
|
const BRAND_COLOR = "#FF4D00";
|
|
5285
5685
|
function getVersion() {
|
|
5286
|
-
return "0.0.0-beta.
|
|
5686
|
+
return "0.0.0-beta.28";
|
|
5287
5687
|
}
|
|
5288
5688
|
function printBanner(subtitle) {
|
|
5289
5689
|
const version = getVersion();
|
|
@@ -6011,32 +6411,6 @@ function checkDockerfileLockFiles(basePath) {
|
|
|
6011
6411
|
* and public/ and does not simplify the setup on this platform.
|
|
6012
6412
|
*/
|
|
6013
6413
|
/**
|
|
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
6414
|
* Detect if the app hardcodes a port that doesn't match the configured port.
|
|
6041
6415
|
* Common issue: app listens on 8080 but service port is 3000.
|
|
6042
6416
|
*/
|
|
@@ -6270,7 +6644,6 @@ function runPreDeployChecks(basePath = ".") {
|
|
|
6270
6644
|
const checks = [
|
|
6271
6645
|
checkNitroPreset,
|
|
6272
6646
|
checkDockerfileLockFiles,
|
|
6273
|
-
checkPrismaGenerate,
|
|
6274
6647
|
checkPortMismatch,
|
|
6275
6648
|
checkEnvFileCommitted,
|
|
6276
6649
|
checkSvelteKitAdapter,
|
|
@@ -6333,24 +6706,50 @@ async function fetchLatestVersion() {
|
|
|
6333
6706
|
return null;
|
|
6334
6707
|
}
|
|
6335
6708
|
}
|
|
6336
|
-
|
|
6337
|
-
|
|
6338
|
-
|
|
6339
|
-
|
|
6340
|
-
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
const installCmd = getInstallCommand(pm,
|
|
6344
|
-
const spin = spinner(`Atualizando CLI ${chalk.dim(
|
|
6709
|
+
function getCurrentVersion() {
|
|
6710
|
+
return "0.0.0-beta.28";
|
|
6711
|
+
}
|
|
6712
|
+
/**
|
|
6713
|
+
* Install a specific CLI version. Returns true on success.
|
|
6714
|
+
*/
|
|
6715
|
+
function installVersion(pm, version) {
|
|
6716
|
+
const installCmd = getInstallCommand(pm, version);
|
|
6717
|
+
const spin = spinner(`Atualizando CLI ${chalk.dim(getCurrentVersion())} → ${chalk.bold(version)}...`);
|
|
6345
6718
|
try {
|
|
6346
6719
|
execSync(installCmd, { stdio: "ignore" });
|
|
6347
6720
|
spin.stop();
|
|
6348
|
-
console.log(chalk.green(`\n✓ CLI atualizada: ${chalk.bold(
|
|
6721
|
+
console.log(chalk.green(`\n✓ CLI atualizada: ${chalk.bold(version)}\n`));
|
|
6722
|
+
return true;
|
|
6349
6723
|
} catch {
|
|
6350
6724
|
spin.stop();
|
|
6351
6725
|
console.log(chalk.yellow(`\n⚠ Não foi possível atualizar automaticamente. Execute manualmente:\n ${installCmd}\n`));
|
|
6726
|
+
return false;
|
|
6352
6727
|
}
|
|
6353
6728
|
}
|
|
6729
|
+
/**
|
|
6730
|
+
* Re-exec the CLI with the same args so the new binary runs.
|
|
6731
|
+
* Used after auto-update during `veloz deploy`.
|
|
6732
|
+
*/
|
|
6733
|
+
function reExec() {
|
|
6734
|
+
execSync(`veloz ${process.argv.slice(2).join(" ")}`, {
|
|
6735
|
+
stdio: "inherit",
|
|
6736
|
+
env: {
|
|
6737
|
+
...process.env,
|
|
6738
|
+
VELOZ_SKIP_UPDATE: "true"
|
|
6739
|
+
}
|
|
6740
|
+
});
|
|
6741
|
+
process.exit(0);
|
|
6742
|
+
}
|
|
6743
|
+
async function autoUpdate() {
|
|
6744
|
+
if (process.env.VELOZ_MCP === "true") return;
|
|
6745
|
+
if (process.env.VELOZ_SKIP_UPDATE === "true") return;
|
|
6746
|
+
const pm = detectPackageManager();
|
|
6747
|
+
if (!pm) return;
|
|
6748
|
+
const currentVersion = getCurrentVersion();
|
|
6749
|
+
const latestVersion = await fetchLatestVersion();
|
|
6750
|
+
if (!latestVersion || latestVersion === currentVersion) return;
|
|
6751
|
+
if (installVersion(pm, latestVersion)) reExec();
|
|
6752
|
+
}
|
|
6354
6753
|
|
|
6355
6754
|
//#endregion
|
|
6356
6755
|
//#region src/lib/deploy-databases.ts
|
|
@@ -6595,7 +6994,6 @@ function prepareServicesForDeploy(services) {
|
|
|
6595
6994
|
}
|
|
6596
6995
|
return services.map((svc) => {
|
|
6597
6996
|
const serviceConf = resolveServiceConf(velozConfig, svc.serviceId);
|
|
6598
|
-
warnIfEphemeralFsDetected(detectLocalRepo(serviceConf?.rootDirectory || "."), serviceConf, svc.serviceName);
|
|
6599
6997
|
return {
|
|
6600
6998
|
serviceId: svc.serviceId,
|
|
6601
6999
|
serviceName: svc.serviceName,
|
|
@@ -6604,13 +7002,12 @@ function prepareServicesForDeploy(services) {
|
|
|
6604
7002
|
};
|
|
6605
7003
|
});
|
|
6606
7004
|
}
|
|
6607
|
-
async function triggerDeploy(serviceId, serviceName
|
|
7005
|
+
async function triggerDeploy(serviceId, serviceName) {
|
|
6608
7006
|
const sizeInBytes = await calculateDirectorySize(process.cwd());
|
|
6609
7007
|
const sizeMB = Math.round(sizeInBytes / (1024 * 1024) * 10) / 10;
|
|
6610
7008
|
const client = await getClient();
|
|
6611
7009
|
const velozConfig = loadConfig();
|
|
6612
7010
|
const serviceConf = resolveServiceConf(velozConfig, serviceId);
|
|
6613
|
-
warnIfEphemeralFsDetected(preDetection ?? detectLocalRepo(), serviceConf, serviceName ?? void 0);
|
|
6614
7011
|
const warnings = runPreDeployChecks(serviceConf?.rootDirectory || ".");
|
|
6615
7012
|
if (warnings.length > 0) printDeployWarnings(warnings);
|
|
6616
7013
|
const spinUpload = spinner(serviceName ? `Fazendo upload ${chalk.bold(serviceName)}...` : "Fazendo upload do código...");
|
|
@@ -6637,25 +7034,6 @@ async function triggerDeploy(serviceId, serviceName, preDetection) {
|
|
|
6637
7034
|
untrackDeployment(deployment.id);
|
|
6638
7035
|
}
|
|
6639
7036
|
}
|
|
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
7037
|
async function findServicesFromConfig() {
|
|
6660
7038
|
const config = loadConfig();
|
|
6661
7039
|
if (!config) return [];
|
|
@@ -6907,7 +7285,8 @@ async function createServiceFlow(projectId, projectName, repoName, opts = {}) {
|
|
|
6907
7285
|
buildCommand: a.framework?.buildCommand ?? null,
|
|
6908
7286
|
startCommand: a.framework?.startCommand ?? null,
|
|
6909
7287
|
port: a.framework?.port ?? 3e3,
|
|
6910
|
-
usesNodeFs: a.usesNodeFs
|
|
7288
|
+
usesNodeFs: a.usesNodeFs,
|
|
7289
|
+
nodeVersion: a.nodeVersion
|
|
6911
7290
|
}));
|
|
6912
7291
|
let selectedApps;
|
|
6913
7292
|
if (opts.yes) if (opts.app) {
|
|
@@ -6951,7 +7330,7 @@ async function createServiceFlow(projectId, projectName, repoName, opts = {}) {
|
|
|
6951
7330
|
if (newPort) app.port = parseInt(newPort, 10) || app.port;
|
|
6952
7331
|
}
|
|
6953
7332
|
}
|
|
6954
|
-
let config
|
|
7333
|
+
let config = {
|
|
6955
7334
|
version: "1.0",
|
|
6956
7335
|
project: {
|
|
6957
7336
|
id: projectId,
|
|
@@ -6974,7 +7353,8 @@ async function createServiceFlow(projectId, projectName, repoName, opts = {}) {
|
|
|
6974
7353
|
buildCommand: app.buildCommand ?? void 0,
|
|
6975
7354
|
startCommand: app.startCommand ?? void 0,
|
|
6976
7355
|
port: app.port ?? 3e3,
|
|
6977
|
-
framework: app.framework ?? void 0
|
|
7356
|
+
framework: app.framework ?? void 0,
|
|
7357
|
+
nodeVersion: app.nodeVersion
|
|
6978
7358
|
}))
|
|
6979
7359
|
});
|
|
6980
7360
|
success(`Serviço criado: ${chalk.bold(service$1.name)}`);
|
|
@@ -6982,7 +7362,7 @@ async function createServiceFlow(projectId, projectName, repoName, opts = {}) {
|
|
|
6982
7362
|
service: service$1,
|
|
6983
7363
|
app
|
|
6984
7364
|
});
|
|
6985
|
-
config
|
|
7365
|
+
config.services[app.root] = {
|
|
6986
7366
|
id: service$1.id,
|
|
6987
7367
|
name: service$1.name,
|
|
6988
7368
|
type: "web",
|
|
@@ -6994,21 +7374,20 @@ async function createServiceFlow(projectId, projectName, repoName, opts = {}) {
|
|
|
6994
7374
|
port: app.port ?? 3e3
|
|
6995
7375
|
}
|
|
6996
7376
|
};
|
|
6997
|
-
await maybeConfigurePersistentVolume(config$1.services[app.root], detectLocalRepo(app.root), opts, app.name);
|
|
6998
7377
|
}
|
|
6999
|
-
saveConfig(config
|
|
7378
|
+
saveConfig(config);
|
|
7000
7379
|
info(`Arquivo ${getConfigFileName()} criado na raiz do projeto.`);
|
|
7001
7380
|
if (!opts.yes) for (const { service: service$1, app } of createdServices) {
|
|
7002
7381
|
console.log(chalk.cyan(`\n── Configurando variáveis: ${app.name} ──\n`));
|
|
7003
7382
|
const serviceDetection = detectLocalRepo(app.root);
|
|
7004
|
-
await promptEnvVars(service$1.id, serviceDetection.envVars.map((v) => v.key), config
|
|
7383
|
+
await promptEnvVars(service$1.id, serviceDetection.envVars.map((v) => v.key), config);
|
|
7005
7384
|
}
|
|
7006
|
-
config
|
|
7385
|
+
config = await provisionDatabases(config, { yes: opts.yes ?? false });
|
|
7007
7386
|
await deployServices(createdServices.map(({ service: service$1, app }) => ({
|
|
7008
7387
|
serviceId: service$1.id,
|
|
7009
7388
|
serviceName: app.name,
|
|
7010
7389
|
projectId,
|
|
7011
|
-
serviceConfig: resolveServiceConf(config
|
|
7390
|
+
serviceConfig: resolveServiceConf(config, service$1.id)
|
|
7012
7391
|
})), {
|
|
7013
7392
|
projectRoot: process.cwd(),
|
|
7014
7393
|
output: createDeployOutput()
|
|
@@ -7067,12 +7446,13 @@ async function createServiceFlow(projectId, projectName, repoName, opts = {}) {
|
|
|
7067
7446
|
buildCommand: settings.buildCommand ?? void 0,
|
|
7068
7447
|
startCommand: settings.startCommand ?? void 0,
|
|
7069
7448
|
port: settings.port,
|
|
7070
|
-
framework: settings.framework ?? void 0
|
|
7449
|
+
framework: settings.framework ?? void 0,
|
|
7450
|
+
nodeVersion: detection.nodeVersion
|
|
7071
7451
|
}))
|
|
7072
7452
|
});
|
|
7073
7453
|
success(`Serviço criado: ${chalk.bold(service.name)}`);
|
|
7074
7454
|
if (!opts.yes) await promptEnvVars(service.id, detection.envVars.map((v) => v.key), null);
|
|
7075
|
-
|
|
7455
|
+
saveConfig({
|
|
7076
7456
|
version: "1.0",
|
|
7077
7457
|
project: {
|
|
7078
7458
|
id: projectId,
|
|
@@ -7094,9 +7474,7 @@ async function createServiceFlow(projectId, projectName, repoName, opts = {}) {
|
|
|
7094
7474
|
}
|
|
7095
7475
|
} },
|
|
7096
7476
|
created: (/* @__PURE__ */ new Date()).toISOString()
|
|
7097
|
-
};
|
|
7098
|
-
await maybeConfigurePersistentVolume(config.services.main, detection, opts, service.name);
|
|
7099
|
-
saveConfig(config);
|
|
7477
|
+
});
|
|
7100
7478
|
info(`Arquivo ${getConfigFileName()} criado na raiz do projeto.`);
|
|
7101
7479
|
return service.id;
|
|
7102
7480
|
}
|
|
@@ -7187,7 +7565,8 @@ async function addServiceFlow(existingConfig, opts) {
|
|
|
7187
7565
|
buildCommand: a.framework?.buildCommand ?? null,
|
|
7188
7566
|
startCommand: a.framework?.startCommand ?? null,
|
|
7189
7567
|
port: a.framework?.port ?? 3e3,
|
|
7190
|
-
usesNodeFs: a.usesNodeFs
|
|
7568
|
+
usesNodeFs: a.usesNodeFs,
|
|
7569
|
+
nodeVersion: a.nodeVersion
|
|
7191
7570
|
}));
|
|
7192
7571
|
const availableApps = allApps.filter((a) => !existingRoots.has(a.root));
|
|
7193
7572
|
if (availableApps.length === 0) {
|
|
@@ -7261,7 +7640,8 @@ async function addServiceFlow(existingConfig, opts) {
|
|
|
7261
7640
|
buildCommand: hasDockerfile ? void 0 : app.buildCommand ?? void 0,
|
|
7262
7641
|
startCommand: hasDockerfile ? void 0 : app.startCommand ?? void 0,
|
|
7263
7642
|
port: app.port ?? 3e3,
|
|
7264
|
-
framework: hasDockerfile ? "docker" : app.framework ?? void 0
|
|
7643
|
+
framework: hasDockerfile ? "docker" : app.framework ?? void 0,
|
|
7644
|
+
nodeVersion: hasDockerfile ? void 0 : app.nodeVersion
|
|
7265
7645
|
}))
|
|
7266
7646
|
});
|
|
7267
7647
|
success(`Serviço criado: ${chalk.bold(service$1.name)}`);
|
|
@@ -7286,7 +7666,6 @@ async function addServiceFlow(existingConfig, opts) {
|
|
|
7286
7666
|
port: app.port ?? 3e3
|
|
7287
7667
|
}
|
|
7288
7668
|
};
|
|
7289
|
-
await maybeConfigurePersistentVolume(updatedConfig$1.services[configKey], detectLocalRepo(app.root), opts, serviceName);
|
|
7290
7669
|
}
|
|
7291
7670
|
const newServices = {};
|
|
7292
7671
|
for (const { app } of createdServices) {
|
|
@@ -7373,7 +7752,8 @@ async function addServiceFlow(existingConfig, opts) {
|
|
|
7373
7752
|
buildCommand: settings.buildCommand ?? void 0,
|
|
7374
7753
|
startCommand: settings.startCommand ?? void 0,
|
|
7375
7754
|
port: settings.port,
|
|
7376
|
-
framework: settings.framework ?? void 0
|
|
7755
|
+
framework: settings.framework ?? void 0,
|
|
7756
|
+
nodeVersion: detection.nodeVersion
|
|
7377
7757
|
}))
|
|
7378
7758
|
});
|
|
7379
7759
|
success(`Serviço criado: ${chalk.bold(service.name)}`);
|
|
@@ -7401,7 +7781,6 @@ async function addServiceFlow(existingConfig, opts) {
|
|
|
7401
7781
|
},
|
|
7402
7782
|
updated: (/* @__PURE__ */ new Date()).toISOString()
|
|
7403
7783
|
};
|
|
7404
|
-
await maybeConfigurePersistentVolume(updatedConfig.services[serviceKey], detection, opts, service.name);
|
|
7405
7784
|
const newServiceEntry = updatedConfig.services[serviceKey];
|
|
7406
7785
|
patchConfig((raw) => {
|
|
7407
7786
|
raw.services[serviceKey] = newServiceEntry;
|
|
@@ -7435,7 +7814,7 @@ async function resolveOrgIfNeeded(nonInteractive) {
|
|
|
7435
7814
|
}
|
|
7436
7815
|
function registerDeploy(cli$1) {
|
|
7437
7816
|
cli$1.command("deploy", {
|
|
7438
|
-
description: "Fazer deploy do serviço",
|
|
7817
|
+
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
7818
|
middleware: [requireAuth],
|
|
7440
7819
|
options: z.object({
|
|
7441
7820
|
all: z.boolean().optional().describe("Deploy todos os serviços do monorepo"),
|
|
@@ -7642,7 +8021,6 @@ async function cliDeployFlow(opts) {
|
|
|
7642
8021
|
serviceName = project.services.find((s) => s.id === serviceId$1)?.name ?? "";
|
|
7643
8022
|
}
|
|
7644
8023
|
const selectedService = project.services.find((s) => s.id === serviceId$1);
|
|
7645
|
-
const detection = detectLocalRepo();
|
|
7646
8024
|
const detectedType = selectedService?.type?.toLowerCase() ?? "web";
|
|
7647
8025
|
saveConfig({
|
|
7648
8026
|
version: "1.0",
|
|
@@ -7662,7 +8040,7 @@ async function cliDeployFlow(opts) {
|
|
|
7662
8040
|
info(`Arquivo ${getConfigFileName()} criado na raiz do projeto.`);
|
|
7663
8041
|
const freshConfig = loadConfig();
|
|
7664
8042
|
if (freshConfig) await provisionDatabases(freshConfig, { yes: opts.yes ?? false });
|
|
7665
|
-
await triggerDeploy(serviceId$1
|
|
8043
|
+
await triggerDeploy(serviceId$1);
|
|
7666
8044
|
return;
|
|
7667
8045
|
}
|
|
7668
8046
|
if (project && project.services.length === 0) {
|
|
@@ -7955,13 +8333,64 @@ async function pruneRemovedEntries(config, existingConfig, services, databases)
|
|
|
7955
8333
|
return config;
|
|
7956
8334
|
}
|
|
7957
8335
|
|
|
8336
|
+
//#endregion
|
|
8337
|
+
//#region src/commands/update.ts
|
|
8338
|
+
function registerUpdate(cli$1) {
|
|
8339
|
+
cli$1.command("update", {
|
|
8340
|
+
description: "Atualizar a CLI para a versão mais recente",
|
|
8341
|
+
output: z.object({
|
|
8342
|
+
updated: z.boolean(),
|
|
8343
|
+
version: z.string().nullable()
|
|
8344
|
+
}),
|
|
8345
|
+
async run() {
|
|
8346
|
+
const pm = detectPackageManager();
|
|
8347
|
+
if (!pm) {
|
|
8348
|
+
warn("Instalação de desenvolvimento detectada. Use 'pnpm cli:install' para atualizar.");
|
|
8349
|
+
return {
|
|
8350
|
+
updated: false,
|
|
8351
|
+
version: null
|
|
8352
|
+
};
|
|
8353
|
+
}
|
|
8354
|
+
const currentVersion = getCurrentVersion();
|
|
8355
|
+
info(`Versão atual: ${chalk.bold(currentVersion)}`);
|
|
8356
|
+
const latestVersion = await fetchLatestVersion();
|
|
8357
|
+
if (!latestVersion) {
|
|
8358
|
+
warn("Não foi possível verificar a versão mais recente.");
|
|
8359
|
+
return {
|
|
8360
|
+
updated: false,
|
|
8361
|
+
version: null
|
|
8362
|
+
};
|
|
8363
|
+
}
|
|
8364
|
+
if (latestVersion === currentVersion) {
|
|
8365
|
+
info(`Você já está na versão mais recente ${chalk.bold(currentVersion)}.`);
|
|
8366
|
+
return {
|
|
8367
|
+
updated: false,
|
|
8368
|
+
version: currentVersion
|
|
8369
|
+
};
|
|
8370
|
+
}
|
|
8371
|
+
if (!installVersion(pm, latestVersion)) {
|
|
8372
|
+
console.log(chalk.dim(` Execute manualmente: ${getInstallCommand(pm, latestVersion)}`));
|
|
8373
|
+
return {
|
|
8374
|
+
updated: false,
|
|
8375
|
+
version: null
|
|
8376
|
+
};
|
|
8377
|
+
}
|
|
8378
|
+
return {
|
|
8379
|
+
updated: true,
|
|
8380
|
+
version: latestVersion
|
|
8381
|
+
};
|
|
8382
|
+
}
|
|
8383
|
+
});
|
|
8384
|
+
}
|
|
8385
|
+
|
|
7958
8386
|
//#endregion
|
|
7959
8387
|
//#region src/index.ts
|
|
7960
8388
|
if (process.argv.includes("--mcp")) process.env.VELOZ_MCP = "true";
|
|
7961
8389
|
const cli = Cli.create("veloz", {
|
|
7962
|
-
version: "0.0.0-beta.
|
|
8390
|
+
version: "0.0.0-beta.28",
|
|
7963
8391
|
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)") })
|
|
8392
|
+
env: z.object({ VELOZ_ENV: z.string().optional().describe("Ambiente alvo (ex: preview, staging)") }),
|
|
8393
|
+
mcp: { command: "npx -y onveloz --mcp" }
|
|
7965
8394
|
});
|
|
7966
8395
|
cli.use(async (c, next) => {
|
|
7967
8396
|
const env = c.env.VELOZ_ENV;
|
|
@@ -8033,6 +8462,7 @@ registerDeploy(cli);
|
|
|
8033
8462
|
registerUse(cli);
|
|
8034
8463
|
registerWhoami(cli);
|
|
8035
8464
|
registerPull(cli);
|
|
8465
|
+
registerUpdate(cli);
|
|
8036
8466
|
cli.serve();
|
|
8037
8467
|
|
|
8038
8468
|
//#endregion
|