onveloz 0.0.0-beta.7 → 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 +980 -638
- 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
|
|
|
@@ -1813,8 +2035,20 @@ function runPreDeployChecks(basePath = ".") {
|
|
|
1813
2035
|
*/
|
|
1814
2036
|
function printDeployWarnings(warnings) {
|
|
1815
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
|
+
}
|
|
1816
2049
|
console.log();
|
|
1817
|
-
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 {
|
|
1818
2052
|
console.log(chalk.yellow(` AVISO: ${w.message}`));
|
|
1819
2053
|
console.log(chalk.dim(` ${w.hint}`));
|
|
1820
2054
|
}
|
|
@@ -1851,10 +2085,17 @@ async function computeExtraFilesForServices(services) {
|
|
|
1851
2085
|
warnings
|
|
1852
2086
|
});
|
|
1853
2087
|
}
|
|
1854
|
-
if (allWarnings.length > 0) for (const { service, warnings } of allWarnings) {
|
|
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
|
+
}, () => {
|
|
1855
2096
|
console.log(chalk.yellow(`\n ${chalk.bold(service)}:`));
|
|
1856
2097
|
printDeployWarnings(warnings);
|
|
1857
|
-
}
|
|
2098
|
+
});
|
|
1858
2099
|
for (const svc of services) {
|
|
1859
2100
|
const serviceConf = resolveServiceConf(velozConfig, svc.serviceId);
|
|
1860
2101
|
if (serviceConf) await syncServiceConfig(client, svc.serviceId, serviceConf);
|
|
@@ -1924,19 +2165,26 @@ function readLocalFile(path) {
|
|
|
1924
2165
|
}
|
|
1925
2166
|
}
|
|
1926
2167
|
function printSummary(settings) {
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
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
|
+
});
|
|
1940
2188
|
}
|
|
1941
2189
|
function detectLocalRepo(basePath = ".") {
|
|
1942
2190
|
const files = {};
|
|
@@ -2008,13 +2256,13 @@ function detectLocalRepo(basePath = ".") {
|
|
|
2008
2256
|
async function promptEnvVars(serviceId, detectedVars) {
|
|
2009
2257
|
if (detectedVars.length === 0) return;
|
|
2010
2258
|
console.log(chalk.cyan(`\n📝 ${detectedVars.length} variável(is) de ambiente detectada(s):\n`));
|
|
2011
|
-
for (const v of detectedVars) console.log(` • ${v
|
|
2259
|
+
for (const v of detectedVars) console.log(` • ${v}`);
|
|
2012
2260
|
console.log();
|
|
2013
2261
|
if (!await promptConfirm("Deseja preencher as variáveis agora?", false)) return;
|
|
2014
2262
|
const vars = {};
|
|
2015
|
-
for (const
|
|
2016
|
-
const
|
|
2017
|
-
if (
|
|
2263
|
+
for (const key of detectedVars) {
|
|
2264
|
+
const val = await prompt(` ${chalk.bold(key)}:`);
|
|
2265
|
+
if (val) vars[key] = val;
|
|
2018
2266
|
}
|
|
2019
2267
|
const filled = Object.keys(vars).length;
|
|
2020
2268
|
if (filled > 0) {
|
|
@@ -2035,50 +2283,54 @@ async function createServiceFlow(projectId, projectName, repoName, opts = {}) {
|
|
|
2035
2283
|
const detection = detectLocalRepo();
|
|
2036
2284
|
spinDetect.stop();
|
|
2037
2285
|
const pm = detection.packageManager;
|
|
2038
|
-
if (detection.isMonorepo && detection.
|
|
2286
|
+
if (detection.isMonorepo && detection.services.length > 0) {
|
|
2039
2287
|
info(`Monorepo detectado (${pm})`);
|
|
2040
2288
|
let selectedApps;
|
|
2041
2289
|
if (opts.yes) if (opts.app) {
|
|
2042
|
-
selectedApps = detection.
|
|
2290
|
+
selectedApps = detection.services.filter((a) => a.root === opts.app);
|
|
2043
2291
|
if (selectedApps.length === 0) {
|
|
2044
|
-
|
|
2045
|
-
|
|
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
|
+
});
|
|
2046
2300
|
process.exit(1);
|
|
2047
2301
|
}
|
|
2048
|
-
} else selectedApps = detection.
|
|
2302
|
+
} else selectedApps = detection.services;
|
|
2049
2303
|
else {
|
|
2050
|
-
const selectedPaths = await promptMultiSelect("Quais apps deseja fazer o deploy?", detection.
|
|
2051
|
-
label: `${app.name}${app.framework ? ` (${app.framework
|
|
2052
|
-
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
|
|
2053
2307
|
})));
|
|
2054
|
-
selectedApps = detection.
|
|
2308
|
+
selectedApps = detection.services.filter((a) => selectedPaths.includes(a.root));
|
|
2055
2309
|
}
|
|
2056
2310
|
for (const app of selectedApps) {
|
|
2057
|
-
const fw$1 = app.framework;
|
|
2058
2311
|
console.log(chalk.cyan(`\n── ${app.name} ──`));
|
|
2059
2312
|
printSummary({
|
|
2060
2313
|
name: app.name,
|
|
2061
|
-
type:
|
|
2062
|
-
rootDir: app.
|
|
2314
|
+
type: "WEB",
|
|
2315
|
+
rootDir: app.root,
|
|
2063
2316
|
branch,
|
|
2064
|
-
framework:
|
|
2317
|
+
framework: app.framework,
|
|
2065
2318
|
packageManager: pm,
|
|
2066
|
-
buildCommand:
|
|
2067
|
-
startCommand:
|
|
2068
|
-
outputDir:
|
|
2069
|
-
port:
|
|
2319
|
+
buildCommand: app.buildCommand,
|
|
2320
|
+
startCommand: app.startCommand,
|
|
2321
|
+
outputDir: null,
|
|
2322
|
+
port: app.port ?? 3e3
|
|
2070
2323
|
});
|
|
2071
2324
|
}
|
|
2072
2325
|
if (!opts.yes) {
|
|
2073
2326
|
if (!await promptConfirm("Confirmar e fazer deploy?")) for (const app of selectedApps) {
|
|
2074
|
-
const fw$1 = app.framework;
|
|
2075
2327
|
console.log(chalk.cyan(`\n── Editar: ${app.name} ──`));
|
|
2076
|
-
const newBuild = await prompt(` Build command: ${chalk.dim(`(${
|
|
2077
|
-
if (newBuild
|
|
2078
|
-
const newStart = await prompt(` Start command: ${chalk.dim(`(${
|
|
2079
|
-
if (newStart
|
|
2080
|
-
const newPort = await prompt(` Port: ${chalk.dim(`(${
|
|
2081
|
-
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;
|
|
2082
2334
|
}
|
|
2083
2335
|
}
|
|
2084
2336
|
const config = {
|
|
@@ -2092,18 +2344,17 @@ async function createServiceFlow(projectId, projectName, repoName, opts = {}) {
|
|
|
2092
2344
|
};
|
|
2093
2345
|
const createdServices = [];
|
|
2094
2346
|
for (const app of selectedApps) {
|
|
2095
|
-
const fw$1 = app.framework;
|
|
2096
2347
|
console.log(chalk.cyan(`\n── Criando serviço: ${app.name} ──\n`));
|
|
2097
2348
|
const spinService$1 = spinner(`Criando serviço ${chalk.bold(app.name)}...`);
|
|
2098
2349
|
const service$1 = await withRetry(() => client.services.create({
|
|
2099
2350
|
projectId,
|
|
2100
2351
|
name: app.name,
|
|
2101
|
-
type:
|
|
2352
|
+
type: "WEB",
|
|
2102
2353
|
branch,
|
|
2103
|
-
rootDirectory: app.
|
|
2104
|
-
buildCommand:
|
|
2105
|
-
startCommand:
|
|
2106
|
-
port:
|
|
2354
|
+
rootDirectory: app.root,
|
|
2355
|
+
buildCommand: app.buildCommand ?? void 0,
|
|
2356
|
+
startCommand: app.startCommand ?? void 0,
|
|
2357
|
+
port: app.port ?? 3e3
|
|
2107
2358
|
}));
|
|
2108
2359
|
spinService$1.stop();
|
|
2109
2360
|
success(`Serviço criado: ${chalk.bold(service$1.name)}`);
|
|
@@ -2111,19 +2362,16 @@ async function createServiceFlow(projectId, projectName, repoName, opts = {}) {
|
|
|
2111
2362
|
service: service$1,
|
|
2112
2363
|
app
|
|
2113
2364
|
});
|
|
2114
|
-
config.services[app.
|
|
2365
|
+
config.services[app.root] = {
|
|
2115
2366
|
id: service$1.id,
|
|
2116
2367
|
name: service$1.name,
|
|
2117
|
-
type:
|
|
2118
|
-
root: app.
|
|
2368
|
+
type: "web",
|
|
2369
|
+
root: app.root,
|
|
2119
2370
|
branch,
|
|
2120
|
-
build:
|
|
2121
|
-
command: fw$1?.buildCommand ?? void 0,
|
|
2122
|
-
outputDir: fw$1?.outputDir ?? void 0
|
|
2123
|
-
} : void 0,
|
|
2371
|
+
build: app.buildCommand ? { command: app.buildCommand ?? void 0 } : void 0,
|
|
2124
2372
|
runtime: {
|
|
2125
|
-
command:
|
|
2126
|
-
port:
|
|
2373
|
+
command: app.startCommand ?? void 0,
|
|
2374
|
+
port: app.port ?? 3e3
|
|
2127
2375
|
}
|
|
2128
2376
|
};
|
|
2129
2377
|
}
|
|
@@ -2131,38 +2379,36 @@ async function createServiceFlow(projectId, projectName, repoName, opts = {}) {
|
|
|
2131
2379
|
info(`Arquivo ${getConfigFileName()} criado na raiz do projeto.`);
|
|
2132
2380
|
if (!opts.yes) for (const { service: service$1, app } of createdServices) {
|
|
2133
2381
|
console.log(chalk.cyan(`\n── Configurando variáveis: ${app.name} ──\n`));
|
|
2134
|
-
const serviceDetection = detectLocalRepo(app.
|
|
2382
|
+
const serviceDetection = detectLocalRepo(app.root);
|
|
2135
2383
|
await promptEnvVars(service$1.id, serviceDetection.envVars);
|
|
2136
2384
|
}
|
|
2137
2385
|
await deployServicesInParallel(createdServices.map(({ service: service$1, app }) => ({
|
|
2138
2386
|
serviceId: service$1.id,
|
|
2139
2387
|
serviceName: app.name,
|
|
2140
|
-
path: resolve(process.cwd(), app.
|
|
2141
|
-
extraFiles: prepareExtraFiles(detectLocalRepo(app.
|
|
2142
|
-
type:
|
|
2143
|
-
buildCommand: app.
|
|
2144
|
-
startCommand: app.
|
|
2145
|
-
port: app.
|
|
2146
|
-
rootDirectory: app.
|
|
2147
|
-
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
|
|
2148
2395
|
})
|
|
2149
2396
|
})));
|
|
2150
2397
|
return createdServices[createdServices.length - 1]?.service.id || "";
|
|
2151
2398
|
}
|
|
2152
|
-
const fw = detection.framework;
|
|
2153
2399
|
const settings = {
|
|
2154
2400
|
name: repoName,
|
|
2155
|
-
type:
|
|
2401
|
+
type: "WEB",
|
|
2156
2402
|
rootDir: ".",
|
|
2157
2403
|
branch,
|
|
2158
|
-
framework:
|
|
2404
|
+
framework: detection.framework,
|
|
2159
2405
|
packageManager: pm,
|
|
2160
|
-
buildCommand:
|
|
2161
|
-
startCommand:
|
|
2162
|
-
outputDir:
|
|
2163
|
-
port:
|
|
2406
|
+
buildCommand: detection.buildCommand,
|
|
2407
|
+
startCommand: detection.startCommand,
|
|
2408
|
+
outputDir: null,
|
|
2409
|
+
port: detection.port ?? 3e3
|
|
2164
2410
|
};
|
|
2165
|
-
if (
|
|
2411
|
+
if (detection.framework) info(`Framework detectado: ${chalk.bold(detection.framework)}`);
|
|
2166
2412
|
printSummary(settings);
|
|
2167
2413
|
if (!opts.yes) {
|
|
2168
2414
|
if (!await promptConfirm("Confirmar e fazer deploy?")) {
|
|
@@ -2232,8 +2478,17 @@ const deployCommand = new Command("deploy").description("Fazer deploy do serviç
|
|
|
2232
2478
|
if (options.service) {
|
|
2233
2479
|
const found = configuredServices.find((s) => s.key === options.service || s.serviceName.toLowerCase() === options.service.toLowerCase() || s.serviceId === options.service);
|
|
2234
2480
|
if (!found) {
|
|
2235
|
-
|
|
2236
|
-
|
|
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
|
+
});
|
|
2237
2492
|
process.exit(1);
|
|
2238
2493
|
}
|
|
2239
2494
|
await triggerDeploy(found.serviceId, found.serviceName);
|
|
@@ -2274,17 +2529,17 @@ const deployCommand = new Command("deploy").description("Fazer deploy do serviç
|
|
|
2274
2529
|
return;
|
|
2275
2530
|
}
|
|
2276
2531
|
}
|
|
2277
|
-
if (!isGitRepo())
|
|
2278
|
-
console.error(chalk.red("\n✗ Este diretório não é um repositório git. Inicialize com `git init` e adicione um remote."));
|
|
2279
|
-
process.exit(1);
|
|
2280
|
-
}
|
|
2532
|
+
if (!isGitRepo()) handleError(/* @__PURE__ */ new Error("Este diretório não é um repositório git. Inicialize com `git init` e adicione um remote."));
|
|
2281
2533
|
info("Detectando repositório git...");
|
|
2282
2534
|
const remote = getGitRemote();
|
|
2283
|
-
if (!remote)
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
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
|
+
});
|
|
2288
2543
|
const client = await getClient();
|
|
2289
2544
|
const spin = spinner("Buscando projeto...");
|
|
2290
2545
|
const project = await withRetry(() => client.projects.findByRepo({
|
|
@@ -2313,7 +2568,7 @@ const deployCommand = new Command("deploy").description("Fazer deploy do serviç
|
|
|
2313
2568
|
}
|
|
2314
2569
|
const selectedService = project.services.find((s) => s.id === serviceId);
|
|
2315
2570
|
const detection = detectLocalRepo();
|
|
2316
|
-
const detectedType =
|
|
2571
|
+
const detectedType = selectedService?.type?.toLowerCase() ?? "web";
|
|
2317
2572
|
saveConfig({
|
|
2318
2573
|
version: "1.0",
|
|
2319
2574
|
project: {
|
|
@@ -2397,10 +2652,14 @@ const TAG_COLORS = [
|
|
|
2397
2652
|
chalk.red
|
|
2398
2653
|
];
|
|
2399
2654
|
function getServiceTag(name, maxLen, colorIndex) {
|
|
2655
|
+
const mode = getOutputMode();
|
|
2656
|
+
if (mode === "json" || mode === "github-actions" || mode === "plain") return `[${name.padEnd(maxLen)}]`;
|
|
2400
2657
|
const color = TAG_COLORS[colorIndex % TAG_COLORS.length];
|
|
2401
2658
|
return color(`[${name.padEnd(maxLen)}]`);
|
|
2402
2659
|
}
|
|
2403
2660
|
function getServiceHeader(name, colorIndex) {
|
|
2661
|
+
const mode = getOutputMode();
|
|
2662
|
+
if (mode === "json" || mode === "github-actions" || mode === "plain") return `── ${name} ──`;
|
|
2404
2663
|
const color = TAG_COLORS[colorIndex % TAG_COLORS.length];
|
|
2405
2664
|
return color(`── ${name} ──`);
|
|
2406
2665
|
}
|
|
@@ -2509,7 +2768,14 @@ async function streamFollow(services, maxNameLen, tailLines) {
|
|
|
2509
2768
|
serviceId: service.id,
|
|
2510
2769
|
tailLines: Math.ceil(tailLines / services.length)
|
|
2511
2770
|
});
|
|
2512
|
-
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
|
+
});
|
|
2513
2779
|
} catch {
|
|
2514
2780
|
console.log(`${tag}${chalk.red("Erro ao conectar ao stream de logs")}`);
|
|
2515
2781
|
}
|
|
@@ -2533,13 +2799,22 @@ async function fetchRecent(services, maxNameLen, tailLines) {
|
|
|
2533
2799
|
info("Nenhum log encontrado.");
|
|
2534
2800
|
return;
|
|
2535
2801
|
}
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
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
|
+
});
|
|
2543
2818
|
}
|
|
2544
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) => {
|
|
2545
2820
|
const spin = spinner("Carregando logs...");
|
|
@@ -2587,7 +2862,11 @@ envCommand.command("list").alias("listar").description("Listar variáveis de amb
|
|
|
2587
2862
|
chalk.bold(v.key),
|
|
2588
2863
|
chalk.dim(v.maskedValue),
|
|
2589
2864
|
new Date(v.updatedAt).toLocaleDateString("pt-BR")
|
|
2590
|
-
]))
|
|
2865
|
+
]), envVars.map((v) => ({
|
|
2866
|
+
key: v.key,
|
|
2867
|
+
maskedValue: v.maskedValue,
|
|
2868
|
+
updatedAt: v.updatedAt
|
|
2869
|
+
})));
|
|
2591
2870
|
}
|
|
2592
2871
|
spin.stop();
|
|
2593
2872
|
if (totalVars === 0 && !showHeaders) info("Nenhuma variável de ambiente configurada.");
|
|
@@ -2605,15 +2884,13 @@ envCommand.command("set <pares...>").description("Definir variável de ambiente
|
|
|
2605
2884
|
const eqIndex = par.indexOf("=");
|
|
2606
2885
|
if (eqIndex === -1) {
|
|
2607
2886
|
spin.stop();
|
|
2608
|
-
|
|
2609
|
-
process.exit(1);
|
|
2887
|
+
handleError(/* @__PURE__ */ new Error(`Formato inválido: "${par}". Use CHAVE=VALOR.`));
|
|
2610
2888
|
}
|
|
2611
2889
|
const key = par.slice(0, eqIndex);
|
|
2612
2890
|
const value = par.slice(eqIndex + 1);
|
|
2613
2891
|
if (!key) {
|
|
2614
2892
|
spin.stop();
|
|
2615
|
-
|
|
2616
|
-
process.exit(1);
|
|
2893
|
+
handleError(/* @__PURE__ */ new Error("Chave não pode estar vazia."));
|
|
2617
2894
|
}
|
|
2618
2895
|
await client.envVars.set({
|
|
2619
2896
|
serviceId,
|
|
@@ -2655,38 +2932,32 @@ envCommand.command("import [arquivo]").description("Importar variáveis de ambie
|
|
|
2655
2932
|
let envContent = "";
|
|
2656
2933
|
if (arquivo) {
|
|
2657
2934
|
const filePath = resolve(process.cwd(), arquivo);
|
|
2658
|
-
if (!existsSync(filePath)) {
|
|
2659
|
-
console.error(chalk.red(`\n✗ Arquivo não encontrado: ${arquivo}`));
|
|
2660
|
-
process.exit(1);
|
|
2661
|
-
}
|
|
2935
|
+
if (!existsSync(filePath)) handleError(/* @__PURE__ */ new Error(`Arquivo não encontrado: ${arquivo}`));
|
|
2662
2936
|
envContent = readFileSync(filePath, "utf-8");
|
|
2663
2937
|
info(`Importando de ${chalk.bold(arquivo)}...`);
|
|
2664
2938
|
} else {
|
|
2665
|
-
console.log(chalk.cyan("\n📋 Modo de colagem interativo"));
|
|
2666
|
-
console.log(chalk.dim("Cole seu conteúdo .env abaixo. Pressione Ctrl+D (ou Ctrl+Z no Windows) quando terminar:\n"));
|
|
2667
2939
|
const rl = readline.createInterface({
|
|
2668
2940
|
input: process.stdin,
|
|
2669
|
-
output: process.stdout
|
|
2941
|
+
output: isInteractive() ? process.stdout : void 0
|
|
2670
2942
|
});
|
|
2671
|
-
|
|
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 = [];
|
|
2672
2948
|
await new Promise((resolve$1) => {
|
|
2673
|
-
rl.on("line", (line) =>
|
|
2674
|
-
|
|
2675
|
-
});
|
|
2676
|
-
rl.on("close", () => {
|
|
2677
|
-
resolve$1();
|
|
2678
|
-
});
|
|
2949
|
+
rl.on("line", (line) => lines.push(line));
|
|
2950
|
+
rl.on("close", () => resolve$1());
|
|
2679
2951
|
});
|
|
2680
|
-
envContent = lines
|
|
2952
|
+
envContent = lines.join("\n");
|
|
2681
2953
|
}
|
|
2682
2954
|
const envVars = {};
|
|
2683
|
-
const
|
|
2684
|
-
for (const line of lines) {
|
|
2955
|
+
for (const line of envContent.split("\n")) {
|
|
2685
2956
|
const trimmed = line.trim();
|
|
2686
2957
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
2687
2958
|
const eqIndex = trimmed.indexOf("=");
|
|
2688
2959
|
if (eqIndex === -1) continue;
|
|
2689
|
-
|
|
2960
|
+
const key = trimmed.slice(0, eqIndex).trim();
|
|
2690
2961
|
let value = trimmed.slice(eqIndex + 1).trim();
|
|
2691
2962
|
if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
|
|
2692
2963
|
value = value.replace(/\\n/g, "\n").replace(/\\r/g, "\r").replace(/\\t/g, " ").replace(/\\\\/g, "\\");
|
|
@@ -2694,7 +2965,7 @@ envCommand.command("import [arquivo]").description("Importar variáveis de ambie
|
|
|
2694
2965
|
}
|
|
2695
2966
|
const varsCount = Object.keys(envVars).length;
|
|
2696
2967
|
if (varsCount === 0) {
|
|
2697
|
-
|
|
2968
|
+
warn("Nenhuma variável válida encontrada.");
|
|
2698
2969
|
return;
|
|
2699
2970
|
}
|
|
2700
2971
|
console.log(chalk.bold("\n📝 Variáveis a serem importadas:\n"));
|
|
@@ -2743,20 +3014,31 @@ envCommand.command("export [arquivo]").description("Exportar variáveis de ambie
|
|
|
2743
3014
|
return;
|
|
2744
3015
|
}
|
|
2745
3016
|
const envContent = envVars.map((v) => `${v.key}=${v.maskedValue}`).join("\n");
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
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
|
+
});
|
|
2755
3034
|
} catch (error) {
|
|
2756
3035
|
spin.stop();
|
|
2757
3036
|
handleError(error);
|
|
2758
3037
|
}
|
|
2759
3038
|
});
|
|
3039
|
+
function warn(message) {
|
|
3040
|
+
console.log(chalk.yellow(`⚠ ${message}`));
|
|
3041
|
+
}
|
|
2760
3042
|
|
|
2761
3043
|
//#endregion
|
|
2762
3044
|
//#region src/commands/domains.ts
|
|
@@ -2789,12 +3071,18 @@ domainsCommand.command("list").alias("listar").description("Listar domínios dos
|
|
|
2789
3071
|
"TLS",
|
|
2790
3072
|
"Tipo"
|
|
2791
3073
|
], domains.map((d) => [
|
|
2792
|
-
d.id
|
|
3074
|
+
d.id,
|
|
2793
3075
|
chalk.bold(d.domain),
|
|
2794
3076
|
d.verified ? chalk.green("✓ Sim") : chalk.yellow("✗ Não"),
|
|
2795
3077
|
tlsStatusLabels[d.tlsStatus] ?? d.tlsStatus,
|
|
2796
3078
|
d.isAutoGenerated ? "Auto" : "Personalizado"
|
|
2797
|
-
]))
|
|
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
|
+
})));
|
|
2798
3086
|
}
|
|
2799
3087
|
spin.stop();
|
|
2800
3088
|
if (totalDomains === 0 && !showHeaders) info("Nenhum domínio configurado.");
|
|
@@ -2812,12 +3100,19 @@ domainsCommand.command("add <dominio>").alias("adicionar").description("Adiciona
|
|
|
2812
3100
|
domain: dominio
|
|
2813
3101
|
});
|
|
2814
3102
|
spin.stop();
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
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
|
+
});
|
|
2821
3116
|
} catch (error) {
|
|
2822
3117
|
spin.stop();
|
|
2823
3118
|
handleError(error);
|
|
@@ -2828,13 +3123,19 @@ domainsCommand.command("verify <domainId>").alias("verificar").description("Veri
|
|
|
2828
3123
|
try {
|
|
2829
3124
|
const result = await (await getClient()).domains.verify({ domainId });
|
|
2830
3125
|
spin.stop();
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
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
|
+
});
|
|
2838
3139
|
} catch (error) {
|
|
2839
3140
|
spin.stop();
|
|
2840
3141
|
handleError(error);
|
|
@@ -2877,14 +3178,22 @@ configCommand.command("show").description("Mostrar configurações atuais dos se
|
|
|
2877
3178
|
const client = await getClient();
|
|
2878
3179
|
const showHeaders = services.length > 1;
|
|
2879
3180
|
const spin = spinner("Buscando configurações...");
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
if (showHeaders) console.log(`\n${getServiceHeader(svcConfig.name, index)}\n`);
|
|
2883
|
-
else console.log(chalk.bold("\n📋 Configurações do Serviço\n"));
|
|
2884
|
-
printServiceConfig(service);
|
|
2885
|
-
console.log();
|
|
2886
|
-
}
|
|
3181
|
+
const configs = [];
|
|
3182
|
+
for (const { service: svcConfig } of services) configs.push(await client.services.get({ serviceId: svcConfig.id }));
|
|
2887
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
|
+
});
|
|
2888
3197
|
} catch (error) {
|
|
2889
3198
|
handleError(error);
|
|
2890
3199
|
}
|
|
@@ -2915,13 +3224,18 @@ configCommand.command("set").description("Atualizar configurações do serviço"
|
|
|
2915
3224
|
});
|
|
2916
3225
|
spin.stop();
|
|
2917
3226
|
success("Configurações atualizadas com sucesso!");
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
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
|
+
});
|
|
2925
3239
|
} catch (error) {
|
|
2926
3240
|
handleError(error);
|
|
2927
3241
|
}
|
|
@@ -2940,8 +3254,8 @@ configCommand.command("edit").description("Editar configurações interativament
|
|
|
2940
3254
|
if (name) updates.name = name;
|
|
2941
3255
|
const buildCmd = await prompt(`Build command ${chalk.dim(`(${service.buildCommand || "—"})`)}: `);
|
|
2942
3256
|
if (buildCmd) updates.buildCommand = buildCmd === "none" ? null : buildCmd;
|
|
2943
|
-
const startCmd
|
|
2944
|
-
if (startCmd
|
|
3257
|
+
const startCmd = await prompt(`Start command ${chalk.dim(`(${service.startCommand || "—"})`)}: `);
|
|
3258
|
+
if (startCmd) updates.startCommand = startCmd === "none" ? null : startCmd;
|
|
2945
3259
|
const port = await prompt(`Porta ${chalk.dim(`(${service.port})`)}: `);
|
|
2946
3260
|
if (port) updates.port = parseInt(port, 10);
|
|
2947
3261
|
const rootDir = await prompt(`Diretório raiz ${chalk.dim(`(${service.rootDirectory || "/"})`)}: `);
|
|
@@ -3003,15 +3317,9 @@ configCommand.command("reset").description("Resetar configurações para os padr
|
|
|
3003
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) => {
|
|
3004
3318
|
try {
|
|
3005
3319
|
const config = loadConfig();
|
|
3006
|
-
if (!config)
|
|
3007
|
-
console.error(chalk.red("\n✗ Nenhum projeto configurado. Execute 'veloz deploy' primeiro."));
|
|
3008
|
-
process.exit(1);
|
|
3009
|
-
}
|
|
3320
|
+
if (!config) handleError(/* @__PURE__ */ new Error("Nenhum projeto configurado. Execute 'veloz deploy' primeiro."));
|
|
3010
3321
|
const services = Object.entries(config.services);
|
|
3011
|
-
if (services.length === 0)
|
|
3012
|
-
console.error(chalk.red("\n✗ Nenhum serviço encontrado na configuração."));
|
|
3013
|
-
process.exit(1);
|
|
3014
|
-
}
|
|
3322
|
+
if (services.length === 0) handleError(/* @__PURE__ */ new Error("Nenhum serviço encontrado na configuração."));
|
|
3015
3323
|
if (services.length === 1) {
|
|
3016
3324
|
info("Apenas um serviço configurado — já é o padrão.");
|
|
3017
3325
|
return;
|
|
@@ -3021,13 +3329,8 @@ const useCommand = new Command("use").description("Selecionar qual serviço usar
|
|
|
3021
3329
|
if (servicoArg) {
|
|
3022
3330
|
const found = services.find(([key, service]) => key === servicoArg || service.name.toLowerCase() === servicoArg.toLowerCase());
|
|
3023
3331
|
if (!found) {
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
services.forEach(([key, service], i) => {
|
|
3027
|
-
const marker = key === currentDefault ? chalk.cyan(" ← padrão") : "";
|
|
3028
|
-
console.log(` ${getServiceHeader(service.name, i)} ${chalk.dim(key)}${marker}`);
|
|
3029
|
-
});
|
|
3030
|
-
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}`));
|
|
3031
3334
|
}
|
|
3032
3335
|
selectedKey = found[0];
|
|
3033
3336
|
} else selectedKey = await promptSelect("Qual serviço usar como padrão?", services.map(([key, service]) => ({
|
|
@@ -3036,9 +3339,15 @@ const useCommand = new Command("use").description("Selecionar qual serviço usar
|
|
|
3036
3339
|
})));
|
|
3037
3340
|
setDefaultServiceKey(selectedKey);
|
|
3038
3341
|
const selectedService = config.services[selectedKey];
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
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
|
+
});
|
|
3042
3351
|
} catch (error) {
|
|
3043
3352
|
handleError(error);
|
|
3044
3353
|
}
|
|
@@ -3077,21 +3386,28 @@ apikeyCommand.command("create").description("Criar uma nova chave de API").optio
|
|
|
3077
3386
|
body: JSON.stringify(body)
|
|
3078
3387
|
});
|
|
3079
3388
|
s.stop();
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
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
|
+
});
|
|
3095
3411
|
} catch (error) {
|
|
3096
3412
|
handleError(error);
|
|
3097
3413
|
}
|
|
@@ -3116,7 +3432,12 @@ apikeyCommand.command("list").alias("listar").description("Listar chaves de API"
|
|
|
3116
3432
|
k.id,
|
|
3117
3433
|
new Date(k.createdAt).toLocaleDateString("pt-BR"),
|
|
3118
3434
|
k.expiresAt ? new Date(k.expiresAt).toLocaleDateString("pt-BR") : "Nunca"
|
|
3119
|
-
]))
|
|
3435
|
+
]), keys.map((k) => ({
|
|
3436
|
+
name: k.name,
|
|
3437
|
+
id: k.id,
|
|
3438
|
+
createdAt: k.createdAt,
|
|
3439
|
+
expiresAt: k.expiresAt
|
|
3440
|
+
})));
|
|
3120
3441
|
} catch (error) {
|
|
3121
3442
|
handleError(error);
|
|
3122
3443
|
}
|
|
@@ -3140,10 +3461,16 @@ apikeyCommand.command("delete <keyId>").alias("deletar").description("Deletar um
|
|
|
3140
3461
|
const whoamiCommand = new Command("whoami").description("Mostrar usuário autenticado").action(async () => {
|
|
3141
3462
|
try {
|
|
3142
3463
|
const user = await (await getClient()).me();
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
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
|
+
});
|
|
3147
3474
|
} catch (error) {
|
|
3148
3475
|
handleError(error);
|
|
3149
3476
|
}
|
|
@@ -3151,7 +3478,22 @@ const whoamiCommand = new Command("whoami").description("Mostrar usuário autent
|
|
|
3151
3478
|
|
|
3152
3479
|
//#endregion
|
|
3153
3480
|
//#region src/index.ts
|
|
3154
|
-
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
|
+
});
|
|
3155
3497
|
program.addCommand(loginCommand);
|
|
3156
3498
|
program.addCommand(logoutCommand);
|
|
3157
3499
|
program.addCommand(projectsCommand);
|