onveloz 0.0.0-beta.6 → 0.0.0-beta.8
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.
- package/dist/index.mjs +992 -652
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from "@commander-js/extra-typings";
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
4
|
import chalk from "chalk";
|
|
4
5
|
import { exec, execSync } from "node:child_process";
|
|
5
6
|
import { homedir, platform, tmpdir } from "node:os";
|
|
6
7
|
import { createHash } from "node:crypto";
|
|
7
|
-
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
8
8
|
import { dirname, join, relative, resolve } from "node:path";
|
|
9
9
|
import * as readline from "node:readline";
|
|
10
10
|
import { createInterface } from "node:readline";
|
|
@@ -18,6 +18,52 @@ import { link, mkdir, mkdtemp, readdir, rm, stat } from "node:fs/promises";
|
|
|
18
18
|
import ignore from "ignore";
|
|
19
19
|
import tar from "tar";
|
|
20
20
|
|
|
21
|
+
//#region src/lib/output.ts
|
|
22
|
+
let _outputMode = null;
|
|
23
|
+
function getOutputMode() {
|
|
24
|
+
if (_outputMode) return _outputMode;
|
|
25
|
+
return detectOutputMode();
|
|
26
|
+
}
|
|
27
|
+
function detectOutputMode() {
|
|
28
|
+
if (process.env.VELOZ_OUTPUT === "json") return "json";
|
|
29
|
+
if (process.env.VELOZ_OUTPUT === "github-actions") return "github-actions";
|
|
30
|
+
if (process.env.VELOZ_OUTPUT === "plain") return "plain";
|
|
31
|
+
if (process.env.GITHUB_ACTIONS === "true") return "github-actions";
|
|
32
|
+
if (process.env.CI === "true") return "plain";
|
|
33
|
+
if (!process.stdout.isTTY || process.env.TERM === "dumb") return "plain";
|
|
34
|
+
return "fancy";
|
|
35
|
+
}
|
|
36
|
+
function setOutputMode(mode) {
|
|
37
|
+
_outputMode = mode;
|
|
38
|
+
}
|
|
39
|
+
function isInteractive() {
|
|
40
|
+
return getOutputMode() === "fancy" && process.stdin.isTTY === true;
|
|
41
|
+
}
|
|
42
|
+
function isMachineReadable() {
|
|
43
|
+
return getOutputMode() === "json";
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Emit structured JSON data to stdout.
|
|
47
|
+
* In JSON mode: outputs raw JSON object.
|
|
48
|
+
* In other modes: no-op (callers should use regular output helpers).
|
|
49
|
+
*/
|
|
50
|
+
function emitData(data) {
|
|
51
|
+
if (isMachineReadable()) process.stdout.write(JSON.stringify(data) + "\n");
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Open a collapsible group (GitHub Actions) or section header.
|
|
55
|
+
*/
|
|
56
|
+
function startGroup(name) {
|
|
57
|
+
if (getOutputMode() === "github-actions") process.stdout.write(`::group::${name}\n`);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Close a collapsible group (GitHub Actions).
|
|
61
|
+
*/
|
|
62
|
+
function endGroup() {
|
|
63
|
+
if (getOutputMode() === "github-actions") process.stdout.write("::endgroup::\n");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
//#endregion
|
|
21
67
|
//#region src/lib/config.ts
|
|
22
68
|
const CONFIG_DIR = join(homedir(), ".veloz");
|
|
23
69
|
const ENV_API_URL = process.env.VELOZ_API_URL;
|
|
@@ -94,6 +140,7 @@ function isRateLimitError(error) {
|
|
|
94
140
|
return null;
|
|
95
141
|
}
|
|
96
142
|
function handleError(error) {
|
|
143
|
+
const mode = getOutputMode();
|
|
97
144
|
if (error instanceof Error) {
|
|
98
145
|
const orpcError = error;
|
|
99
146
|
if (orpcError.code) {
|
|
@@ -106,51 +153,189 @@ function handleError(error) {
|
|
|
106
153
|
INTERNAL_SERVER_ERROR: "Erro interno do servidor. Tente novamente.",
|
|
107
154
|
TOO_MANY_REQUESTS: "Tente novamente em alguns segundos."
|
|
108
155
|
}[orpcError.code] ?? "Erro desconhecido.";
|
|
109
|
-
|
|
156
|
+
if (mode === "json") emitData({
|
|
157
|
+
type: "error",
|
|
158
|
+
code: orpcError.code,
|
|
159
|
+
message
|
|
160
|
+
});
|
|
161
|
+
else if (mode === "github-actions") process.stdout.write(`::error::${message}\n`);
|
|
162
|
+
else console.error(chalk.red(`\n✗ Erro: ${message}`));
|
|
110
163
|
process.exit(1);
|
|
111
164
|
}
|
|
112
165
|
if (error.message.includes("fetch") || error.message.includes("ECONNREFUSED")) {
|
|
113
|
-
|
|
114
|
-
|
|
166
|
+
const message = "Não foi possível conectar ao servidor Veloz.";
|
|
167
|
+
const hint = "Verifique se o servidor está rodando e a URL está correta.";
|
|
168
|
+
if (mode === "json") emitData({
|
|
169
|
+
type: "error",
|
|
170
|
+
code: "CONNECTION_ERROR",
|
|
171
|
+
message,
|
|
172
|
+
hint
|
|
173
|
+
});
|
|
174
|
+
else if (mode === "github-actions") process.stdout.write(`::error::${message} ${hint}\n`);
|
|
175
|
+
else {
|
|
176
|
+
console.error(chalk.red(`\n✗ Erro de conexão: ${message}`));
|
|
177
|
+
console.error(chalk.yellow(` ${hint}`));
|
|
178
|
+
}
|
|
115
179
|
process.exit(1);
|
|
116
180
|
}
|
|
117
|
-
|
|
181
|
+
if (mode === "json") emitData({
|
|
182
|
+
type: "error",
|
|
183
|
+
message: error.message
|
|
184
|
+
});
|
|
185
|
+
else if (mode === "github-actions") process.stdout.write(`::error::${error.message}\n`);
|
|
186
|
+
else console.error(chalk.red(`\n✗ Erro: ${error.message}`));
|
|
118
187
|
process.exit(1);
|
|
119
188
|
}
|
|
120
|
-
|
|
189
|
+
if (mode === "json") emitData({
|
|
190
|
+
type: "error",
|
|
191
|
+
message: "Erro inesperado."
|
|
192
|
+
});
|
|
193
|
+
else if (mode === "github-actions") process.stdout.write("::error::Erro inesperado.\n");
|
|
194
|
+
else console.error(chalk.red("\n✗ Erro inesperado."));
|
|
121
195
|
process.exit(1);
|
|
122
196
|
}
|
|
123
197
|
function success(message) {
|
|
124
|
-
|
|
198
|
+
const mode = getOutputMode();
|
|
199
|
+
if (mode === "json") emitData({
|
|
200
|
+
type: "success",
|
|
201
|
+
message
|
|
202
|
+
});
|
|
203
|
+
else if (mode === "github-actions") process.stdout.write(`✓ ${message}\n`);
|
|
204
|
+
else console.log(chalk.green(`\n✓ ${message}`));
|
|
125
205
|
}
|
|
126
206
|
function info(message) {
|
|
127
|
-
|
|
207
|
+
const mode = getOutputMode();
|
|
208
|
+
if (mode === "json") emitData({
|
|
209
|
+
type: "info",
|
|
210
|
+
message
|
|
211
|
+
});
|
|
212
|
+
else if (mode === "github-actions") process.stdout.write(`${message}\n`);
|
|
213
|
+
else console.log(chalk.cyan(`ℹ ${message}`));
|
|
214
|
+
}
|
|
215
|
+
function warn$1(message) {
|
|
216
|
+
const mode = getOutputMode();
|
|
217
|
+
if (mode === "json") emitData({
|
|
218
|
+
type: "warning",
|
|
219
|
+
message
|
|
220
|
+
});
|
|
221
|
+
else if (mode === "github-actions") process.stdout.write(`::warning::${message}\n`);
|
|
222
|
+
else console.log(chalk.yellow(`⚠ ${message}`));
|
|
128
223
|
}
|
|
129
|
-
|
|
130
|
-
|
|
224
|
+
/**
|
|
225
|
+
* Emit structured data in JSON mode, or run the display callback otherwise.
|
|
226
|
+
*
|
|
227
|
+
* Usage:
|
|
228
|
+
* output({ type: "user", name, email }, () => {
|
|
229
|
+
* console.log(` Nome: ${name}`);
|
|
230
|
+
* console.log(` Email: ${email}`);
|
|
231
|
+
* });
|
|
232
|
+
*/
|
|
233
|
+
function output(data, display) {
|
|
234
|
+
if (isMachineReadable()) emitData(data);
|
|
235
|
+
else display();
|
|
131
236
|
}
|
|
132
237
|
function spinner(text) {
|
|
238
|
+
const mode = getOutputMode();
|
|
239
|
+
if (mode === "json" || mode === "github-actions" || mode === "plain") {
|
|
240
|
+
const noopSpinner = ora({
|
|
241
|
+
text,
|
|
242
|
+
isEnabled: false
|
|
243
|
+
});
|
|
244
|
+
const originalStop = noopSpinner.stop.bind(noopSpinner);
|
|
245
|
+
const originalFail = noopSpinner.fail.bind(noopSpinner);
|
|
246
|
+
noopSpinner.start = function(newText) {
|
|
247
|
+
if (newText) this.text = newText;
|
|
248
|
+
if (mode === "github-actions") process.stdout.write(`${this.text}\n`);
|
|
249
|
+
return this;
|
|
250
|
+
};
|
|
251
|
+
noopSpinner.stop = function() {
|
|
252
|
+
return originalStop();
|
|
253
|
+
};
|
|
254
|
+
noopSpinner.fail = function(newText) {
|
|
255
|
+
const msg = newText ?? this.text;
|
|
256
|
+
if (mode === "json") emitData({
|
|
257
|
+
type: "error",
|
|
258
|
+
message: msg
|
|
259
|
+
});
|
|
260
|
+
else if (mode === "github-actions") process.stdout.write(`::error::${msg}\n`);
|
|
261
|
+
else console.error(msg);
|
|
262
|
+
return originalFail(newText);
|
|
263
|
+
};
|
|
264
|
+
return noopSpinner;
|
|
265
|
+
}
|
|
133
266
|
return ora({
|
|
134
267
|
text,
|
|
135
268
|
color: "cyan"
|
|
136
269
|
}).start();
|
|
137
270
|
}
|
|
138
|
-
|
|
271
|
+
/**
|
|
272
|
+
* Print a table. Pass `rawData` for structured JSON output — commands
|
|
273
|
+
* build display rows with chalk as before, and raw objects for machines.
|
|
274
|
+
*
|
|
275
|
+
* Usage:
|
|
276
|
+
* printTable(
|
|
277
|
+
* ["Nome", "Email"],
|
|
278
|
+
* users.map(u => [chalk.bold(u.name), u.email]),
|
|
279
|
+
* users.map(u => ({ name: u.name, email: u.email })),
|
|
280
|
+
* );
|
|
281
|
+
*/
|
|
282
|
+
function printTable(headers, rows, rawData) {
|
|
283
|
+
const mode = getOutputMode();
|
|
284
|
+
if (mode === "json") {
|
|
285
|
+
if (rawData) emitData({
|
|
286
|
+
type: "table",
|
|
287
|
+
data: rawData
|
|
288
|
+
});
|
|
289
|
+
else emitData({
|
|
290
|
+
type: "table",
|
|
291
|
+
data: rows.map((row) => {
|
|
292
|
+
const obj = {};
|
|
293
|
+
headers.forEach((h, i) => {
|
|
294
|
+
obj[h] = stripAnsi(row[i] ?? "");
|
|
295
|
+
});
|
|
296
|
+
return obj;
|
|
297
|
+
})
|
|
298
|
+
});
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
if (mode === "github-actions" || mode === "plain") {
|
|
302
|
+
const cleanHeaders = headers.map(stripAnsi);
|
|
303
|
+
const cleanRows = rows.map((row) => row.map(stripAnsi));
|
|
304
|
+
const colWidths$1 = cleanHeaders.map((h, i) => {
|
|
305
|
+
const maxRow = cleanRows.reduce((max, row) => Math.max(max, (row[i] ?? "").length), 0);
|
|
306
|
+
return Math.max(h.length, maxRow);
|
|
307
|
+
});
|
|
308
|
+
const headerLine$1 = cleanHeaders.map((h, i) => h.padEnd(colWidths$1[i])).join(" ");
|
|
309
|
+
const separator$1 = colWidths$1.map((w) => "-".repeat(w)).join("--");
|
|
310
|
+
process.stdout.write(`\n${headerLine$1}\n`);
|
|
311
|
+
process.stdout.write(`${separator$1}\n`);
|
|
312
|
+
for (const row of cleanRows) {
|
|
313
|
+
const line = row.map((cell, i) => cell.padEnd(colWidths$1[i])).join(" ");
|
|
314
|
+
process.stdout.write(`${line}\n`);
|
|
315
|
+
}
|
|
316
|
+
process.stdout.write("\n");
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
139
319
|
const colWidths = headers.map((h, i) => {
|
|
140
|
-
const maxRow = rows.reduce((max, row) => Math.max(max, (row[i] ?? "").length), 0);
|
|
141
|
-
return Math.max(h.length, maxRow);
|
|
320
|
+
const maxRow = rows.reduce((max, row) => Math.max(max, stripAnsi(row[i] ?? "").length), 0);
|
|
321
|
+
return Math.max(stripAnsi(h).length, maxRow);
|
|
142
322
|
});
|
|
143
323
|
const headerLine = headers.map((h, i) => chalk.bold(h.padEnd(colWidths[i]))).join(" ");
|
|
144
324
|
const separator = colWidths.map((w) => "─".repeat(w)).join("──");
|
|
145
325
|
console.log(`\n${headerLine}`);
|
|
146
326
|
console.log(chalk.dim(separator));
|
|
147
327
|
for (const row of rows) {
|
|
148
|
-
const line = row.map((cell, i) =>
|
|
328
|
+
const line = row.map((cell, i) => {
|
|
329
|
+
const visible = stripAnsi(cell);
|
|
330
|
+
const padding = colWidths[i] - visible.length;
|
|
331
|
+
return cell + " ".repeat(Math.max(0, padding));
|
|
332
|
+
}).join(" ");
|
|
149
333
|
console.log(line);
|
|
150
334
|
}
|
|
151
335
|
console.log();
|
|
152
336
|
}
|
|
153
337
|
async function prompt(question) {
|
|
338
|
+
if (!isInteractive()) return "";
|
|
154
339
|
const rl = createInterface({
|
|
155
340
|
input: process.stdin,
|
|
156
341
|
output: process.stdout
|
|
@@ -163,11 +348,17 @@ async function prompt(question) {
|
|
|
163
348
|
});
|
|
164
349
|
}
|
|
165
350
|
async function promptConfirm(question, defaultYes = true) {
|
|
351
|
+
if (!isInteractive()) return defaultYes;
|
|
166
352
|
const answer = await prompt(`${question} ${defaultYes ? "(S/n)" : "(s/N)"}`);
|
|
167
353
|
if (answer === "") return defaultYes;
|
|
168
354
|
return answer.toLowerCase().startsWith("s") || answer.toLowerCase().startsWith("y");
|
|
169
355
|
}
|
|
170
356
|
async function promptSelect(question, options) {
|
|
357
|
+
if (!isInteractive()) {
|
|
358
|
+
if (options.length > 0) return options[0].value;
|
|
359
|
+
console.error(chalk.red("Nenhuma opção disponível no modo não-interativo."));
|
|
360
|
+
process.exit(1);
|
|
361
|
+
}
|
|
171
362
|
console.log(chalk.cyan(`\n${question}\n`));
|
|
172
363
|
for (let i = 0; i < options.length; i++) console.log(chalk.white(` ${chalk.bold(`${i + 1})`)} ${options[i].label}`));
|
|
173
364
|
const answer = await prompt("\nEscolha (número):");
|
|
@@ -179,6 +370,7 @@ async function promptSelect(question, options) {
|
|
|
179
370
|
return options[index].value;
|
|
180
371
|
}
|
|
181
372
|
async function promptMultiSelect(question, options) {
|
|
373
|
+
if (!isInteractive()) return options.map((o) => o.value);
|
|
182
374
|
console.log(chalk.cyan(`\n${question}\n`));
|
|
183
375
|
for (let i = 0; i < options.length; i++) console.log(chalk.white(` ${chalk.bold(`${i + 1})`)} ${options[i].label}`));
|
|
184
376
|
console.log(chalk.dim(`\n * = todos`));
|
|
@@ -191,6 +383,10 @@ async function promptMultiSelect(question, options) {
|
|
|
191
383
|
}
|
|
192
384
|
return indices.map((i) => options[i].value);
|
|
193
385
|
}
|
|
386
|
+
const ANSI_REGEX = /\x1B\[[0-9;]*m/g;
|
|
387
|
+
function stripAnsi(str) {
|
|
388
|
+
return str.replace(ANSI_REGEX, "");
|
|
389
|
+
}
|
|
194
390
|
|
|
195
391
|
//#endregion
|
|
196
392
|
//#region src/commands/login.ts
|
|
@@ -213,15 +409,21 @@ async function performLogin(apiUrl) {
|
|
|
213
409
|
}
|
|
214
410
|
s.stop();
|
|
215
411
|
const verificationUrl = `${apiUrl.includes("localhost") ? "http://localhost:3001" : "https://app.onveloz.com"}${data.verification_uri_complete ? new URL(data.verification_uri_complete).pathname + new URL(data.verification_uri_complete).search : `/cli/auth?code=${data.user_code}`}`;
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
412
|
+
output({
|
|
413
|
+
type: "auth_code",
|
|
414
|
+
userCode: data.user_code,
|
|
415
|
+
verificationUrl
|
|
416
|
+
}, () => {
|
|
417
|
+
console.log();
|
|
418
|
+
info("Abrindo navegador para autenticação...");
|
|
419
|
+
console.log();
|
|
420
|
+
console.log(chalk.white(` Código de verificação: ${chalk.bold.cyan(data.user_code)}`));
|
|
421
|
+
console.log();
|
|
422
|
+
console.log(chalk.dim(" Se o navegador não abrir, acesse manualmente:"));
|
|
423
|
+
console.log(chalk.dim(` ${verificationUrl}`));
|
|
424
|
+
console.log();
|
|
425
|
+
openBrowser(verificationUrl);
|
|
426
|
+
});
|
|
225
427
|
const pollSpinner = spinner("Aguardando autorização no navegador...");
|
|
226
428
|
const token = await pollForToken(authClient, data.device_code, data.interval || 5);
|
|
227
429
|
if (!token) {
|
|
@@ -236,18 +438,17 @@ async function performLogin(apiUrl) {
|
|
|
236
438
|
success("Autenticado com sucesso!");
|
|
237
439
|
} catch (err) {
|
|
238
440
|
s.stop();
|
|
239
|
-
if (err instanceof Error
|
|
240
|
-
|
|
241
|
-
console.error(chalk.yellow(" Verifique se o servidor está rodando e a URL está correta."));
|
|
242
|
-
process.exit(1);
|
|
243
|
-
}
|
|
244
|
-
console.error(chalk.red(`\n✗ Erro: ${err instanceof Error ? err.message : "Erro inesperado."}`));
|
|
245
|
-
process.exit(1);
|
|
441
|
+
if (err instanceof Error) handleLoginError(err);
|
|
442
|
+
throw err;
|
|
246
443
|
}
|
|
247
444
|
}
|
|
445
|
+
function handleLoginError(err) {
|
|
446
|
+
if (err.message.includes("fetch") || err.message.includes("ECONNREFUSED")) throw err;
|
|
447
|
+
throw err;
|
|
448
|
+
}
|
|
248
449
|
const loginCommand = new Command("login").description("Autenticar na plataforma Veloz").option("--api-url <url>", "URL da API Veloz").option("--api-key <key>", "Chave de API (para CI/automação)").action(async (opts) => {
|
|
249
450
|
if (isAuthenticated()) {
|
|
250
|
-
warn("Você já está autenticado.");
|
|
451
|
+
warn$1("Você já está autenticado.");
|
|
251
452
|
if ((await prompt("Deseja fazer login novamente? (s/N)")).toLowerCase() !== "s") process.exit(0);
|
|
252
453
|
}
|
|
253
454
|
const config = loadConfig$1();
|
|
@@ -285,7 +486,7 @@ async function pollForToken(authClient, deviceCode, interval) {
|
|
|
285
486
|
}
|
|
286
487
|
const logoutCommand = new Command("logout").description("Encerrar sessão na plataforma Veloz").action(() => {
|
|
287
488
|
if (!isAuthenticated()) {
|
|
288
|
-
warn("Você não está autenticado.");
|
|
489
|
+
warn$1("Você não está autenticado.");
|
|
289
490
|
return;
|
|
290
491
|
}
|
|
291
492
|
deleteConfig();
|
|
@@ -549,12 +750,18 @@ projectsCommand.command("list").alias("listar").description("Listar todos os pro
|
|
|
549
750
|
"Repo GitHub",
|
|
550
751
|
"Criado em"
|
|
551
752
|
], projects.map((p) => [
|
|
552
|
-
p.id
|
|
753
|
+
p.id,
|
|
553
754
|
p.name,
|
|
554
755
|
p.slug,
|
|
555
756
|
p.githubRepoOwner && p.githubRepoName ? `${p.githubRepoOwner}/${p.githubRepoName}` : "—",
|
|
556
757
|
new Date(p.createdAt).toLocaleDateString("pt-BR")
|
|
557
|
-
]))
|
|
758
|
+
]), projects.map((p) => ({
|
|
759
|
+
id: p.id,
|
|
760
|
+
name: p.name,
|
|
761
|
+
slug: p.slug,
|
|
762
|
+
githubRepo: p.githubRepoOwner && p.githubRepoName ? `${p.githubRepoOwner}/${p.githubRepoName}` : null,
|
|
763
|
+
createdAt: p.createdAt
|
|
764
|
+
})));
|
|
558
765
|
} catch (error) {
|
|
559
766
|
spin.stop();
|
|
560
767
|
handleError(error);
|
|
@@ -566,396 +773,208 @@ projectsCommand.command("list").alias("listar").description("Listar todos os pro
|
|
|
566
773
|
const linkCommand = new Command("link").description("Verificar vínculo do projeto com Veloz").action(async () => {
|
|
567
774
|
const config = loadConfig();
|
|
568
775
|
if (!config) {
|
|
569
|
-
warn("Nenhum projeto configurado. Execute 'veloz deploy' para configurar seu projeto.");
|
|
776
|
+
warn$1("Nenhum projeto configurado. Execute 'veloz deploy' para configurar seu projeto.");
|
|
570
777
|
return;
|
|
571
778
|
}
|
|
572
|
-
console.log(chalk.bold("\n🔗 Projeto Vinculado\n"));
|
|
573
|
-
console.log(` ${chalk.bold("Projeto:")} ${chalk.cyan(config.project.name)}`);
|
|
574
|
-
console.log(` ${chalk.bold("ID:")} ${chalk.dim(config.project.id)}`);
|
|
575
779
|
const services = Object.entries(config.services);
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
780
|
+
output({
|
|
781
|
+
type: "link",
|
|
782
|
+
project: {
|
|
783
|
+
id: config.project.id,
|
|
784
|
+
name: config.project.name
|
|
785
|
+
},
|
|
786
|
+
services: services.map(([key, service]) => ({
|
|
787
|
+
key,
|
|
788
|
+
id: service.id,
|
|
789
|
+
name: service.name,
|
|
790
|
+
type: service.type
|
|
791
|
+
}))
|
|
792
|
+
}, () => {
|
|
793
|
+
console.log(chalk.bold("\n🔗 Projeto Vinculado\n"));
|
|
794
|
+
console.log(` ${chalk.bold("Projeto:")} ${chalk.cyan(config.project.name)}`);
|
|
795
|
+
console.log(` ${chalk.bold("ID:")} ${chalk.dim(config.project.id)}`);
|
|
796
|
+
if (services.length > 0) {
|
|
797
|
+
console.log(`\n ${chalk.bold("Serviços:")}`);
|
|
798
|
+
services.forEach(([key, service]) => {
|
|
799
|
+
const type = service.type === "web" ? "Serviço Web" : "Site Estático";
|
|
800
|
+
console.log(` • ${chalk.cyan(service.name)} (${key}) - ${type}`);
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
console.log();
|
|
804
|
+
info("Use 'veloz deploy' para fazer deploy ou atualizar serviços.");
|
|
805
|
+
});
|
|
585
806
|
});
|
|
586
807
|
|
|
587
808
|
//#endregion
|
|
588
809
|
//#region ../../packages/api/src/lib/framework-detector.ts
|
|
589
|
-
|
|
590
|
-
{
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
810
|
+
function safeParsePkg(content) {
|
|
811
|
+
try {
|
|
812
|
+
return JSON.parse(content);
|
|
813
|
+
} catch {
|
|
814
|
+
return null;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
function detectFrameworkFromPkg(pkg) {
|
|
818
|
+
const allDeps = {
|
|
819
|
+
...pkg.dependencies,
|
|
820
|
+
...pkg.devDependencies
|
|
821
|
+
};
|
|
822
|
+
if (allDeps["next"]) return {
|
|
823
|
+
framework: "nextjs",
|
|
595
824
|
buildCommand: "npm run build",
|
|
596
|
-
startCommand: "npm start",
|
|
597
|
-
outputDir: ".next",
|
|
825
|
+
startCommand: "npm run start",
|
|
598
826
|
port: 3e3
|
|
599
|
-
}
|
|
600
|
-
{
|
|
601
|
-
|
|
602
|
-
label: "Hono",
|
|
603
|
-
type: "WEB",
|
|
604
|
-
match: (deps) => "hono" in deps,
|
|
827
|
+
};
|
|
828
|
+
if (allDeps["nuxt"] || allDeps["nuxt3"]) return {
|
|
829
|
+
framework: "nuxt",
|
|
605
830
|
buildCommand: "npm run build",
|
|
606
|
-
startCommand: "npm start",
|
|
607
|
-
outputDir: "dist",
|
|
831
|
+
startCommand: "npm run start",
|
|
608
832
|
port: 3e3
|
|
609
|
-
}
|
|
610
|
-
{
|
|
611
|
-
|
|
612
|
-
label: "Nuxt",
|
|
613
|
-
type: "WEB",
|
|
614
|
-
match: (deps) => "nuxt" in deps,
|
|
833
|
+
};
|
|
834
|
+
if (allDeps["@remix-run/node"] || allDeps["@remix-run/react"]) return {
|
|
835
|
+
framework: "remix",
|
|
615
836
|
buildCommand: "npm run build",
|
|
616
|
-
startCommand: "npm start",
|
|
617
|
-
outputDir: ".nuxt",
|
|
837
|
+
startCommand: "npm run start",
|
|
618
838
|
port: 3e3
|
|
619
|
-
}
|
|
620
|
-
{
|
|
621
|
-
|
|
622
|
-
label: "Remix",
|
|
623
|
-
type: "WEB",
|
|
624
|
-
match: (deps) => "@remix-run/node" in deps,
|
|
839
|
+
};
|
|
840
|
+
if (allDeps["astro"]) return {
|
|
841
|
+
framework: "astro",
|
|
625
842
|
buildCommand: "npm run build",
|
|
626
|
-
startCommand: "npm
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
name: "sveltekit",
|
|
632
|
-
label: "SvelteKit",
|
|
633
|
-
type: "WEB",
|
|
634
|
-
match: (deps) => "@sveltejs/kit" in deps,
|
|
843
|
+
startCommand: "npm run preview",
|
|
844
|
+
port: 4321
|
|
845
|
+
};
|
|
846
|
+
if (allDeps["@sveltejs/kit"]) return {
|
|
847
|
+
framework: "svelte",
|
|
635
848
|
buildCommand: "npm run build",
|
|
636
|
-
startCommand: "npm
|
|
637
|
-
outputDir: ".svelte-kit",
|
|
849
|
+
startCommand: "npm run preview",
|
|
638
850
|
port: 3e3
|
|
639
|
-
}
|
|
640
|
-
{
|
|
641
|
-
|
|
642
|
-
label: "Astro",
|
|
643
|
-
type: "STATIC",
|
|
644
|
-
match: (deps) => "astro" in deps,
|
|
851
|
+
};
|
|
852
|
+
if (allDeps["gatsby"]) return {
|
|
853
|
+
framework: "gatsby",
|
|
645
854
|
buildCommand: "npm run build",
|
|
646
|
-
startCommand:
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
name: "gatsby",
|
|
652
|
-
label: "Gatsby",
|
|
653
|
-
type: "STATIC",
|
|
654
|
-
match: (deps) => "gatsby" in deps,
|
|
855
|
+
startCommand: "npm run serve",
|
|
856
|
+
port: 9e3
|
|
857
|
+
};
|
|
858
|
+
if (allDeps["@angular/core"]) return {
|
|
859
|
+
framework: "angular",
|
|
655
860
|
buildCommand: "npm run build",
|
|
656
|
-
startCommand:
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
name: "create-react-app",
|
|
662
|
-
label: "Create React App",
|
|
663
|
-
type: "STATIC",
|
|
664
|
-
match: (deps) => "react-scripts" in deps,
|
|
861
|
+
startCommand: "npm run start",
|
|
862
|
+
port: 4200
|
|
863
|
+
};
|
|
864
|
+
if (allDeps["hono"]) return {
|
|
865
|
+
framework: "hono",
|
|
665
866
|
buildCommand: "npm run build",
|
|
666
|
-
startCommand:
|
|
667
|
-
outputDir: "build",
|
|
867
|
+
startCommand: "npm run start",
|
|
668
868
|
port: 3e3
|
|
669
|
-
}
|
|
670
|
-
{
|
|
671
|
-
|
|
672
|
-
label: "Vite + React",
|
|
673
|
-
type: "STATIC",
|
|
674
|
-
match: (deps) => "vite" in deps && "react" in deps,
|
|
869
|
+
};
|
|
870
|
+
if (allDeps["express"]) return {
|
|
871
|
+
framework: "express",
|
|
675
872
|
buildCommand: "npm run build",
|
|
676
|
-
startCommand:
|
|
677
|
-
outputDir: "dist",
|
|
873
|
+
startCommand: "npm run start",
|
|
678
874
|
port: 3e3
|
|
679
|
-
}
|
|
680
|
-
{
|
|
681
|
-
|
|
682
|
-
label: "Vite + Vue",
|
|
683
|
-
type: "STATIC",
|
|
684
|
-
match: (deps) => "vite" in deps && "vue" in deps,
|
|
875
|
+
};
|
|
876
|
+
if (allDeps["fastify"]) return {
|
|
877
|
+
framework: "fastify",
|
|
685
878
|
buildCommand: "npm run build",
|
|
686
|
-
startCommand:
|
|
687
|
-
outputDir: "dist",
|
|
879
|
+
startCommand: "npm run start",
|
|
688
880
|
port: 3e3
|
|
689
|
-
}
|
|
690
|
-
{
|
|
691
|
-
|
|
692
|
-
label: "Vite",
|
|
693
|
-
type: "STATIC",
|
|
694
|
-
match: (deps) => "vite" in deps,
|
|
881
|
+
};
|
|
882
|
+
if (allDeps["@nestjs/core"]) return {
|
|
883
|
+
framework: "nestjs",
|
|
695
884
|
buildCommand: "npm run build",
|
|
696
|
-
startCommand:
|
|
697
|
-
outputDir: "dist",
|
|
885
|
+
startCommand: "npm run start:prod",
|
|
698
886
|
port: 3e3
|
|
699
|
-
}
|
|
700
|
-
{
|
|
701
|
-
|
|
702
|
-
label: "Angular",
|
|
703
|
-
type: "STATIC",
|
|
704
|
-
match: (deps) => "@angular/core" in deps,
|
|
887
|
+
};
|
|
888
|
+
if (allDeps["vite"]) return {
|
|
889
|
+
framework: "vite",
|
|
705
890
|
buildCommand: "npm run build",
|
|
706
|
-
startCommand:
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
name: "express",
|
|
712
|
-
label: "Express",
|
|
713
|
-
type: "WEB",
|
|
714
|
-
match: (deps) => "express" in deps,
|
|
715
|
-
buildCommand: null,
|
|
716
|
-
startCommand: "node index.js",
|
|
717
|
-
outputDir: null,
|
|
718
|
-
port: 3e3
|
|
719
|
-
},
|
|
720
|
-
{
|
|
721
|
-
name: "fastify",
|
|
722
|
-
label: "Fastify",
|
|
723
|
-
type: "WEB",
|
|
724
|
-
match: (deps) => "fastify" in deps,
|
|
725
|
-
buildCommand: null,
|
|
726
|
-
startCommand: "node index.js",
|
|
727
|
-
outputDir: null,
|
|
728
|
-
port: 3e3
|
|
729
|
-
},
|
|
730
|
-
{
|
|
731
|
-
name: "nestjs",
|
|
732
|
-
label: "NestJS",
|
|
733
|
-
type: "WEB",
|
|
734
|
-
match: (deps) => "@nestjs/core" in deps,
|
|
891
|
+
startCommand: "npm run preview",
|
|
892
|
+
port: 4173
|
|
893
|
+
};
|
|
894
|
+
if (allDeps["react-scripts"]) return {
|
|
895
|
+
framework: "cra",
|
|
735
896
|
buildCommand: "npm run build",
|
|
736
|
-
startCommand: "
|
|
737
|
-
outputDir: "dist",
|
|
897
|
+
startCommand: "npx serve -s build",
|
|
738
898
|
port: 3e3
|
|
739
|
-
}
|
|
740
|
-
{
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
match: (deps) => "hono" in deps || "@hono/node-server" in deps,
|
|
745
|
-
buildCommand: null,
|
|
746
|
-
startCommand: "node index.js",
|
|
747
|
-
outputDir: null,
|
|
899
|
+
};
|
|
900
|
+
return {
|
|
901
|
+
framework: "node",
|
|
902
|
+
buildCommand: pkg.scripts?.build ? "npm run build" : "",
|
|
903
|
+
startCommand: pkg.scripts?.start ? "npm run start" : "node index.js",
|
|
748
904
|
port: 3e3
|
|
749
|
-
}
|
|
750
|
-
|
|
905
|
+
};
|
|
906
|
+
}
|
|
751
907
|
function detectPackageManager(files) {
|
|
752
|
-
if ("
|
|
753
|
-
if ("pnpm-lock.yaml" in files || "pnpm-workspace.yaml" in files) return "pnpm";
|
|
908
|
+
if ("pnpm-lock.yaml" in files) return "pnpm";
|
|
754
909
|
if ("yarn.lock" in files) return "yarn";
|
|
910
|
+
if ("bun.lock" in files || "bun.lockb" in files) return "bun";
|
|
755
911
|
return "npm";
|
|
756
912
|
}
|
|
757
|
-
function
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
function safeParse(json) {
|
|
774
|
-
if (!json) return null;
|
|
775
|
-
try {
|
|
776
|
-
return JSON.parse(json);
|
|
777
|
-
} catch {
|
|
778
|
-
return null;
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
function getAllDeps(pkg) {
|
|
782
|
-
const deps = pkg.dependencies ?? {};
|
|
783
|
-
const devDeps = pkg.devDependencies ?? {};
|
|
784
|
-
return {
|
|
785
|
-
...deps,
|
|
786
|
-
...devDeps
|
|
787
|
-
};
|
|
788
|
-
}
|
|
789
|
-
function detectFramework(packageJsonStr, pm = "npm") {
|
|
790
|
-
const pkg = safeParse(packageJsonStr);
|
|
791
|
-
if (!pkg) return null;
|
|
792
|
-
const allDeps = getAllDeps(pkg);
|
|
793
|
-
const scripts = pkg.scripts ?? {};
|
|
794
|
-
for (const rule of FRAMEWORK_RULES) if (rule.match(allDeps)) {
|
|
795
|
-
const ruleStart = rule.startCommand === "node index.js" ? "node index.js" : rule.startCommand ? startCmd(pm) : null;
|
|
796
|
-
let buildScript = "build";
|
|
797
|
-
let startScript = "start";
|
|
798
|
-
const scriptKeys = Object.keys(scripts);
|
|
799
|
-
const buildVariants = scriptKeys.filter((k) => k.startsWith("build:") || k.startsWith("build-"));
|
|
800
|
-
const startVariants = scriptKeys.filter((k) => k.startsWith("start:") || k.startsWith("start-"));
|
|
801
|
-
if (buildVariants.length > 0 && !scripts.build) buildScript = buildVariants[0];
|
|
802
|
-
if (startVariants.length > 0 && !scripts.start) startScript = startVariants[0];
|
|
803
|
-
return {
|
|
804
|
-
name: rule.name,
|
|
805
|
-
label: rule.label,
|
|
806
|
-
type: rule.type,
|
|
807
|
-
buildCommand: scripts[buildScript] ? runCmd(pm, buildScript) : rule.buildCommand ? runCmd(pm, "build") : null,
|
|
808
|
-
startCommand: scripts[startScript] ? runCmd(pm, startScript) : ruleStart,
|
|
809
|
-
outputDir: rule.outputDir,
|
|
810
|
-
port: rule.port
|
|
811
|
-
};
|
|
812
|
-
}
|
|
813
|
-
if (scripts.build || scripts.start) {
|
|
814
|
-
let outputDir = null;
|
|
815
|
-
if (scripts.build) {
|
|
816
|
-
if (scripts.build.includes("tsc")) outputDir = "dist";
|
|
817
|
-
else if (scripts.build.includes("tsup")) outputDir = "dist";
|
|
818
|
-
else if (scripts.build.includes("esbuild")) outputDir = "dist";
|
|
819
|
-
else if (scripts.build.includes("webpack")) outputDir = "dist";
|
|
820
|
-
else if (scripts.build.includes("rollup")) outputDir = "dist";
|
|
821
|
-
else if (scripts.build.includes("parcel")) outputDir = "dist";
|
|
822
|
-
}
|
|
823
|
-
return {
|
|
824
|
-
name: "node",
|
|
825
|
-
label: "Node.js",
|
|
826
|
-
type: scripts.start ? "WEB" : "STATIC",
|
|
827
|
-
buildCommand: scripts.build ? runCmd(pm, "build") : null,
|
|
828
|
-
startCommand: scripts.start ? startCmd(pm) : null,
|
|
829
|
-
outputDir,
|
|
830
|
-
port: 3e3
|
|
831
|
-
};
|
|
832
|
-
}
|
|
833
|
-
return null;
|
|
834
|
-
}
|
|
835
|
-
function detectEnvVars(files) {
|
|
836
|
-
const envContent = files[".env.example"] ?? files[".env.sample"] ?? files[".env.local.example"] ?? files[".env"];
|
|
837
|
-
if (!envContent) return [];
|
|
838
|
-
const vars = [];
|
|
839
|
-
for (const line of envContent.split("\n")) {
|
|
840
|
-
const trimmed = line.trim();
|
|
841
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
842
|
-
const eqIdx = trimmed.indexOf("=");
|
|
843
|
-
if (eqIdx === -1) continue;
|
|
844
|
-
const key = trimmed.slice(0, eqIdx).trim();
|
|
845
|
-
const value = trimmed.slice(eqIdx + 1).trim();
|
|
846
|
-
if (key) vars.push({
|
|
847
|
-
key,
|
|
848
|
-
value
|
|
849
|
-
});
|
|
850
|
-
}
|
|
851
|
-
return vars;
|
|
852
|
-
}
|
|
853
|
-
function resolveWorkspacePatterns(patterns, availableFiles) {
|
|
854
|
-
const dirs = /* @__PURE__ */ new Set();
|
|
855
|
-
for (const pattern of patterns) {
|
|
856
|
-
const hasGlob = /\/?\*\*?$/.test(pattern);
|
|
857
|
-
const base = pattern.replace(/\/?\*\*?$/, "");
|
|
858
|
-
if (hasGlob) {
|
|
859
|
-
for (const filePath of availableFiles) if (filePath.startsWith(base + "/") && filePath.endsWith("/package.json")) {
|
|
860
|
-
const parts = filePath.slice(base.length + 1).split("/");
|
|
861
|
-
if (parts.length === 2 && parts[1] === "package.json") dirs.add(base + "/" + parts[0]);
|
|
862
|
-
}
|
|
863
|
-
} else {
|
|
864
|
-
const pkgPath = base + "/package.json";
|
|
865
|
-
if (availableFiles.includes(pkgPath)) dirs.add(base);
|
|
913
|
+
function extractEnvVars(files) {
|
|
914
|
+
const vars = /* @__PURE__ */ new Set();
|
|
915
|
+
for (const key of [
|
|
916
|
+
".env.example",
|
|
917
|
+
".env.sample",
|
|
918
|
+
".env"
|
|
919
|
+
]) {
|
|
920
|
+
const content = files[key];
|
|
921
|
+
if (!content) continue;
|
|
922
|
+
for (const line of content.split("\n")) {
|
|
923
|
+
const trimmed = line.trim();
|
|
924
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
925
|
+
const eqIdx = trimmed.indexOf("=");
|
|
926
|
+
if (eqIdx > 0) vars.add(trimmed.slice(0, eqIdx).trim());
|
|
866
927
|
}
|
|
867
928
|
}
|
|
868
|
-
return
|
|
929
|
+
return Array.from(vars);
|
|
869
930
|
}
|
|
870
|
-
function
|
|
871
|
-
const
|
|
872
|
-
const
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
if (workspacePatterns.length === 0 && files["pnpm-workspace.yaml"]) {
|
|
880
|
-
const lines = files["pnpm-workspace.yaml"].split("\n");
|
|
881
|
-
let inPackages = false;
|
|
882
|
-
for (const line of lines) {
|
|
883
|
-
if (line.match(/^packages\s*:/)) {
|
|
884
|
-
inPackages = true;
|
|
885
|
-
continue;
|
|
886
|
-
}
|
|
887
|
-
if (inPackages) {
|
|
888
|
-
const match = line.match(/^\s+-\s+['"]?([^'"]+)['"]?\s*$/);
|
|
889
|
-
if (match) workspacePatterns.push(match[1]);
|
|
890
|
-
else if (line.match(/^\S/)) break;
|
|
891
|
-
}
|
|
892
|
-
}
|
|
931
|
+
function analyzeRepo(files) {
|
|
932
|
+
const packageManager = detectPackageManager(files);
|
|
933
|
+
const envVars = extractEnvVars(files);
|
|
934
|
+
const rootPkgContent = files["package.json"];
|
|
935
|
+
const rootPkg = rootPkgContent ? safeParsePkg(rootPkgContent) : null;
|
|
936
|
+
const isMonorepo = "pnpm-workspace.yaml" in files || !!rootPkg?.workspaces;
|
|
937
|
+
function fixPm(cmd) {
|
|
938
|
+
if (packageManager === "npm") return cmd;
|
|
939
|
+
return cmd.replace(/^npm run /, `${packageManager} run `);
|
|
893
940
|
}
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
`build:${serviceName}`,
|
|
910
|
-
`build-${serviceName}`,
|
|
911
|
-
`${serviceName}:build`
|
|
912
|
-
].find((k) => rootScripts[k]);
|
|
913
|
-
if (buildKey) framework.buildCommand = `${pm} run ${buildKey}`;
|
|
914
|
-
const startKey = [
|
|
915
|
-
`start:${serviceName}`,
|
|
916
|
-
`start-${serviceName}`,
|
|
917
|
-
`${serviceName}:start`,
|
|
918
|
-
`dev:${serviceName}`,
|
|
919
|
-
`serve:${serviceName}`
|
|
920
|
-
].find((k) => rootScripts[k]);
|
|
921
|
-
if (startKey) framework.startCommand = `${pm} run ${startKey}`;
|
|
922
|
-
if (framework.name === "nextjs" && !startKey) framework.startCommand = `cd ${dir} && ${pm} run start`;
|
|
923
|
-
else if ([
|
|
924
|
-
"hono",
|
|
925
|
-
"express",
|
|
926
|
-
"fastify",
|
|
927
|
-
"nestjs"
|
|
928
|
-
].includes(framework.name)) {
|
|
929
|
-
if (!startKey && framework.buildCommand) {
|
|
930
|
-
const scripts = pkg?.scripts || {};
|
|
931
|
-
if (scripts.build && scripts.build.includes("tsc")) framework.startCommand = `node ${dir}/dist/index.js`;
|
|
932
|
-
else if (scripts.build && scripts.build.includes("tsup")) framework.startCommand = `node ${dir}/dist/index.js`;
|
|
933
|
-
else if (scripts.build && scripts.build.includes("esbuild")) framework.startCommand = `node ${dir}/dist/index.js`;
|
|
934
|
-
}
|
|
935
|
-
}
|
|
936
|
-
}
|
|
937
|
-
apps.push({
|
|
938
|
-
name,
|
|
939
|
-
path: dir,
|
|
940
|
-
framework
|
|
941
|
+
const services = [];
|
|
942
|
+
if (isMonorepo) for (const [path, content] of Object.entries(files)) {
|
|
943
|
+
if (path === "package.json") continue;
|
|
944
|
+
if (!path.endsWith("/package.json")) continue;
|
|
945
|
+
const nested = safeParsePkg(content);
|
|
946
|
+
if (!nested) continue;
|
|
947
|
+
const root = path.replace("/package.json", "");
|
|
948
|
+
const detected = detectFrameworkFromPkg(nested);
|
|
949
|
+
services.push({
|
|
950
|
+
name: nested.name || root.split("/").pop() || root,
|
|
951
|
+
root,
|
|
952
|
+
framework: detected.framework,
|
|
953
|
+
buildCommand: fixPm(detected.buildCommand),
|
|
954
|
+
startCommand: fixPm(detected.startCommand),
|
|
955
|
+
port: detected.port
|
|
941
956
|
});
|
|
942
957
|
}
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
958
|
+
let framework = null;
|
|
959
|
+
let buildCommand = null;
|
|
960
|
+
let startCommand = null;
|
|
961
|
+
let port = null;
|
|
962
|
+
if (rootPkg) {
|
|
963
|
+
const detected = detectFrameworkFromPkg(rootPkg);
|
|
964
|
+
framework = detected.framework;
|
|
965
|
+
buildCommand = fixPm(detected.buildCommand);
|
|
966
|
+
startCommand = fixPm(detected.startCommand);
|
|
967
|
+
port = detected.port;
|
|
968
|
+
}
|
|
953
969
|
return {
|
|
954
970
|
framework,
|
|
955
|
-
|
|
956
|
-
|
|
971
|
+
buildCommand,
|
|
972
|
+
startCommand,
|
|
973
|
+
port,
|
|
974
|
+
packageManager,
|
|
957
975
|
isMonorepo,
|
|
958
|
-
|
|
976
|
+
services,
|
|
977
|
+
envVars
|
|
959
978
|
};
|
|
960
979
|
}
|
|
961
980
|
|
|
@@ -1107,15 +1126,30 @@ const statusLabels = {
|
|
|
1107
1126
|
FAILED: "Falhou",
|
|
1108
1127
|
CANCELLED: "Cancelado"
|
|
1109
1128
|
};
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1129
|
+
function makeStatusIcons() {
|
|
1130
|
+
const mode = getOutputMode();
|
|
1131
|
+
if (mode === "json" || mode === "github-actions" || mode === "plain") return {
|
|
1132
|
+
QUEUED: "○",
|
|
1133
|
+
BUILDING: "●",
|
|
1134
|
+
DEPLOYING: "●",
|
|
1135
|
+
LIVE: "●",
|
|
1136
|
+
BUILD_FAILED: "●",
|
|
1137
|
+
FAILED: "●",
|
|
1138
|
+
CANCELLED: "●"
|
|
1139
|
+
};
|
|
1140
|
+
return {
|
|
1141
|
+
QUEUED: chalk.gray("○"),
|
|
1142
|
+
BUILDING: chalk.yellow("●"),
|
|
1143
|
+
DEPLOYING: chalk.blue("●"),
|
|
1144
|
+
LIVE: chalk.green("●"),
|
|
1145
|
+
BUILD_FAILED: chalk.red("●"),
|
|
1146
|
+
FAILED: chalk.red("●"),
|
|
1147
|
+
CANCELLED: chalk.gray("●")
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
const statusIcons = new Proxy({}, { get(_target, prop) {
|
|
1151
|
+
return makeStatusIcons()[prop] ?? chalk.yellow("●");
|
|
1152
|
+
} });
|
|
1119
1153
|
const TERMINAL_STATUSES = new Set([
|
|
1120
1154
|
"LIVE",
|
|
1121
1155
|
"BUILD_FAILED",
|
|
@@ -1125,13 +1159,39 @@ const TERMINAL_STATUSES = new Set([
|
|
|
1125
1159
|
|
|
1126
1160
|
//#endregion
|
|
1127
1161
|
//#region src/lib/deploy-stream.ts
|
|
1162
|
+
function getFailureHints$1(status) {
|
|
1163
|
+
switch (status) {
|
|
1164
|
+
case "BUILD_FAILED": return [
|
|
1165
|
+
"Verifique os logs de build acima para erros de compilação",
|
|
1166
|
+
"Teste o build localmente: rode o comando de build do seu projeto",
|
|
1167
|
+
"Use 'veloz config show' para verificar as configurações do serviço"
|
|
1168
|
+
];
|
|
1169
|
+
case "DEPLOY_FAILED": return [
|
|
1170
|
+
"O build passou mas o container falhou ao iniciar",
|
|
1171
|
+
"Verifique se a porta configurada está correta: 'veloz config show'",
|
|
1172
|
+
"Veja os logs de runtime: 'veloz logs -f'"
|
|
1173
|
+
];
|
|
1174
|
+
case "CANCELLED": return ["Deploy cancelado. Execute 'veloz deploy' para tentar novamente."];
|
|
1175
|
+
default: return ["Execute 'veloz logs -f' para mais detalhes."];
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1128
1178
|
async function streamDeploymentLogs(deploymentId, serviceName) {
|
|
1129
1179
|
const client = await getClient();
|
|
1130
1180
|
const isVerbose = process.env.VELOZ_VERBOSE === "true";
|
|
1131
|
-
const
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1181
|
+
const mode = getOutputMode();
|
|
1182
|
+
const logHeader = serviceName ? `Build logs para ${serviceName}:` : "Build logs:";
|
|
1183
|
+
if (mode === "json") emitData({
|
|
1184
|
+
type: "deploy_stream_start",
|
|
1185
|
+
deploymentId,
|
|
1186
|
+
serviceName: serviceName ?? null
|
|
1187
|
+
});
|
|
1188
|
+
else if (mode === "github-actions") startGroup(logHeader);
|
|
1189
|
+
else {
|
|
1190
|
+
const fancyHeader = serviceName ? `📦 Build logs para ${chalk.bold(serviceName)}:` : "📦 Build logs:";
|
|
1191
|
+
console.log(chalk.cyan(`\n${fancyHeader}`));
|
|
1192
|
+
console.log(chalk.dim("─".repeat(60)));
|
|
1193
|
+
}
|
|
1194
|
+
if (isVerbose && mode === "fancy") console.log(chalk.dim(`[verbose] Conectando ao stream de logs para deployment ${deploymentId}...`));
|
|
1135
1195
|
let finalStatus = "";
|
|
1136
1196
|
let logsReceived = false;
|
|
1137
1197
|
try {
|
|
@@ -1141,36 +1201,77 @@ async function streamDeploymentLogs(deploymentId, serviceName) {
|
|
|
1141
1201
|
if (event.type === "status") {
|
|
1142
1202
|
const label = statusLabels[event.content] ?? event.content;
|
|
1143
1203
|
const icon = statusIcons[event.content] ?? chalk.yellow("●");
|
|
1144
|
-
|
|
1204
|
+
if (mode === "json") emitData({
|
|
1205
|
+
type: "deploy_status",
|
|
1206
|
+
deploymentId,
|
|
1207
|
+
status: event.content,
|
|
1208
|
+
label
|
|
1209
|
+
});
|
|
1210
|
+
else process.stdout.write(`\n${icon} ${chalk.bold(label)}\n`);
|
|
1145
1211
|
finalStatus = event.content;
|
|
1146
|
-
if (isVerbose) console.log(chalk.dim(`[verbose] Status mudou para: ${event.content}`));
|
|
1147
|
-
} else if (event.type === "log")
|
|
1212
|
+
if (isVerbose && mode === "fancy") console.log(chalk.dim(`[verbose] Status mudou para: ${event.content}`));
|
|
1213
|
+
} else if (event.type === "log") if (mode === "json") emitData({
|
|
1214
|
+
type: "build_log",
|
|
1215
|
+
deploymentId,
|
|
1216
|
+
content: event.content
|
|
1217
|
+
});
|
|
1218
|
+
else process.stdout.write(event.content);
|
|
1148
1219
|
}
|
|
1149
|
-
if (!logsReceived && isVerbose) console.log(chalk.yellow("[verbose] Nenhum log recebido do stream. Buscando status do deployment..."));
|
|
1220
|
+
if (!logsReceived && isVerbose && mode === "fancy") console.log(chalk.yellow("[verbose] Nenhum log recebido do stream. Buscando status do deployment..."));
|
|
1150
1221
|
} catch (error) {
|
|
1151
|
-
if (isVerbose) console.log(chalk.red(`[verbose] Erro no stream: ${error.message}`));
|
|
1222
|
+
if (isVerbose && mode === "fancy") console.log(chalk.red(`[verbose] Erro no stream: ${error.message}`));
|
|
1152
1223
|
try {
|
|
1153
1224
|
const d = await client.deployments.get({ deploymentId });
|
|
1154
1225
|
finalStatus = d.status;
|
|
1155
|
-
if (isVerbose) console.log(chalk.dim(`[verbose] Status do deployment: ${d.status}`));
|
|
1226
|
+
if (isVerbose && mode === "fancy") console.log(chalk.dim(`[verbose] Status do deployment: ${d.status}`));
|
|
1156
1227
|
try {
|
|
1157
1228
|
const logs = await client.logs.getBuildLogs({ deploymentId });
|
|
1158
|
-
if (logs.buildLogs) {
|
|
1229
|
+
if (logs.buildLogs) if (mode === "json") emitData({
|
|
1230
|
+
type: "build_log",
|
|
1231
|
+
deploymentId,
|
|
1232
|
+
content: logs.buildLogs
|
|
1233
|
+
});
|
|
1234
|
+
else {
|
|
1159
1235
|
console.log(chalk.dim("\n── Logs salvos ──\n"));
|
|
1160
1236
|
process.stdout.write(logs.buildLogs);
|
|
1161
|
-
}
|
|
1237
|
+
}
|
|
1238
|
+
else if (isVerbose && mode === "fancy") console.log(chalk.yellow("[verbose] Nenhum log salvo encontrado para este deployment"));
|
|
1162
1239
|
} catch {
|
|
1163
|
-
if (isVerbose) console.log(chalk.yellow("[verbose] Não foi possível buscar logs de build"));
|
|
1240
|
+
if (isVerbose && mode === "fancy") console.log(chalk.yellow("[verbose] Não foi possível buscar logs de build"));
|
|
1164
1241
|
}
|
|
1165
1242
|
} catch (fetchError) {
|
|
1166
|
-
if (isVerbose) console.log(chalk.red(`[verbose] Erro ao buscar deployment: ${fetchError.message}`));
|
|
1243
|
+
if (isVerbose && mode === "fancy") console.log(chalk.red(`[verbose] Erro ao buscar deployment: ${fetchError.message}`));
|
|
1167
1244
|
}
|
|
1168
1245
|
}
|
|
1169
|
-
|
|
1170
|
-
if (
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1246
|
+
if (mode === "github-actions") endGroup();
|
|
1247
|
+
else if (mode !== "json") console.log(chalk.dim("\n" + "─".repeat(60)));
|
|
1248
|
+
if (finalStatus === "LIVE") {
|
|
1249
|
+
success(serviceName ? `Deploy de ${chalk.bold(serviceName)} concluído! Serviço está ativo.` : "Deploy concluído! Serviço está ativo.");
|
|
1250
|
+
if (mode === "json") emitData({
|
|
1251
|
+
type: "deploy_complete",
|
|
1252
|
+
deploymentId,
|
|
1253
|
+
status: "LIVE",
|
|
1254
|
+
serviceName: serviceName ?? null
|
|
1255
|
+
});
|
|
1256
|
+
} else if (TERMINAL_STATUSES.has(finalStatus)) {
|
|
1257
|
+
const label = statusLabels[finalStatus] ?? finalStatus;
|
|
1258
|
+
const hints = getFailureHints$1(finalStatus);
|
|
1259
|
+
if (mode === "json") emitData({
|
|
1260
|
+
type: "deploy_complete",
|
|
1261
|
+
deploymentId,
|
|
1262
|
+
status: finalStatus,
|
|
1263
|
+
serviceName: serviceName ?? null,
|
|
1264
|
+
hints
|
|
1265
|
+
});
|
|
1266
|
+
else if (mode === "github-actions") {
|
|
1267
|
+
const msg = serviceName ? `Deploy de ${serviceName} finalizou com status: ${label}` : `Deploy finalizou com status: ${label}`;
|
|
1268
|
+
process.stdout.write(`::error::${msg}\n`);
|
|
1269
|
+
for (const hint of hints) process.stdout.write(` ${hint}\n`);
|
|
1270
|
+
} else {
|
|
1271
|
+
const errorMsg = serviceName ? `✗ Deploy de ${chalk.bold(serviceName)} finalizou com status: ${label}` : `✗ Deploy finalizou com status: ${label}`;
|
|
1272
|
+
console.error(chalk.red(errorMsg));
|
|
1273
|
+
for (const hint of hints) console.error(chalk.yellow(` → ${hint}`));
|
|
1274
|
+
}
|
|
1174
1275
|
process.exit(1);
|
|
1175
1276
|
}
|
|
1176
1277
|
}
|
|
@@ -1209,12 +1310,14 @@ function setupSigintHandler() {
|
|
|
1209
1310
|
sigintHandlerRegistered = true;
|
|
1210
1311
|
process.on("SIGINT", async () => {
|
|
1211
1312
|
if (activeDeploymentIds.size === 0) process.exit(130);
|
|
1212
|
-
|
|
1313
|
+
const mode = getOutputMode();
|
|
1314
|
+
if (mode === "json") process.stdout.write(JSON.stringify({ type: "deploy_cancelled" }) + "\n");
|
|
1315
|
+
else console.log(chalk.yellow("\n\nCancelando deploy(s)..."));
|
|
1213
1316
|
try {
|
|
1214
1317
|
const client = await getClient();
|
|
1215
1318
|
const cancelPromises = Array.from(activeDeploymentIds).map((deploymentId) => client.deployments.cancel({ deploymentId }).catch(() => {}));
|
|
1216
1319
|
await Promise.all(cancelPromises);
|
|
1217
|
-
console.log(chalk.yellow("Deploy cancelado."));
|
|
1320
|
+
if (mode !== "json") console.log(chalk.yellow("Deploy cancelado."));
|
|
1218
1321
|
} catch {}
|
|
1219
1322
|
process.exit(130);
|
|
1220
1323
|
});
|
|
@@ -1222,6 +1325,22 @@ function setupSigintHandler() {
|
|
|
1222
1325
|
|
|
1223
1326
|
//#endregion
|
|
1224
1327
|
//#region src/lib/deploy-parallel.ts
|
|
1328
|
+
function getFailureHints(status) {
|
|
1329
|
+
switch (status) {
|
|
1330
|
+
case "BUILD_FAILED": return [
|
|
1331
|
+
"Verifique os logs de build acima para erros de compilação",
|
|
1332
|
+
"Teste o build localmente: rode o comando de build do seu projeto",
|
|
1333
|
+
"Use 'veloz config show' para verificar as configurações"
|
|
1334
|
+
];
|
|
1335
|
+
case "DEPLOY_FAILED": return [
|
|
1336
|
+
"O build passou mas o container falhou ao iniciar",
|
|
1337
|
+
"Verifique se a porta configurada está correta: 'veloz config show'",
|
|
1338
|
+
"Veja os logs de runtime: 'veloz logs -f'"
|
|
1339
|
+
];
|
|
1340
|
+
case "CANCELLED": return ["Deploy cancelado. Execute 'veloz deploy' para tentar novamente."];
|
|
1341
|
+
default: return ["Execute 'veloz logs -f' para mais detalhes."];
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1225
1344
|
function renderProgress(progressMap, prevLineCount) {
|
|
1226
1345
|
for (let i = 0; i < prevLineCount; i++) process.stdout.write("\x1B[1A\x1B[2K");
|
|
1227
1346
|
let lineCount = 0;
|
|
@@ -1251,7 +1370,16 @@ function renderProgress(progressMap, prevLineCount) {
|
|
|
1251
1370
|
}
|
|
1252
1371
|
async function deployServicesInParallel(services) {
|
|
1253
1372
|
const client = await getClient();
|
|
1254
|
-
|
|
1373
|
+
const mode = getOutputMode();
|
|
1374
|
+
if (mode === "json") emitData({
|
|
1375
|
+
type: "parallel_deploy_start",
|
|
1376
|
+
services: services.map((s) => ({
|
|
1377
|
+
serviceId: s.serviceId,
|
|
1378
|
+
serviceName: s.serviceName
|
|
1379
|
+
}))
|
|
1380
|
+
});
|
|
1381
|
+
else if (mode === "github-actions") process.stdout.write(`Iniciando deploy paralelo de ${services.length} serviço(s)\n`);
|
|
1382
|
+
else console.log(chalk.cyan("\n🚀 Iniciando deploy paralelo de múltiplos serviços...\n"));
|
|
1255
1383
|
setupSigintHandler();
|
|
1256
1384
|
const progressMap = /* @__PURE__ */ new Map();
|
|
1257
1385
|
const projectRoot = process.cwd();
|
|
@@ -1270,13 +1398,27 @@ async function deployServicesInParallel(services) {
|
|
|
1270
1398
|
completed: false,
|
|
1271
1399
|
success: false
|
|
1272
1400
|
});
|
|
1273
|
-
|
|
1401
|
+
if (mode === "json") emitData({
|
|
1402
|
+
type: "upload_complete",
|
|
1403
|
+
serviceId: service.serviceId,
|
|
1404
|
+
serviceName: service.serviceName,
|
|
1405
|
+
deploymentId: deployment.id,
|
|
1406
|
+
sizeMB
|
|
1407
|
+
});
|
|
1408
|
+
else if (mode === "github-actions") process.stdout.write(`✓ ${service.serviceName}: Upload concluído (${sizeMB} MB)\n`);
|
|
1409
|
+
else console.log(`${chalk.green("✓")} ${chalk.bold(service.serviceName)}: Upload concluído (${sizeMB} MB)`);
|
|
1274
1410
|
return {
|
|
1275
1411
|
service,
|
|
1276
1412
|
deploymentId: deployment.id
|
|
1277
1413
|
};
|
|
1278
1414
|
} catch (error) {
|
|
1279
|
-
|
|
1415
|
+
if (mode === "json") emitData({
|
|
1416
|
+
type: "upload_failed",
|
|
1417
|
+
serviceId: service.serviceId,
|
|
1418
|
+
serviceName: service.serviceName
|
|
1419
|
+
});
|
|
1420
|
+
else if (mode === "github-actions") process.stdout.write(`::error::${service.serviceName}: Falha ao iniciar deploy\n`);
|
|
1421
|
+
else console.log(`${chalk.red("✗")} ${chalk.bold(service.serviceName)}: Falha ao iniciar deploy`);
|
|
1280
1422
|
progressMap.set(service.serviceId, {
|
|
1281
1423
|
serviceName: service.serviceName,
|
|
1282
1424
|
deploymentId: "",
|
|
@@ -1290,18 +1432,31 @@ async function deployServicesInParallel(services) {
|
|
|
1290
1432
|
});
|
|
1291
1433
|
const activeDeployments = (await Promise.allSettled(deploymentPromises)).filter((d) => d.status === "fulfilled").map((d) => d.value);
|
|
1292
1434
|
if (activeDeployments.length === 0) {
|
|
1293
|
-
|
|
1435
|
+
if (mode === "json") emitData({
|
|
1436
|
+
type: "error",
|
|
1437
|
+
message: "Todos os deploys falharam ao iniciar."
|
|
1438
|
+
});
|
|
1439
|
+
else if (mode === "github-actions") process.stdout.write("::error::Todos os deploys falharam ao iniciar.\n");
|
|
1440
|
+
else console.error(chalk.red("\n✗ Todos os deploys falharam ao iniciar."));
|
|
1294
1441
|
process.exit(1);
|
|
1295
1442
|
}
|
|
1296
|
-
|
|
1297
|
-
|
|
1443
|
+
if (mode === "json") emitData({
|
|
1444
|
+
type: "monitoring_deploys",
|
|
1445
|
+
count: activeDeployments.length
|
|
1446
|
+
});
|
|
1447
|
+
else if (mode === "github-actions") process.stdout.write(`Monitorando ${activeDeployments.length} deploy(s)\n`);
|
|
1448
|
+
else {
|
|
1449
|
+
console.log(chalk.cyan("\n📦 Monitorando progresso dos deploys:\n"));
|
|
1450
|
+
console.log(chalk.dim("─".repeat(60)) + "\n");
|
|
1451
|
+
}
|
|
1298
1452
|
let lineCount = 0;
|
|
1299
|
-
lineCount = renderProgress(progressMap, lineCount);
|
|
1453
|
+
if (mode === "fancy") lineCount = renderProgress(progressMap, lineCount);
|
|
1300
1454
|
const isVerbose = process.env.VELOZ_VERBOSE === "true";
|
|
1301
1455
|
const streamPromises = activeDeployments.map(async ({ service, deploymentId }) => {
|
|
1456
|
+
if (mode === "github-actions") startGroup(`Build: ${service.serviceName}`);
|
|
1302
1457
|
try {
|
|
1303
1458
|
await new Promise((resolve$1) => setTimeout(resolve$1, 1e3));
|
|
1304
|
-
if (isVerbose) console.log(chalk.dim(`\n[verbose] Conectando ao stream para ${service.serviceName} (${deploymentId})...`));
|
|
1459
|
+
if (isVerbose && mode === "fancy") console.log(chalk.dim(`\n[verbose] Conectando ao stream para ${service.serviceName} (${deploymentId})...`));
|
|
1305
1460
|
const stream = await client.logs.streamBuildLogs({ deploymentId });
|
|
1306
1461
|
for await (const event of stream) {
|
|
1307
1462
|
const progress = progressMap.get(service.serviceId);
|
|
@@ -1312,48 +1467,115 @@ async function deployServicesInParallel(services) {
|
|
|
1312
1467
|
progress.completed = true;
|
|
1313
1468
|
progress.success = event.content === "LIVE";
|
|
1314
1469
|
}
|
|
1315
|
-
if (
|
|
1470
|
+
if (mode === "json") emitData({
|
|
1471
|
+
type: "deploy_status",
|
|
1472
|
+
serviceId: service.serviceId,
|
|
1473
|
+
serviceName: service.serviceName,
|
|
1474
|
+
deploymentId,
|
|
1475
|
+
status: event.content
|
|
1476
|
+
});
|
|
1477
|
+
else if (mode === "plain") {
|
|
1478
|
+
const label = statusLabels[event.content] || event.content;
|
|
1479
|
+
process.stdout.write(`[${service.serviceName}] ${label}\n`);
|
|
1480
|
+
}
|
|
1481
|
+
if (isVerbose && mode === "fancy") console.log(chalk.dim(`\n[verbose] ${service.serviceName}: status mudou para ${event.content}`));
|
|
1316
1482
|
} else if (event.type === "log") {
|
|
1317
1483
|
const newLines = event.content.split("\n").filter((l) => l.trim());
|
|
1318
1484
|
progress.logLines.push(...newLines);
|
|
1485
|
+
if (mode === "json") emitData({
|
|
1486
|
+
type: "build_log",
|
|
1487
|
+
serviceId: service.serviceId,
|
|
1488
|
+
serviceName: service.serviceName,
|
|
1489
|
+
deploymentId,
|
|
1490
|
+
lines: newLines
|
|
1491
|
+
});
|
|
1492
|
+
else if (mode === "github-actions" || mode === "plain") {
|
|
1493
|
+
const prefix = mode === "plain" ? `[${service.serviceName}] ` : "";
|
|
1494
|
+
for (const line of newLines) process.stdout.write(`${prefix}${line}\n`);
|
|
1495
|
+
}
|
|
1319
1496
|
}
|
|
1320
|
-
lineCount = renderProgress(progressMap, lineCount);
|
|
1497
|
+
if (mode === "fancy") lineCount = renderProgress(progressMap, lineCount);
|
|
1321
1498
|
}
|
|
1322
1499
|
} catch (error) {
|
|
1323
1500
|
const progress = progressMap.get(service.serviceId);
|
|
1324
1501
|
if (progress && !progress.completed) {
|
|
1325
|
-
if (
|
|
1502
|
+
if (mode === "json") emitData({
|
|
1503
|
+
type: "deploy_stream_error",
|
|
1504
|
+
serviceId: service.serviceId,
|
|
1505
|
+
serviceName: service.serviceName,
|
|
1506
|
+
error: error.message
|
|
1507
|
+
});
|
|
1508
|
+
else if (mode === "github-actions") process.stdout.write(`::error::Error streaming logs for ${service.serviceName}\n`);
|
|
1509
|
+
else if (isVerbose) console.error(`\n${chalk.red("[verbose]")} Error streaming logs for ${service.serviceName}:`, error.message);
|
|
1326
1510
|
else console.error(`\n${chalk.red("✗")} Error streaming logs for ${service.serviceName}`);
|
|
1327
1511
|
progress.status = "FAILED";
|
|
1328
1512
|
progress.completed = true;
|
|
1329
|
-
lineCount = renderProgress(progressMap, lineCount);
|
|
1513
|
+
if (mode === "fancy") lineCount = renderProgress(progressMap, lineCount);
|
|
1330
1514
|
}
|
|
1331
1515
|
} finally {
|
|
1332
1516
|
untrackDeployment(deploymentId);
|
|
1517
|
+
if (mode === "github-actions") endGroup();
|
|
1333
1518
|
}
|
|
1334
1519
|
});
|
|
1335
1520
|
await Promise.all(streamPromises);
|
|
1336
|
-
renderProgress(progressMap, lineCount);
|
|
1337
|
-
console.log(chalk.dim("\n" + "─".repeat(60)));
|
|
1521
|
+
if (mode === "fancy") renderProgress(progressMap, lineCount);
|
|
1338
1522
|
const successful = Array.from(progressMap.values()).filter((p) => p.success);
|
|
1339
1523
|
const failed = Array.from(progressMap.values()).filter((p) => !p.success);
|
|
1340
|
-
if (
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1524
|
+
if (mode === "json") emitData({
|
|
1525
|
+
type: "parallel_deploy_complete",
|
|
1526
|
+
successful: successful.map((p) => ({
|
|
1527
|
+
serviceName: p.serviceName,
|
|
1528
|
+
deploymentId: p.deploymentId
|
|
1529
|
+
})),
|
|
1530
|
+
failed: failed.map((p) => ({
|
|
1531
|
+
serviceName: p.serviceName,
|
|
1532
|
+
deploymentId: p.deploymentId,
|
|
1533
|
+
status: p.status,
|
|
1534
|
+
hints: getFailureHints(p.status)
|
|
1535
|
+
}))
|
|
1536
|
+
});
|
|
1537
|
+
else {
|
|
1538
|
+
if (mode === "fancy") console.log(chalk.dim("\n" + "─".repeat(60)));
|
|
1539
|
+
if (successful.length > 0) if (mode === "github-actions") {
|
|
1540
|
+
process.stdout.write(`\n✓ ${successful.length} serviço(s) implantado(s) com sucesso\n`);
|
|
1541
|
+
for (const progress of successful) process.stdout.write(` ✓ ${progress.serviceName}\n`);
|
|
1542
|
+
} else if (mode === "plain") {
|
|
1543
|
+
process.stdout.write(`\n${successful.length} serviço(s) implantado(s) com sucesso:\n`);
|
|
1544
|
+
for (const progress of successful) process.stdout.write(` ✓ ${progress.serviceName}\n`);
|
|
1545
|
+
} else {
|
|
1546
|
+
console.log(chalk.green(`\n✅ ${successful.length} serviço(s) implantado(s) com sucesso:\n`));
|
|
1547
|
+
for (const progress of successful) console.log(` ${chalk.green("✓")} ${chalk.bold(progress.serviceName)}`);
|
|
1548
|
+
}
|
|
1549
|
+
if (failed.length > 0) if (mode === "github-actions") {
|
|
1550
|
+
process.stdout.write(`\n✗ ${failed.length} serviço(s) falhou(aram)\n`);
|
|
1551
|
+
for (const progress of failed) {
|
|
1552
|
+
process.stdout.write(`::error::${progress.serviceName} falhou (${progress.status})\n`);
|
|
1553
|
+
const hints = getFailureHints(progress.status);
|
|
1554
|
+
for (const hint of hints) process.stdout.write(` ${hint}\n`);
|
|
1555
|
+
}
|
|
1556
|
+
} else if (mode === "plain") {
|
|
1557
|
+
process.stdout.write(`\n${failed.length} serviço(s) falhou(aram):\n`);
|
|
1558
|
+
for (const progress of failed) {
|
|
1559
|
+
process.stdout.write(`\n ✗ ${progress.serviceName} (${progress.status})\n`);
|
|
1560
|
+
const hints = getFailureHints(progress.status);
|
|
1561
|
+
for (const hint of hints) process.stdout.write(` → ${hint}\n`);
|
|
1562
|
+
}
|
|
1563
|
+
} else {
|
|
1564
|
+
console.log(chalk.red(`\n✗ ${failed.length} serviço(s) falhou(aram):\n`));
|
|
1565
|
+
for (const progress of failed) {
|
|
1566
|
+
console.log(` ${chalk.red("✗")} ${chalk.bold(progress.serviceName)} ${chalk.dim(`(${progress.status})`)}`);
|
|
1567
|
+
if (progress.logLines.length > 0) {
|
|
1568
|
+
console.log(chalk.dim("─".repeat(40)));
|
|
1569
|
+
const tail = progress.logLines.slice(-50);
|
|
1570
|
+
for (const line of tail) console.log(` ${chalk.dim(line)}`);
|
|
1571
|
+
console.log(chalk.dim("─".repeat(40)));
|
|
1572
|
+
}
|
|
1573
|
+
const hints = getFailureHints(progress.status);
|
|
1574
|
+
for (const hint of hints) console.log(chalk.yellow(` → ${hint}`));
|
|
1353
1575
|
}
|
|
1354
1576
|
}
|
|
1577
|
+
if (successful.length > 0) info("\nUse 'veloz logs -f' para acompanhar os logs de execução.");
|
|
1355
1578
|
}
|
|
1356
|
-
if (successful.length > 0) info("\nUse 'veloz logs -f' para acompanhar os logs de execução.");
|
|
1357
1579
|
if (failed.length > 0) process.exit(1);
|
|
1358
1580
|
}
|
|
1359
1581
|
|
|
@@ -1374,7 +1596,11 @@ function resolveServiceConf(velozConfig, serviceId) {
|
|
|
1374
1596
|
cpuLimit: merged.resources?.cpu ?? void 0,
|
|
1375
1597
|
memoryLimit: merged.resources?.memory ?? void 0,
|
|
1376
1598
|
healthCheckPath: merged.runtime?.healthCheck?.path ?? null,
|
|
1377
|
-
aptPackages: merged.build?.aptPackages ?? void 0
|
|
1599
|
+
aptPackages: merged.build?.aptPackages ?? void 0,
|
|
1600
|
+
nodeVersion: merged.build?.nodeVersion ?? void 0,
|
|
1601
|
+
nixpkgsArchive: merged.build?.nixpkgsArchive ?? void 0,
|
|
1602
|
+
packageManager: merged.build?.packageManager ?? void 0,
|
|
1603
|
+
installCommand: merged.build?.installCommand ?? void 0
|
|
1378
1604
|
};
|
|
1379
1605
|
}
|
|
1380
1606
|
}
|
|
@@ -1389,7 +1615,11 @@ async function syncServiceConfig(client, serviceId, conf) {
|
|
|
1389
1615
|
startCommand: conf.startCommand,
|
|
1390
1616
|
rootDirectory: conf.rootDirectory,
|
|
1391
1617
|
healthCheckPath: conf.healthCheckPath,
|
|
1392
|
-
aptPackages: conf.aptPackages
|
|
1618
|
+
aptPackages: conf.aptPackages,
|
|
1619
|
+
nodeVersion: conf.nodeVersion,
|
|
1620
|
+
nixpkgsArchive: conf.nixpkgsArchive,
|
|
1621
|
+
packageManager: conf.packageManager,
|
|
1622
|
+
installCommand: conf.installCommand
|
|
1393
1623
|
}));
|
|
1394
1624
|
}
|
|
1395
1625
|
|
|
@@ -1805,8 +2035,20 @@ function runPreDeployChecks(basePath = ".") {
|
|
|
1805
2035
|
*/
|
|
1806
2036
|
function printDeployWarnings(warnings) {
|
|
1807
2037
|
if (warnings.length === 0) return false;
|
|
2038
|
+
const mode = getOutputMode();
|
|
2039
|
+
if (mode === "json") {
|
|
2040
|
+
emitData({
|
|
2041
|
+
type: "deploy_warnings",
|
|
2042
|
+
warnings: warnings.map((w) => ({
|
|
2043
|
+
message: w.message,
|
|
2044
|
+
hint: w.hint
|
|
2045
|
+
}))
|
|
2046
|
+
});
|
|
2047
|
+
return true;
|
|
2048
|
+
}
|
|
1808
2049
|
console.log();
|
|
1809
|
-
for (const w of warnings) {
|
|
2050
|
+
for (const w of warnings) if (mode === "github-actions") process.stdout.write(`::warning::${w.message} — ${w.hint}\n`);
|
|
2051
|
+
else {
|
|
1810
2052
|
console.log(chalk.yellow(` AVISO: ${w.message}`));
|
|
1811
2053
|
console.log(chalk.dim(` ${w.hint}`));
|
|
1812
2054
|
}
|
|
@@ -1843,16 +2085,17 @@ async function computeExtraFilesForServices(services) {
|
|
|
1843
2085
|
warnings
|
|
1844
2086
|
});
|
|
1845
2087
|
}
|
|
1846
|
-
if (allWarnings.length > 0) {
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
}
|
|
1855
|
-
|
|
2088
|
+
if (allWarnings.length > 0) for (const { service, warnings } of allWarnings) output({
|
|
2089
|
+
type: "service_warnings",
|
|
2090
|
+
service,
|
|
2091
|
+
warnings: warnings.map((w) => ({
|
|
2092
|
+
message: w.message,
|
|
2093
|
+
hint: w.hint
|
|
2094
|
+
}))
|
|
2095
|
+
}, () => {
|
|
2096
|
+
console.log(chalk.yellow(`\n ${chalk.bold(service)}:`));
|
|
2097
|
+
printDeployWarnings(warnings);
|
|
2098
|
+
});
|
|
1856
2099
|
for (const svc of services) {
|
|
1857
2100
|
const serviceConf = resolveServiceConf(velozConfig, svc.serviceId);
|
|
1858
2101
|
if (serviceConf) await syncServiceConfig(client, svc.serviceId, serviceConf);
|
|
@@ -1878,10 +2121,6 @@ async function triggerDeploy(serviceId, serviceName, preDetection) {
|
|
|
1878
2121
|
if (warnings.length > 0) {
|
|
1879
2122
|
spinUpload.stop();
|
|
1880
2123
|
printDeployWarnings(warnings);
|
|
1881
|
-
if (!await promptConfirm("Continuar mesmo assim?", false)) {
|
|
1882
|
-
info("Deploy cancelado.");
|
|
1883
|
-
return;
|
|
1884
|
-
}
|
|
1885
2124
|
spinUpload.start();
|
|
1886
2125
|
}
|
|
1887
2126
|
spinUpload.text = "Iniciando deploy...";
|
|
@@ -1926,19 +2165,26 @@ function readLocalFile(path) {
|
|
|
1926
2165
|
}
|
|
1927
2166
|
}
|
|
1928
2167
|
function printSummary(settings) {
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
2168
|
+
const { type: serviceType, ...rest } = settings;
|
|
2169
|
+
output({
|
|
2170
|
+
type: "service_summary",
|
|
2171
|
+
serviceType,
|
|
2172
|
+
...rest
|
|
2173
|
+
}, () => {
|
|
2174
|
+
console.log();
|
|
2175
|
+
console.log(chalk.dim("─".repeat(40)));
|
|
2176
|
+
console.log(` ${chalk.bold("Nome:")} ${settings.name}`);
|
|
2177
|
+
console.log(` ${chalk.bold("Tipo:")} ${settings.type === "WEB" ? "Serviço Web" : "Site Estático"}`);
|
|
2178
|
+
console.log(` ${chalk.bold("Branch:")} ${settings.branch}`);
|
|
2179
|
+
if (settings.framework) console.log(` ${chalk.bold("Framework:")} ${settings.framework}`);
|
|
2180
|
+
if (settings.packageManager) console.log(` ${chalk.bold("Package Mgr:")} ${settings.packageManager}`);
|
|
2181
|
+
if (settings.buildCommand) console.log(` ${chalk.bold("Build:")} ${settings.buildCommand}`);
|
|
2182
|
+
if (settings.startCommand) console.log(` ${chalk.bold("Start:")} ${settings.startCommand}`);
|
|
2183
|
+
if (settings.outputDir) console.log(` ${chalk.bold("Output:")} ${settings.outputDir}`);
|
|
2184
|
+
if (settings.port) console.log(` ${chalk.bold("Porta:")} ${settings.port}`);
|
|
2185
|
+
console.log(chalk.dim("─".repeat(40)));
|
|
2186
|
+
console.log();
|
|
2187
|
+
});
|
|
1942
2188
|
}
|
|
1943
2189
|
function detectLocalRepo(basePath = ".") {
|
|
1944
2190
|
const files = {};
|
|
@@ -2010,13 +2256,13 @@ function detectLocalRepo(basePath = ".") {
|
|
|
2010
2256
|
async function promptEnvVars(serviceId, detectedVars) {
|
|
2011
2257
|
if (detectedVars.length === 0) return;
|
|
2012
2258
|
console.log(chalk.cyan(`\n📝 ${detectedVars.length} variável(is) de ambiente detectada(s):\n`));
|
|
2013
|
-
for (const v of detectedVars) console.log(` • ${v
|
|
2259
|
+
for (const v of detectedVars) console.log(` • ${v}`);
|
|
2014
2260
|
console.log();
|
|
2015
2261
|
if (!await promptConfirm("Deseja preencher as variáveis agora?", false)) return;
|
|
2016
2262
|
const vars = {};
|
|
2017
|
-
for (const
|
|
2018
|
-
const
|
|
2019
|
-
if (
|
|
2263
|
+
for (const key of detectedVars) {
|
|
2264
|
+
const val = await prompt(` ${chalk.bold(key)}:`);
|
|
2265
|
+
if (val) vars[key] = val;
|
|
2020
2266
|
}
|
|
2021
2267
|
const filled = Object.keys(vars).length;
|
|
2022
2268
|
if (filled > 0) {
|
|
@@ -2037,50 +2283,54 @@ async function createServiceFlow(projectId, projectName, repoName, opts = {}) {
|
|
|
2037
2283
|
const detection = detectLocalRepo();
|
|
2038
2284
|
spinDetect.stop();
|
|
2039
2285
|
const pm = detection.packageManager;
|
|
2040
|
-
if (detection.isMonorepo && detection.
|
|
2286
|
+
if (detection.isMonorepo && detection.services.length > 0) {
|
|
2041
2287
|
info(`Monorepo detectado (${pm})`);
|
|
2042
2288
|
let selectedApps;
|
|
2043
2289
|
if (opts.yes) if (opts.app) {
|
|
2044
|
-
selectedApps = detection.
|
|
2290
|
+
selectedApps = detection.services.filter((a) => a.root === opts.app);
|
|
2045
2291
|
if (selectedApps.length === 0) {
|
|
2046
|
-
|
|
2047
|
-
|
|
2292
|
+
output({
|
|
2293
|
+
type: "error",
|
|
2294
|
+
message: `App '${opts.app}' não encontrado.`,
|
|
2295
|
+
available: detection.services.map((a) => a.root)
|
|
2296
|
+
}, () => {
|
|
2297
|
+
const available = detection.services.map((a) => ` • ${a.root}`).join("\n");
|
|
2298
|
+
console.error(chalk.red(`\n✗ App '${opts.app}' não encontrado.\n\nApps disponíveis:\n${available}`));
|
|
2299
|
+
});
|
|
2048
2300
|
process.exit(1);
|
|
2049
2301
|
}
|
|
2050
|
-
} else selectedApps = detection.
|
|
2302
|
+
} else selectedApps = detection.services;
|
|
2051
2303
|
else {
|
|
2052
|
-
const selectedPaths = await promptMultiSelect("Quais apps deseja fazer o deploy?", detection.
|
|
2053
|
-
label: `${app.name}${app.framework ? ` (${app.framework
|
|
2054
|
-
value: app.
|
|
2304
|
+
const selectedPaths = await promptMultiSelect("Quais apps deseja fazer o deploy?", detection.services.map((app) => ({
|
|
2305
|
+
label: `${app.name}${app.framework ? ` (${app.framework})` : ""} — ${app.root}`,
|
|
2306
|
+
value: app.root
|
|
2055
2307
|
})));
|
|
2056
|
-
selectedApps = detection.
|
|
2308
|
+
selectedApps = detection.services.filter((a) => selectedPaths.includes(a.root));
|
|
2057
2309
|
}
|
|
2058
2310
|
for (const app of selectedApps) {
|
|
2059
|
-
const fw$1 = app.framework;
|
|
2060
2311
|
console.log(chalk.cyan(`\n── ${app.name} ──`));
|
|
2061
2312
|
printSummary({
|
|
2062
2313
|
name: app.name,
|
|
2063
|
-
type:
|
|
2064
|
-
rootDir: app.
|
|
2314
|
+
type: "WEB",
|
|
2315
|
+
rootDir: app.root,
|
|
2065
2316
|
branch,
|
|
2066
|
-
framework:
|
|
2317
|
+
framework: app.framework,
|
|
2067
2318
|
packageManager: pm,
|
|
2068
|
-
buildCommand:
|
|
2069
|
-
startCommand:
|
|
2070
|
-
outputDir:
|
|
2071
|
-
port:
|
|
2319
|
+
buildCommand: app.buildCommand,
|
|
2320
|
+
startCommand: app.startCommand,
|
|
2321
|
+
outputDir: null,
|
|
2322
|
+
port: app.port ?? 3e3
|
|
2072
2323
|
});
|
|
2073
2324
|
}
|
|
2074
2325
|
if (!opts.yes) {
|
|
2075
2326
|
if (!await promptConfirm("Confirmar e fazer deploy?")) for (const app of selectedApps) {
|
|
2076
|
-
const fw$1 = app.framework;
|
|
2077
2327
|
console.log(chalk.cyan(`\n── Editar: ${app.name} ──`));
|
|
2078
|
-
const newBuild = await prompt(` Build command: ${chalk.dim(`(${
|
|
2079
|
-
if (newBuild
|
|
2080
|
-
const newStart = await prompt(` Start command: ${chalk.dim(`(${
|
|
2081
|
-
if (newStart
|
|
2082
|
-
const newPort = await prompt(` Port: ${chalk.dim(`(${
|
|
2083
|
-
if (newPort
|
|
2328
|
+
const newBuild = await prompt(` Build command: ${chalk.dim(`(${app.buildCommand ?? "—"})`)}`);
|
|
2329
|
+
if (newBuild) app.buildCommand = newBuild;
|
|
2330
|
+
const newStart = await prompt(` Start command: ${chalk.dim(`(${app.startCommand ?? "—"})`)}`);
|
|
2331
|
+
if (newStart) app.startCommand = newStart;
|
|
2332
|
+
const newPort = await prompt(` Port: ${chalk.dim(`(${app.port ?? 3e3})`)}`);
|
|
2333
|
+
if (newPort) app.port = parseInt(newPort, 10) || app.port;
|
|
2084
2334
|
}
|
|
2085
2335
|
}
|
|
2086
2336
|
const config = {
|
|
@@ -2094,18 +2344,17 @@ async function createServiceFlow(projectId, projectName, repoName, opts = {}) {
|
|
|
2094
2344
|
};
|
|
2095
2345
|
const createdServices = [];
|
|
2096
2346
|
for (const app of selectedApps) {
|
|
2097
|
-
const fw$1 = app.framework;
|
|
2098
2347
|
console.log(chalk.cyan(`\n── Criando serviço: ${app.name} ──\n`));
|
|
2099
2348
|
const spinService$1 = spinner(`Criando serviço ${chalk.bold(app.name)}...`);
|
|
2100
2349
|
const service$1 = await withRetry(() => client.services.create({
|
|
2101
2350
|
projectId,
|
|
2102
2351
|
name: app.name,
|
|
2103
|
-
type:
|
|
2352
|
+
type: "WEB",
|
|
2104
2353
|
branch,
|
|
2105
|
-
rootDirectory: app.
|
|
2106
|
-
buildCommand:
|
|
2107
|
-
startCommand:
|
|
2108
|
-
port:
|
|
2354
|
+
rootDirectory: app.root,
|
|
2355
|
+
buildCommand: app.buildCommand ?? void 0,
|
|
2356
|
+
startCommand: app.startCommand ?? void 0,
|
|
2357
|
+
port: app.port ?? 3e3
|
|
2109
2358
|
}));
|
|
2110
2359
|
spinService$1.stop();
|
|
2111
2360
|
success(`Serviço criado: ${chalk.bold(service$1.name)}`);
|
|
@@ -2113,19 +2362,16 @@ async function createServiceFlow(projectId, projectName, repoName, opts = {}) {
|
|
|
2113
2362
|
service: service$1,
|
|
2114
2363
|
app
|
|
2115
2364
|
});
|
|
2116
|
-
config.services[app.
|
|
2365
|
+
config.services[app.root] = {
|
|
2117
2366
|
id: service$1.id,
|
|
2118
2367
|
name: service$1.name,
|
|
2119
|
-
type:
|
|
2120
|
-
root: app.
|
|
2368
|
+
type: "web",
|
|
2369
|
+
root: app.root,
|
|
2121
2370
|
branch,
|
|
2122
|
-
build:
|
|
2123
|
-
command: fw$1?.buildCommand ?? void 0,
|
|
2124
|
-
outputDir: fw$1?.outputDir ?? void 0
|
|
2125
|
-
} : void 0,
|
|
2371
|
+
build: app.buildCommand ? { command: app.buildCommand ?? void 0 } : void 0,
|
|
2126
2372
|
runtime: {
|
|
2127
|
-
command:
|
|
2128
|
-
port:
|
|
2373
|
+
command: app.startCommand ?? void 0,
|
|
2374
|
+
port: app.port ?? 3e3
|
|
2129
2375
|
}
|
|
2130
2376
|
};
|
|
2131
2377
|
}
|
|
@@ -2133,38 +2379,36 @@ async function createServiceFlow(projectId, projectName, repoName, opts = {}) {
|
|
|
2133
2379
|
info(`Arquivo ${getConfigFileName()} criado na raiz do projeto.`);
|
|
2134
2380
|
if (!opts.yes) for (const { service: service$1, app } of createdServices) {
|
|
2135
2381
|
console.log(chalk.cyan(`\n── Configurando variáveis: ${app.name} ──\n`));
|
|
2136
|
-
const serviceDetection = detectLocalRepo(app.
|
|
2382
|
+
const serviceDetection = detectLocalRepo(app.root);
|
|
2137
2383
|
await promptEnvVars(service$1.id, serviceDetection.envVars);
|
|
2138
2384
|
}
|
|
2139
2385
|
await deployServicesInParallel(createdServices.map(({ service: service$1, app }) => ({
|
|
2140
2386
|
serviceId: service$1.id,
|
|
2141
2387
|
serviceName: app.name,
|
|
2142
|
-
path: resolve(process.cwd(), app.
|
|
2143
|
-
extraFiles: prepareExtraFiles(detectLocalRepo(app.
|
|
2144
|
-
type:
|
|
2145
|
-
buildCommand: app.
|
|
2146
|
-
startCommand: app.
|
|
2147
|
-
port: app.
|
|
2148
|
-
rootDirectory: app.
|
|
2149
|
-
outputDir: app.framework?.outputDir ?? void 0
|
|
2388
|
+
path: resolve(process.cwd(), app.root),
|
|
2389
|
+
extraFiles: prepareExtraFiles(detectLocalRepo(app.root), {
|
|
2390
|
+
type: "WEB",
|
|
2391
|
+
buildCommand: app.buildCommand ?? void 0,
|
|
2392
|
+
startCommand: app.startCommand ?? void 0,
|
|
2393
|
+
port: app.port ?? void 0,
|
|
2394
|
+
rootDirectory: app.root
|
|
2150
2395
|
})
|
|
2151
2396
|
})));
|
|
2152
2397
|
return createdServices[createdServices.length - 1]?.service.id || "";
|
|
2153
2398
|
}
|
|
2154
|
-
const fw = detection.framework;
|
|
2155
2399
|
const settings = {
|
|
2156
2400
|
name: repoName,
|
|
2157
|
-
type:
|
|
2401
|
+
type: "WEB",
|
|
2158
2402
|
rootDir: ".",
|
|
2159
2403
|
branch,
|
|
2160
|
-
framework:
|
|
2404
|
+
framework: detection.framework,
|
|
2161
2405
|
packageManager: pm,
|
|
2162
|
-
buildCommand:
|
|
2163
|
-
startCommand:
|
|
2164
|
-
outputDir:
|
|
2165
|
-
port:
|
|
2406
|
+
buildCommand: detection.buildCommand,
|
|
2407
|
+
startCommand: detection.startCommand,
|
|
2408
|
+
outputDir: null,
|
|
2409
|
+
port: detection.port ?? 3e3
|
|
2166
2410
|
};
|
|
2167
|
-
if (
|
|
2411
|
+
if (detection.framework) info(`Framework detectado: ${chalk.bold(detection.framework)}`);
|
|
2168
2412
|
printSummary(settings);
|
|
2169
2413
|
if (!opts.yes) {
|
|
2170
2414
|
if (!await promptConfirm("Confirmar e fazer deploy?")) {
|
|
@@ -2234,8 +2478,17 @@ const deployCommand = new Command("deploy").description("Fazer deploy do serviç
|
|
|
2234
2478
|
if (options.service) {
|
|
2235
2479
|
const found = configuredServices.find((s) => s.key === options.service || s.serviceName.toLowerCase() === options.service.toLowerCase() || s.serviceId === options.service);
|
|
2236
2480
|
if (!found) {
|
|
2237
|
-
|
|
2238
|
-
|
|
2481
|
+
output({
|
|
2482
|
+
type: "error",
|
|
2483
|
+
message: `Serviço '${options.service}' não encontrado.`,
|
|
2484
|
+
available: configuredServices.map((s) => ({
|
|
2485
|
+
key: s.key,
|
|
2486
|
+
name: s.serviceName
|
|
2487
|
+
}))
|
|
2488
|
+
}, () => {
|
|
2489
|
+
const available = configuredServices.map((s) => ` • ${s.key} (${s.serviceName})`).join("\n");
|
|
2490
|
+
console.error(chalk.red(`\n✗ Serviço '${options.service}' não encontrado.\n\nServiços disponíveis:\n${available}`));
|
|
2491
|
+
});
|
|
2239
2492
|
process.exit(1);
|
|
2240
2493
|
}
|
|
2241
2494
|
await triggerDeploy(found.serviceId, found.serviceName);
|
|
@@ -2276,17 +2529,17 @@ const deployCommand = new Command("deploy").description("Fazer deploy do serviç
|
|
|
2276
2529
|
return;
|
|
2277
2530
|
}
|
|
2278
2531
|
}
|
|
2279
|
-
if (!isGitRepo())
|
|
2280
|
-
console.error(chalk.red("\n✗ Este diretório não é um repositório git. Inicialize com `git init` e adicione um remote."));
|
|
2281
|
-
process.exit(1);
|
|
2282
|
-
}
|
|
2532
|
+
if (!isGitRepo()) handleError(/* @__PURE__ */ new Error("Este diretório não é um repositório git. Inicialize com `git init` e adicione um remote."));
|
|
2283
2533
|
info("Detectando repositório git...");
|
|
2284
2534
|
const remote = getGitRemote();
|
|
2285
|
-
if (!remote)
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2535
|
+
if (!remote) handleError(/* @__PURE__ */ new Error("Nenhum remote 'origin' encontrado. Adicione um remote git."));
|
|
2536
|
+
output({
|
|
2537
|
+
type: "git_repo",
|
|
2538
|
+
owner: remote.owner,
|
|
2539
|
+
repo: remote.repo
|
|
2540
|
+
}, () => {
|
|
2541
|
+
console.log(chalk.white(` ${chalk.bold("Repositório:")} ${remote.owner}/${remote.repo}`));
|
|
2542
|
+
});
|
|
2290
2543
|
const client = await getClient();
|
|
2291
2544
|
const spin = spinner("Buscando projeto...");
|
|
2292
2545
|
const project = await withRetry(() => client.projects.findByRepo({
|
|
@@ -2315,7 +2568,7 @@ const deployCommand = new Command("deploy").description("Fazer deploy do serviç
|
|
|
2315
2568
|
}
|
|
2316
2569
|
const selectedService = project.services.find((s) => s.id === serviceId);
|
|
2317
2570
|
const detection = detectLocalRepo();
|
|
2318
|
-
const detectedType =
|
|
2571
|
+
const detectedType = selectedService?.type?.toLowerCase() ?? "web";
|
|
2319
2572
|
saveConfig({
|
|
2320
2573
|
version: "1.0",
|
|
2321
2574
|
project: {
|
|
@@ -2399,10 +2652,14 @@ const TAG_COLORS = [
|
|
|
2399
2652
|
chalk.red
|
|
2400
2653
|
];
|
|
2401
2654
|
function getServiceTag(name, maxLen, colorIndex) {
|
|
2655
|
+
const mode = getOutputMode();
|
|
2656
|
+
if (mode === "json" || mode === "github-actions" || mode === "plain") return `[${name.padEnd(maxLen)}]`;
|
|
2402
2657
|
const color = TAG_COLORS[colorIndex % TAG_COLORS.length];
|
|
2403
2658
|
return color(`[${name.padEnd(maxLen)}]`);
|
|
2404
2659
|
}
|
|
2405
2660
|
function getServiceHeader(name, colorIndex) {
|
|
2661
|
+
const mode = getOutputMode();
|
|
2662
|
+
if (mode === "json" || mode === "github-actions" || mode === "plain") return `── ${name} ──`;
|
|
2406
2663
|
const color = TAG_COLORS[colorIndex % TAG_COLORS.length];
|
|
2407
2664
|
return color(`── ${name} ──`);
|
|
2408
2665
|
}
|
|
@@ -2511,7 +2768,14 @@ async function streamFollow(services, maxNameLen, tailLines) {
|
|
|
2511
2768
|
serviceId: service.id,
|
|
2512
2769
|
tailLines: Math.ceil(tailLines / services.length)
|
|
2513
2770
|
});
|
|
2514
|
-
for await (const entry of stream)
|
|
2771
|
+
for await (const entry of stream) output({
|
|
2772
|
+
type: "log_entry",
|
|
2773
|
+
service: service.name,
|
|
2774
|
+
timestamp: entry.timestamp,
|
|
2775
|
+
message: entry.message
|
|
2776
|
+
}, () => {
|
|
2777
|
+
console.log(`${tag}${formatTime(entry.timestamp)} ${entry.message}`);
|
|
2778
|
+
});
|
|
2515
2779
|
} catch {
|
|
2516
2780
|
console.log(`${tag}${chalk.red("Erro ao conectar ao stream de logs")}`);
|
|
2517
2781
|
}
|
|
@@ -2535,13 +2799,22 @@ async function fetchRecent(services, maxNameLen, tailLines) {
|
|
|
2535
2799
|
info("Nenhum log encontrado.");
|
|
2536
2800
|
return;
|
|
2537
2801
|
}
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2802
|
+
output({
|
|
2803
|
+
type: "logs",
|
|
2804
|
+
entries: allEntries.map((e) => ({
|
|
2805
|
+
service: e.serviceName,
|
|
2806
|
+
timestamp: e.timestamp,
|
|
2807
|
+
message: e.message
|
|
2808
|
+
}))
|
|
2809
|
+
}, () => {
|
|
2810
|
+
console.log();
|
|
2811
|
+
for (const entry of allEntries) {
|
|
2812
|
+
const tag = showTags ? `${getServiceTag(entry.serviceName, maxNameLen, entry.serviceIndex)} ` : "";
|
|
2813
|
+
console.log(`${tag}${formatTime(entry.timestamp)} ${entry.message}`);
|
|
2814
|
+
}
|
|
2815
|
+
console.log();
|
|
2816
|
+
info(`Mostrando ${allEntries.length} linha(s). Use ${chalk.bold("--follow")} para acompanhar em tempo real.`);
|
|
2817
|
+
});
|
|
2545
2818
|
}
|
|
2546
2819
|
const logsCommand = new Command("logs").description("Visualizar logs dos serviços").option("-f, --follow", "Acompanhar logs em tempo real").option("-n, --tail <linhas>", "Número de linhas recentes", "50").option("--service <service>", "Filtrar por serviço (chave ou nome)").action(async (opts) => {
|
|
2547
2820
|
const spin = spinner("Carregando logs...");
|
|
@@ -2589,7 +2862,11 @@ envCommand.command("list").alias("listar").description("Listar variáveis de amb
|
|
|
2589
2862
|
chalk.bold(v.key),
|
|
2590
2863
|
chalk.dim(v.maskedValue),
|
|
2591
2864
|
new Date(v.updatedAt).toLocaleDateString("pt-BR")
|
|
2592
|
-
]))
|
|
2865
|
+
]), envVars.map((v) => ({
|
|
2866
|
+
key: v.key,
|
|
2867
|
+
maskedValue: v.maskedValue,
|
|
2868
|
+
updatedAt: v.updatedAt
|
|
2869
|
+
})));
|
|
2593
2870
|
}
|
|
2594
2871
|
spin.stop();
|
|
2595
2872
|
if (totalVars === 0 && !showHeaders) info("Nenhuma variável de ambiente configurada.");
|
|
@@ -2607,15 +2884,13 @@ envCommand.command("set <pares...>").description("Definir variável de ambiente
|
|
|
2607
2884
|
const eqIndex = par.indexOf("=");
|
|
2608
2885
|
if (eqIndex === -1) {
|
|
2609
2886
|
spin.stop();
|
|
2610
|
-
|
|
2611
|
-
process.exit(1);
|
|
2887
|
+
handleError(/* @__PURE__ */ new Error(`Formato inválido: "${par}". Use CHAVE=VALOR.`));
|
|
2612
2888
|
}
|
|
2613
2889
|
const key = par.slice(0, eqIndex);
|
|
2614
2890
|
const value = par.slice(eqIndex + 1);
|
|
2615
2891
|
if (!key) {
|
|
2616
2892
|
spin.stop();
|
|
2617
|
-
|
|
2618
|
-
process.exit(1);
|
|
2893
|
+
handleError(/* @__PURE__ */ new Error("Chave não pode estar vazia."));
|
|
2619
2894
|
}
|
|
2620
2895
|
await client.envVars.set({
|
|
2621
2896
|
serviceId,
|
|
@@ -2657,38 +2932,32 @@ envCommand.command("import [arquivo]").description("Importar variáveis de ambie
|
|
|
2657
2932
|
let envContent = "";
|
|
2658
2933
|
if (arquivo) {
|
|
2659
2934
|
const filePath = resolve(process.cwd(), arquivo);
|
|
2660
|
-
if (!existsSync(filePath)) {
|
|
2661
|
-
console.error(chalk.red(`\n✗ Arquivo não encontrado: ${arquivo}`));
|
|
2662
|
-
process.exit(1);
|
|
2663
|
-
}
|
|
2935
|
+
if (!existsSync(filePath)) handleError(/* @__PURE__ */ new Error(`Arquivo não encontrado: ${arquivo}`));
|
|
2664
2936
|
envContent = readFileSync(filePath, "utf-8");
|
|
2665
2937
|
info(`Importando de ${chalk.bold(arquivo)}...`);
|
|
2666
2938
|
} else {
|
|
2667
|
-
console.log(chalk.cyan("\n📋 Modo de colagem interativo"));
|
|
2668
|
-
console.log(chalk.dim("Cole seu conteúdo .env abaixo. Pressione Ctrl+D (ou Ctrl+Z no Windows) quando terminar:\n"));
|
|
2669
2939
|
const rl = readline.createInterface({
|
|
2670
2940
|
input: process.stdin,
|
|
2671
|
-
output: process.stdout
|
|
2941
|
+
output: isInteractive() ? process.stdout : void 0
|
|
2672
2942
|
});
|
|
2673
|
-
|
|
2943
|
+
if (isInteractive()) {
|
|
2944
|
+
console.log(chalk.cyan("\n📋 Modo de colagem interativo"));
|
|
2945
|
+
console.log(chalk.dim("Cole seu conteúdo .env abaixo. Pressione Ctrl+D (ou Ctrl+Z no Windows) quando terminar:\n"));
|
|
2946
|
+
}
|
|
2947
|
+
const lines = [];
|
|
2674
2948
|
await new Promise((resolve$1) => {
|
|
2675
|
-
rl.on("line", (line) =>
|
|
2676
|
-
|
|
2677
|
-
});
|
|
2678
|
-
rl.on("close", () => {
|
|
2679
|
-
resolve$1();
|
|
2680
|
-
});
|
|
2949
|
+
rl.on("line", (line) => lines.push(line));
|
|
2950
|
+
rl.on("close", () => resolve$1());
|
|
2681
2951
|
});
|
|
2682
|
-
envContent = lines
|
|
2952
|
+
envContent = lines.join("\n");
|
|
2683
2953
|
}
|
|
2684
2954
|
const envVars = {};
|
|
2685
|
-
const
|
|
2686
|
-
for (const line of lines) {
|
|
2955
|
+
for (const line of envContent.split("\n")) {
|
|
2687
2956
|
const trimmed = line.trim();
|
|
2688
2957
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
2689
2958
|
const eqIndex = trimmed.indexOf("=");
|
|
2690
2959
|
if (eqIndex === -1) continue;
|
|
2691
|
-
|
|
2960
|
+
const key = trimmed.slice(0, eqIndex).trim();
|
|
2692
2961
|
let value = trimmed.slice(eqIndex + 1).trim();
|
|
2693
2962
|
if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
|
|
2694
2963
|
value = value.replace(/\\n/g, "\n").replace(/\\r/g, "\r").replace(/\\t/g, " ").replace(/\\\\/g, "\\");
|
|
@@ -2696,7 +2965,7 @@ envCommand.command("import [arquivo]").description("Importar variáveis de ambie
|
|
|
2696
2965
|
}
|
|
2697
2966
|
const varsCount = Object.keys(envVars).length;
|
|
2698
2967
|
if (varsCount === 0) {
|
|
2699
|
-
|
|
2968
|
+
warn("Nenhuma variável válida encontrada.");
|
|
2700
2969
|
return;
|
|
2701
2970
|
}
|
|
2702
2971
|
console.log(chalk.bold("\n📝 Variáveis a serem importadas:\n"));
|
|
@@ -2745,20 +3014,31 @@ envCommand.command("export [arquivo]").description("Exportar variáveis de ambie
|
|
|
2745
3014
|
return;
|
|
2746
3015
|
}
|
|
2747
3016
|
const envContent = envVars.map((v) => `${v.key}=${v.maskedValue}`).join("\n");
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
3017
|
+
output({
|
|
3018
|
+
type: "env_export",
|
|
3019
|
+
vars: envVars.map((v) => ({
|
|
3020
|
+
key: v.key,
|
|
3021
|
+
maskedValue: v.maskedValue
|
|
3022
|
+
}))
|
|
3023
|
+
}, () => {
|
|
3024
|
+
if (arquivo) {
|
|
3025
|
+
writeFileSync(resolve(process.cwd(), arquivo), envContent + "\n", "utf-8");
|
|
3026
|
+
success(`Variáveis exportadas para ${chalk.bold(arquivo)}`);
|
|
3027
|
+
console.log(chalk.dim("Nota: Valores estão mascarados por segurança."));
|
|
3028
|
+
} else {
|
|
3029
|
+
console.log(chalk.bold("\n# Variáveis de Ambiente (valores mascarados)\n"));
|
|
3030
|
+
console.log(envContent);
|
|
3031
|
+
console.log();
|
|
3032
|
+
}
|
|
3033
|
+
});
|
|
2757
3034
|
} catch (error) {
|
|
2758
3035
|
spin.stop();
|
|
2759
3036
|
handleError(error);
|
|
2760
3037
|
}
|
|
2761
3038
|
});
|
|
3039
|
+
function warn(message) {
|
|
3040
|
+
console.log(chalk.yellow(`⚠ ${message}`));
|
|
3041
|
+
}
|
|
2762
3042
|
|
|
2763
3043
|
//#endregion
|
|
2764
3044
|
//#region src/commands/domains.ts
|
|
@@ -2791,12 +3071,18 @@ domainsCommand.command("list").alias("listar").description("Listar domínios dos
|
|
|
2791
3071
|
"TLS",
|
|
2792
3072
|
"Tipo"
|
|
2793
3073
|
], domains.map((d) => [
|
|
2794
|
-
d.id
|
|
3074
|
+
d.id,
|
|
2795
3075
|
chalk.bold(d.domain),
|
|
2796
3076
|
d.verified ? chalk.green("✓ Sim") : chalk.yellow("✗ Não"),
|
|
2797
3077
|
tlsStatusLabels[d.tlsStatus] ?? d.tlsStatus,
|
|
2798
3078
|
d.isAutoGenerated ? "Auto" : "Personalizado"
|
|
2799
|
-
]))
|
|
3079
|
+
]), domains.map((d) => ({
|
|
3080
|
+
id: d.id,
|
|
3081
|
+
domain: d.domain,
|
|
3082
|
+
verified: d.verified,
|
|
3083
|
+
tlsStatus: d.tlsStatus,
|
|
3084
|
+
isAutoGenerated: d.isAutoGenerated
|
|
3085
|
+
})));
|
|
2800
3086
|
}
|
|
2801
3087
|
spin.stop();
|
|
2802
3088
|
if (totalDomains === 0 && !showHeaders) info("Nenhum domínio configurado.");
|
|
@@ -2814,12 +3100,19 @@ domainsCommand.command("add <dominio>").alias("adicionar").description("Adiciona
|
|
|
2814
3100
|
domain: dominio
|
|
2815
3101
|
});
|
|
2816
3102
|
spin.stop();
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
3103
|
+
output({
|
|
3104
|
+
type: "domain_added",
|
|
3105
|
+
id: result.id,
|
|
3106
|
+
domain: dominio,
|
|
3107
|
+
dnsInstruction: result.dnsInstruction
|
|
3108
|
+
}, () => {
|
|
3109
|
+
success(`Domínio ${chalk.bold(dominio)} adicionado com sucesso!`);
|
|
3110
|
+
console.log();
|
|
3111
|
+
console.log(chalk.yellow.bold(" Instruções de DNS:"));
|
|
3112
|
+
console.log(chalk.white(` ${result.dnsInstruction}`));
|
|
3113
|
+
console.log();
|
|
3114
|
+
info(`Após configurar o DNS, execute: ${chalk.bold(`veloz domains verify ${result.id}`)}`);
|
|
3115
|
+
});
|
|
2823
3116
|
} catch (error) {
|
|
2824
3117
|
spin.stop();
|
|
2825
3118
|
handleError(error);
|
|
@@ -2830,13 +3123,19 @@ domainsCommand.command("verify <domainId>").alias("verificar").description("Veri
|
|
|
2830
3123
|
try {
|
|
2831
3124
|
const result = await (await getClient()).domains.verify({ domainId });
|
|
2832
3125
|
spin.stop();
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
3126
|
+
output({
|
|
3127
|
+
type: "domain_verified",
|
|
3128
|
+
domain: result.domain.domain,
|
|
3129
|
+
verified: result.verified
|
|
3130
|
+
}, () => {
|
|
3131
|
+
if (result.verified) {
|
|
3132
|
+
success(`Domínio ${chalk.bold(result.domain.domain)} verificado com sucesso!`);
|
|
3133
|
+
info("O certificado TLS será provisionado automaticamente.");
|
|
3134
|
+
} else {
|
|
3135
|
+
console.log(chalk.yellow(`\n⚠ Domínio ${chalk.bold(result.domain.domain)} ainda não verificado.`));
|
|
3136
|
+
info("Verifique se o CNAME foi propagado e tente novamente.");
|
|
3137
|
+
}
|
|
3138
|
+
});
|
|
2840
3139
|
} catch (error) {
|
|
2841
3140
|
spin.stop();
|
|
2842
3141
|
handleError(error);
|
|
@@ -2879,14 +3178,22 @@ configCommand.command("show").description("Mostrar configurações atuais dos se
|
|
|
2879
3178
|
const client = await getClient();
|
|
2880
3179
|
const showHeaders = services.length > 1;
|
|
2881
3180
|
const spin = spinner("Buscando configurações...");
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
if (showHeaders) console.log(`\n${getServiceHeader(svcConfig.name, index)}\n`);
|
|
2885
|
-
else console.log(chalk.bold("\n📋 Configurações do Serviço\n"));
|
|
2886
|
-
printServiceConfig(service);
|
|
2887
|
-
console.log();
|
|
2888
|
-
}
|
|
3181
|
+
const configs = [];
|
|
3182
|
+
for (const { service: svcConfig } of services) configs.push(await client.services.get({ serviceId: svcConfig.id }));
|
|
2889
3183
|
spin.stop();
|
|
3184
|
+
output({
|
|
3185
|
+
type: "service_configs",
|
|
3186
|
+
services: configs
|
|
3187
|
+
}, () => {
|
|
3188
|
+
for (let i = 0; i < services.length; i++) {
|
|
3189
|
+
const { service: svcConfig, index } = services[i];
|
|
3190
|
+
const service = configs[i];
|
|
3191
|
+
if (showHeaders) console.log(`\n${getServiceHeader(svcConfig.name, index)}\n`);
|
|
3192
|
+
else console.log(chalk.bold("\n📋 Configurações do Serviço\n"));
|
|
3193
|
+
printServiceConfig(service);
|
|
3194
|
+
console.log();
|
|
3195
|
+
}
|
|
3196
|
+
});
|
|
2890
3197
|
} catch (error) {
|
|
2891
3198
|
handleError(error);
|
|
2892
3199
|
}
|
|
@@ -2917,13 +3224,18 @@ configCommand.command("set").description("Atualizar configurações do serviço"
|
|
|
2917
3224
|
});
|
|
2918
3225
|
spin.stop();
|
|
2919
3226
|
success("Configurações atualizadas com sucesso!");
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
3227
|
+
output({
|
|
3228
|
+
type: "config_updated",
|
|
3229
|
+
updates
|
|
3230
|
+
}, () => {
|
|
3231
|
+
console.log(chalk.dim("\nValores atualizados:"));
|
|
3232
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
3233
|
+
const displayKey = key.replace(/([A-Z])/g, " $1").replace(/^./, (str) => str.toUpperCase()).trim();
|
|
3234
|
+
console.log(` ${chalk.bold(displayKey)}: ${formatValue(value)}`);
|
|
3235
|
+
}
|
|
3236
|
+
console.log();
|
|
3237
|
+
info("Execute 'veloz deploy' para aplicar as mudanças.");
|
|
3238
|
+
});
|
|
2927
3239
|
} catch (error) {
|
|
2928
3240
|
handleError(error);
|
|
2929
3241
|
}
|
|
@@ -2942,8 +3254,8 @@ configCommand.command("edit").description("Editar configurações interativament
|
|
|
2942
3254
|
if (name) updates.name = name;
|
|
2943
3255
|
const buildCmd = await prompt(`Build command ${chalk.dim(`(${service.buildCommand || "—"})`)}: `);
|
|
2944
3256
|
if (buildCmd) updates.buildCommand = buildCmd === "none" ? null : buildCmd;
|
|
2945
|
-
const startCmd
|
|
2946
|
-
if (startCmd
|
|
3257
|
+
const startCmd = await prompt(`Start command ${chalk.dim(`(${service.startCommand || "—"})`)}: `);
|
|
3258
|
+
if (startCmd) updates.startCommand = startCmd === "none" ? null : startCmd;
|
|
2947
3259
|
const port = await prompt(`Porta ${chalk.dim(`(${service.port})`)}: `);
|
|
2948
3260
|
if (port) updates.port = parseInt(port, 10);
|
|
2949
3261
|
const rootDir = await prompt(`Diretório raiz ${chalk.dim(`(${service.rootDirectory || "/"})`)}: `);
|
|
@@ -3005,15 +3317,9 @@ configCommand.command("reset").description("Resetar configurações para os padr
|
|
|
3005
3317
|
const useCommand = new Command("use").description("Selecionar qual serviço usar como padrão").argument("[serviço]", "Nome ou chave do serviço").action(async (servicoArg) => {
|
|
3006
3318
|
try {
|
|
3007
3319
|
const config = loadConfig();
|
|
3008
|
-
if (!config)
|
|
3009
|
-
console.error(chalk.red("\n✗ Nenhum projeto configurado. Execute 'veloz deploy' primeiro."));
|
|
3010
|
-
process.exit(1);
|
|
3011
|
-
}
|
|
3320
|
+
if (!config) handleError(/* @__PURE__ */ new Error("Nenhum projeto configurado. Execute 'veloz deploy' primeiro."));
|
|
3012
3321
|
const services = Object.entries(config.services);
|
|
3013
|
-
if (services.length === 0)
|
|
3014
|
-
console.error(chalk.red("\n✗ Nenhum serviço encontrado na configuração."));
|
|
3015
|
-
process.exit(1);
|
|
3016
|
-
}
|
|
3322
|
+
if (services.length === 0) handleError(/* @__PURE__ */ new Error("Nenhum serviço encontrado na configuração."));
|
|
3017
3323
|
if (services.length === 1) {
|
|
3018
3324
|
info("Apenas um serviço configurado — já é o padrão.");
|
|
3019
3325
|
return;
|
|
@@ -3023,13 +3329,8 @@ const useCommand = new Command("use").description("Selecionar qual serviço usar
|
|
|
3023
3329
|
if (servicoArg) {
|
|
3024
3330
|
const found = services.find(([key, service]) => key === servicoArg || service.name.toLowerCase() === servicoArg.toLowerCase());
|
|
3025
3331
|
if (!found) {
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
services.forEach(([key, service], i) => {
|
|
3029
|
-
const marker = key === currentDefault ? chalk.cyan(" ← padrão") : "";
|
|
3030
|
-
console.log(` ${getServiceHeader(service.name, i)} ${chalk.dim(key)}${marker}`);
|
|
3031
|
-
});
|
|
3032
|
-
process.exit(1);
|
|
3332
|
+
const available = services.map(([key, svc]) => ` • ${key} (${svc.name})`).join("\n");
|
|
3333
|
+
handleError(/* @__PURE__ */ new Error(`Serviço '${servicoArg}' não encontrado.\n\nServiços disponíveis:\n${available}`));
|
|
3033
3334
|
}
|
|
3034
3335
|
selectedKey = found[0];
|
|
3035
3336
|
} else selectedKey = await promptSelect("Qual serviço usar como padrão?", services.map(([key, service]) => ({
|
|
@@ -3038,9 +3339,15 @@ const useCommand = new Command("use").description("Selecionar qual serviço usar
|
|
|
3038
3339
|
})));
|
|
3039
3340
|
setDefaultServiceKey(selectedKey);
|
|
3040
3341
|
const selectedService = config.services[selectedKey];
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3342
|
+
output({
|
|
3343
|
+
type: "default_service",
|
|
3344
|
+
key: selectedKey,
|
|
3345
|
+
name: selectedService.name
|
|
3346
|
+
}, () => {
|
|
3347
|
+
success(`Serviço padrão: ${chalk.bold(selectedService.name)} (${selectedKey})`);
|
|
3348
|
+
console.log(chalk.dim(`\nComandos como ${chalk.white("logs")}, ${chalk.white("env")}, ${chalk.white("config")} vão usar este serviço automaticamente.`));
|
|
3349
|
+
console.log(chalk.dim(`Use ${chalk.white("--service <nome>")} para sobrescrever pontualmente.`));
|
|
3350
|
+
});
|
|
3044
3351
|
} catch (error) {
|
|
3045
3352
|
handleError(error);
|
|
3046
3353
|
}
|
|
@@ -3079,21 +3386,28 @@ apikeyCommand.command("create").description("Criar uma nova chave de API").optio
|
|
|
3079
3386
|
body: JSON.stringify(body)
|
|
3080
3387
|
});
|
|
3081
3388
|
s.stop();
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3389
|
+
output({
|
|
3390
|
+
type: "apikey_created",
|
|
3391
|
+
key: data.key,
|
|
3392
|
+
name: data.name,
|
|
3393
|
+
expiresAt: data.expiresAt
|
|
3394
|
+
}, () => {
|
|
3395
|
+
success("Chave de API criada!");
|
|
3396
|
+
console.log();
|
|
3397
|
+
console.log(chalk.bold(" Chave:"), chalk.green(data.key));
|
|
3398
|
+
console.log(chalk.bold(" Nome:"), data.name);
|
|
3399
|
+
if (data.expiresAt) console.log(chalk.bold(" Expira:"), new Date(data.expiresAt).toLocaleDateString("pt-BR"));
|
|
3400
|
+
else console.log(chalk.bold(" Expira:"), "Nunca");
|
|
3401
|
+
console.log();
|
|
3402
|
+
console.log(chalk.yellow(" Guarde esta chave — ela não será exibida novamente."));
|
|
3403
|
+
console.log();
|
|
3404
|
+
console.log(chalk.dim(" Uso em CI:"));
|
|
3405
|
+
console.log(chalk.dim(` VELOZ_API_KEY=${data.key} veloz deploy -y --service web`));
|
|
3406
|
+
console.log();
|
|
3407
|
+
console.log(chalk.dim(" GitHub Actions:"));
|
|
3408
|
+
console.log(chalk.dim(` gh secret set VELOZ_API_KEY --body "${data.key}"`));
|
|
3409
|
+
console.log();
|
|
3410
|
+
});
|
|
3097
3411
|
} catch (error) {
|
|
3098
3412
|
handleError(error);
|
|
3099
3413
|
}
|
|
@@ -3118,7 +3432,12 @@ apikeyCommand.command("list").alias("listar").description("Listar chaves de API"
|
|
|
3118
3432
|
k.id,
|
|
3119
3433
|
new Date(k.createdAt).toLocaleDateString("pt-BR"),
|
|
3120
3434
|
k.expiresAt ? new Date(k.expiresAt).toLocaleDateString("pt-BR") : "Nunca"
|
|
3121
|
-
]))
|
|
3435
|
+
]), keys.map((k) => ({
|
|
3436
|
+
name: k.name,
|
|
3437
|
+
id: k.id,
|
|
3438
|
+
createdAt: k.createdAt,
|
|
3439
|
+
expiresAt: k.expiresAt
|
|
3440
|
+
})));
|
|
3122
3441
|
} catch (error) {
|
|
3123
3442
|
handleError(error);
|
|
3124
3443
|
}
|
|
@@ -3142,10 +3461,16 @@ apikeyCommand.command("delete <keyId>").alias("deletar").description("Deletar um
|
|
|
3142
3461
|
const whoamiCommand = new Command("whoami").description("Mostrar usuário autenticado").action(async () => {
|
|
3143
3462
|
try {
|
|
3144
3463
|
const user = await (await getClient()).me();
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3464
|
+
output({
|
|
3465
|
+
type: "user",
|
|
3466
|
+
name: user.name,
|
|
3467
|
+
email: user.email
|
|
3468
|
+
}, () => {
|
|
3469
|
+
console.log();
|
|
3470
|
+
console.log(` ${chalk.bold("Nome:")} ${user.name}`);
|
|
3471
|
+
console.log(` ${chalk.bold("Email:")} ${user.email}`);
|
|
3472
|
+
console.log();
|
|
3473
|
+
});
|
|
3149
3474
|
} catch (error) {
|
|
3150
3475
|
handleError(error);
|
|
3151
3476
|
}
|
|
@@ -3153,7 +3478,22 @@ const whoamiCommand = new Command("whoami").description("Mostrar usuário autent
|
|
|
3153
3478
|
|
|
3154
3479
|
//#endregion
|
|
3155
3480
|
//#region src/index.ts
|
|
3156
|
-
const program = new Command().name("veloz").description("CLI da plataforma Veloz — deploy rápido para o Brasil").version("0.0.0-beta.
|
|
3481
|
+
const program = new Command().name("veloz").description("CLI da plataforma Veloz — deploy rápido para o Brasil").version("0.0.0-beta.8").option("--output <format>", "Formato de saída: fancy, json, github-actions, plain").hook("preAction", (thisCommand) => {
|
|
3482
|
+
const opts = thisCommand.opts();
|
|
3483
|
+
if (opts.output) {
|
|
3484
|
+
const valid = [
|
|
3485
|
+
"fancy",
|
|
3486
|
+
"json",
|
|
3487
|
+
"github-actions",
|
|
3488
|
+
"plain"
|
|
3489
|
+
];
|
|
3490
|
+
if (!valid.includes(opts.output)) {
|
|
3491
|
+
console.error(`Formato de saída inválido: "${opts.output}". Use: ${valid.join(", ")}`);
|
|
3492
|
+
process.exit(1);
|
|
3493
|
+
}
|
|
3494
|
+
setOutputMode(opts.output);
|
|
3495
|
+
}
|
|
3496
|
+
});
|
|
3157
3497
|
program.addCommand(loginCommand);
|
|
3158
3498
|
program.addCommand(logoutCommand);
|
|
3159
3499
|
program.addCommand(projectsCommand);
|