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.
Files changed (2) hide show
  1. package/dist/index.mjs +980 -638
  2. 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
- console.error(chalk.red(`\n✗ Erro: ${message}`));
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
- console.error(chalk.red("\n✗ Erro de conexão: Não foi possível conectar ao servidor Veloz."));
114
- console.error(chalk.yellow(" Verifique se o servidor está rodando e a URL está correta."));
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
- console.error(chalk.red(`\n✗ Erro: ${error.message}`));
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
- console.error(chalk.red("\n✗ Erro inesperado."));
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
- console.log(chalk.green(`\n✓ ${message}`));
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
- console.log(chalk.cyan(`ℹ ${message}`));
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
- function warn(message) {
130
- console.log(chalk.yellow(`⚠ ${message}`));
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
- function printTable(headers, rows) {
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) => cell.padEnd(colWidths[i])).join(" ");
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
- console.log();
217
- info("Abrindo navegador para autenticação...");
218
- console.log();
219
- console.log(chalk.white(` Código de verificação: ${chalk.bold.cyan(data.user_code)}`));
220
- console.log();
221
- console.log(chalk.dim(" Se o navegador não abrir, acesse manualmente:"));
222
- console.log(chalk.dim(` ${verificationUrl}`));
223
- console.log();
224
- openBrowser(verificationUrl);
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 && (err.message.includes("fetch") || err.message.includes("ECONNREFUSED"))) {
240
- console.error(chalk.red("\n✗ Erro de conexão: Não foi possível conectar ao servidor Veloz."));
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.slice(0, 8),
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
- if (services.length > 0) {
577
- console.log(`\n ${chalk.bold("Serviços:")}`);
578
- services.forEach(([key, service]) => {
579
- const type = service.type === "web" ? "Serviço Web" : "Site Estático";
580
- console.log(` • ${chalk.cyan(service.name)} (${key}) - ${type}`);
581
- });
582
- }
583
- console.log();
584
- info("Use 'veloz deploy' para fazer deploy ou atualizar serviços.");
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
- const FRAMEWORK_RULES = [
590
- {
591
- name: "nextjs",
592
- label: "Next.js",
593
- type: "WEB",
594
- match: (deps) => "next" in deps,
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
- name: "hono",
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
- name: "nuxt",
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
- name: "remix",
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 start",
627
- outputDir: "build",
628
- port: 3e3
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 start",
637
- outputDir: ".svelte-kit",
849
+ startCommand: "npm run preview",
638
850
  port: 3e3
639
- },
640
- {
641
- name: "astro",
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: null,
647
- outputDir: "dist",
648
- port: 3e3
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: null,
657
- outputDir: "public",
658
- port: 3e3
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: null,
667
- outputDir: "build",
867
+ startCommand: "npm run start",
668
868
  port: 3e3
669
- },
670
- {
671
- name: "vite-react",
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: null,
677
- outputDir: "dist",
873
+ startCommand: "npm run start",
678
874
  port: 3e3
679
- },
680
- {
681
- name: "vite-vue",
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: null,
687
- outputDir: "dist",
879
+ startCommand: "npm run start",
688
880
  port: 3e3
689
- },
690
- {
691
- name: "vite",
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: null,
697
- outputDir: "dist",
885
+ startCommand: "npm run start:prod",
698
886
  port: 3e3
699
- },
700
- {
701
- name: "angular",
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: null,
707
- outputDir: "dist",
708
- port: 4200
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: "npm run start:prod",
737
- outputDir: "dist",
897
+ startCommand: "npx serve -s build",
738
898
  port: 3e3
739
- },
740
- {
741
- name: "hono",
742
- label: "Hono",
743
- type: "WEB",
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 ("bun.lockb" in files || "bun.lock" in files) return "bun";
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 runCmd(pm, script) {
758
- switch (pm) {
759
- case "bun": return `bun run ${script}`;
760
- case "yarn": return `yarn ${script}`;
761
- case "pnpm": return `pnpm run ${script}`;
762
- default: return `npm run ${script}`;
763
- }
764
- }
765
- function startCmd(pm) {
766
- switch (pm) {
767
- case "bun": return "bun run start";
768
- case "yarn": return "yarn start";
769
- case "pnpm": return "pnpm start";
770
- default: return "npm start";
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 [...dirs];
929
+ return Array.from(vars);
869
930
  }
870
- function detectMonorepo(files, pm = "npm") {
871
- const rootPkg = safeParse(files["package.json"]);
872
- const availableFiles = Object.keys(files);
873
- let workspacePatterns = [];
874
- if (rootPkg?.workspaces) {
875
- const ws = rootPkg.workspaces;
876
- if (Array.isArray(ws)) workspacePatterns = ws;
877
- else if (typeof ws === "object" && ws !== null && "packages" in ws && Array.isArray(ws.packages)) workspacePatterns = ws.packages;
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
- if (workspacePatterns.length === 0) return {
895
- isMonorepo: false,
896
- apps: []
897
- };
898
- const dirs = resolveWorkspacePatterns(workspacePatterns, availableFiles);
899
- const rootScripts = rootPkg?.scripts || {};
900
- const apps = [];
901
- for (const dir of dirs) {
902
- const pkgContent = files[`${dir}/package.json`];
903
- const pkg = safeParse(pkgContent);
904
- const name = pkg?.name ?? dir.split("/").pop() ?? dir;
905
- let framework = detectFramework(pkgContent, pm);
906
- if (framework && rootScripts) {
907
- const serviceName = dir.split("/").pop() ?? name;
908
- const buildKey = [
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
- return {
944
- isMonorepo: apps.length > 0,
945
- apps
946
- };
947
- }
948
- function analyzeRepo(files) {
949
- const pm = detectPackageManager(files);
950
- const framework = detectFramework(files["package.json"], pm);
951
- const envVars = detectEnvVars(files);
952
- const { isMonorepo, apps } = detectMonorepo(files, pm);
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
- packageManager: pm,
956
- envVars,
971
+ buildCommand,
972
+ startCommand,
973
+ port,
974
+ packageManager,
957
975
  isMonorepo,
958
- monorepoApps: apps
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
- const statusIcons = {
1111
- QUEUED: chalk.gray("○"),
1112
- BUILDING: chalk.yellow(""),
1113
- DEPLOYING: chalk.blue(""),
1114
- LIVE: chalk.green("●"),
1115
- BUILD_FAILED: chalk.red("●"),
1116
- FAILED: chalk.red("●"),
1117
- CANCELLED: chalk.gray("●")
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 logHeader = serviceName ? `📦 Build logs para ${chalk.bold(serviceName)}:` : "📦 Build logs:";
1132
- console.log(chalk.cyan(`\n${logHeader}`));
1133
- console.log(chalk.dim("".repeat(60)));
1134
- if (isVerbose) console.log(chalk.dim(`[verbose] Conectando ao stream de logs para deployment ${deploymentId}...`));
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
- process.stdout.write(`\n${icon} ${chalk.bold(label)}\n`);
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") process.stdout.write(event.content);
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
- } else if (isVerbose) console.log(chalk.yellow("[verbose] Nenhum log salvo encontrado para este deployment"));
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
- console.log(chalk.dim("\n" + "".repeat(60)));
1170
- if (finalStatus === "LIVE") success(serviceName ? `Deploy de ${chalk.bold(serviceName)} concluído! Serviço está ativo.` : "Deploy concluído! Serviço está ativo.");
1171
- else if (TERMINAL_STATUSES.has(finalStatus)) {
1172
- const errorMsg = serviceName ? `✗ Deploy de ${chalk.bold(serviceName)} finalizou com status: ${statusLabels[finalStatus] ?? finalStatus}` : `✗ Deploy finalizou com status: ${statusLabels[finalStatus] ?? finalStatus}`;
1173
- console.error(chalk.red(errorMsg));
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
- console.log(chalk.yellow("\n\nCancelando deploy(s)..."));
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
- console.log(chalk.cyan("\n🚀 Iniciando deploy paralelo de múltiplos serviços...\n"));
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
- console.log(`${chalk.green("")} ${chalk.bold(service.serviceName)}: Upload concluído (${sizeMB} MB)`);
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
- console.log(`${chalk.red("")} ${chalk.bold(service.serviceName)}: Falha ao iniciar deploy`);
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
- console.error(chalk.red("\n✗ Todos os deploys falharam ao iniciar."));
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
- console.log(chalk.cyan("\n📦 Monitorando progresso dos deploys:\n"));
1297
- console.log(chalk.dim("─".repeat(60)) + "\n");
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 (isVerbose) console.log(chalk.dim(`\n[verbose] ${service.serviceName}: status mudou para ${event.content}`));
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 (isVerbose) console.error(`\n${chalk.red("[verbose]")} Error streaming logs for ${service.serviceName}:`, error.message);
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 (successful.length > 0) {
1341
- console.log(chalk.green(`\n✅ ${successful.length} serviço(s) implantado(s) com sucesso:\n`));
1342
- for (const progress of successful) console.log(` ${chalk.green("✓")} ${chalk.bold(progress.serviceName)}`);
1343
- }
1344
- if (failed.length > 0) {
1345
- console.log(chalk.red(`\n✗ ${failed.length} serviço(s) falhou(aram):\n`));
1346
- for (const progress of failed) {
1347
- console.log(` ${chalk.red("✗")} ${chalk.bold(progress.serviceName)}`);
1348
- if (progress.logLines.length > 0) {
1349
- console.log(chalk.dim("─".repeat(40)));
1350
- const tail = progress.logLines.slice(-50);
1351
- for (const line of tail) console.log(` ${chalk.dim(line)}`);
1352
- console.log(chalk.dim("─".repeat(40)));
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
- console.log();
1928
- console.log(chalk.dim("─".repeat(40)));
1929
- console.log(` ${chalk.bold("Nome:")} ${settings.name}`);
1930
- console.log(` ${chalk.bold("Tipo:")} ${settings.type === "WEB" ? "Serviço Web" : "Site Estático"}`);
1931
- console.log(` ${chalk.bold("Branch:")} ${settings.branch}`);
1932
- if (settings.framework) console.log(` ${chalk.bold("Framework:")} ${settings.framework}`);
1933
- if (settings.packageManager) console.log(` ${chalk.bold("Package Mgr:")} ${settings.packageManager}`);
1934
- if (settings.buildCommand) console.log(` ${chalk.bold("Build:")} ${settings.buildCommand}`);
1935
- if (settings.startCommand) console.log(` ${chalk.bold("Start:")} ${settings.startCommand}`);
1936
- if (settings.outputDir) console.log(` ${chalk.bold("Output:")} ${settings.outputDir}`);
1937
- if (settings.port) console.log(` ${chalk.bold("Porta:")} ${settings.port}`);
1938
- console.log(chalk.dim("".repeat(40)));
1939
- console.log();
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.key}`);
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 v of detectedVars) {
2016
- const finalVal = await prompt(` ${chalk.bold(v.key)}:${v.value ? chalk.dim(` (${v.value})`) : ""}`) || v.value;
2017
- if (finalVal) vars[v.key] = finalVal;
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.monorepoApps.length > 0) {
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.monorepoApps.filter((a) => a.path === opts.app);
2290
+ selectedApps = detection.services.filter((a) => a.root === opts.app);
2043
2291
  if (selectedApps.length === 0) {
2044
- const available = detection.monorepoApps.map((a) => ` • ${a.path}`).join("\n");
2045
- console.error(chalk.red(`\n✗ App '${opts.app}' não encontrado.\n\nApps disponíveis:\n${available}`));
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.monorepoApps;
2302
+ } else selectedApps = detection.services;
2049
2303
  else {
2050
- const selectedPaths = await promptMultiSelect("Quais apps deseja fazer o deploy?", detection.monorepoApps.map((app) => ({
2051
- label: `${app.name}${app.framework ? ` (${app.framework.label})` : ""} — ${app.path}`,
2052
- value: app.path
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.monorepoApps.filter((a) => selectedPaths.includes(a.path));
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: fw$1?.type ?? "WEB",
2062
- rootDir: app.path,
2314
+ type: "WEB",
2315
+ rootDir: app.root,
2063
2316
  branch,
2064
- framework: fw$1?.label ?? null,
2317
+ framework: app.framework,
2065
2318
  packageManager: pm,
2066
- buildCommand: fw$1?.buildCommand ?? null,
2067
- startCommand: fw$1?.startCommand ?? null,
2068
- outputDir: fw$1?.outputDir ?? null,
2069
- port: fw$1?.port ?? 3e3
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(`(${fw$1?.buildCommand ?? "—"})`)}`);
2077
- if (newBuild && fw$1) fw$1.buildCommand = newBuild;
2078
- const newStart = await prompt(` Start command: ${chalk.dim(`(${fw$1?.startCommand ?? "—"})`)}`);
2079
- if (newStart && fw$1) fw$1.startCommand = newStart;
2080
- const newPort = await prompt(` Port: ${chalk.dim(`(${fw$1?.port ?? 3e3})`)}`);
2081
- if (newPort && fw$1) fw$1.port = parseInt(newPort, 10) || fw$1.port;
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: fw$1?.type ?? "WEB",
2352
+ type: "WEB",
2102
2353
  branch,
2103
- rootDirectory: app.path,
2104
- buildCommand: fw$1?.buildCommand ?? void 0,
2105
- startCommand: fw$1?.startCommand ?? void 0,
2106
- port: fw$1?.port ?? 3e3
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.path] = {
2365
+ config.services[app.root] = {
2115
2366
  id: service$1.id,
2116
2367
  name: service$1.name,
2117
- type: (fw$1?.type ?? "web").toLowerCase(),
2118
- root: app.path,
2368
+ type: "web",
2369
+ root: app.root,
2119
2370
  branch,
2120
- build: fw$1?.buildCommand || fw$1?.outputDir ? {
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: fw$1?.startCommand ?? void 0,
2126
- port: fw$1?.port ?? 3e3
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.path);
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.path),
2141
- extraFiles: prepareExtraFiles(detectLocalRepo(app.path), {
2142
- type: app.framework?.type,
2143
- buildCommand: app.framework?.buildCommand ?? void 0,
2144
- startCommand: app.framework?.startCommand ?? void 0,
2145
- port: app.framework?.port ?? void 0,
2146
- rootDirectory: app.path,
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: fw?.type ?? "WEB",
2401
+ type: "WEB",
2156
2402
  rootDir: ".",
2157
2403
  branch,
2158
- framework: fw?.label ?? null,
2404
+ framework: detection.framework,
2159
2405
  packageManager: pm,
2160
- buildCommand: fw?.buildCommand ?? null,
2161
- startCommand: fw?.startCommand ?? null,
2162
- outputDir: fw?.outputDir ?? null,
2163
- port: fw?.port ?? 3e3
2406
+ buildCommand: detection.buildCommand,
2407
+ startCommand: detection.startCommand,
2408
+ outputDir: null,
2409
+ port: detection.port ?? 3e3
2164
2410
  };
2165
- if (fw) info(`Framework detectado: ${chalk.bold(fw.label)}`);
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
- const available = configuredServices.map((s) => ` • ${s.key} (${s.serviceName})`).join("\n");
2236
- console.error(chalk.red(`\n✗ Serviço '${options.service}' não encontrado.\n\nServiços disponíveis:\n${available}`));
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
- console.error(chalk.red("\n✗ Nenhum remote 'origin' encontrado. Adicione um remote git."));
2285
- process.exit(1);
2286
- }
2287
- console.log(chalk.white(` ${chalk.bold("Repositório:")} ${remote.owner}/${remote.repo}`));
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 = detection.framework?.type?.toLowerCase() ?? selectedService?.type?.toLowerCase() ?? "web";
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) console.log(`${tag}${formatTime(entry.timestamp)} ${entry.message}`);
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
- console.log();
2537
- for (const entry of allEntries) {
2538
- const tag = showTags ? `${getServiceTag(entry.serviceName, maxNameLen, entry.serviceIndex)} ` : "";
2539
- console.log(`${tag}${formatTime(entry.timestamp)} ${entry.message}`);
2540
- }
2541
- console.log();
2542
- info(`Mostrando ${allEntries.length} linha(s). Use ${chalk.bold("--follow")} para acompanhar em tempo real.`);
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
- console.error(chalk.red(`\n✗ Formato inválido: "${par}". Use CHAVE=VALOR.`));
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
- console.error(chalk.red("\n✗ Chave não pode estar vazia."));
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
- const lines$1 = [];
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
- lines$1.push(line);
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$1.join("\n");
2952
+ envContent = lines.join("\n");
2681
2953
  }
2682
2954
  const envVars = {};
2683
- const lines = envContent.split("\n");
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
- let key = trimmed.slice(0, eqIndex).trim();
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
- console.error(chalk.yellow("Nenhuma variável válida encontrada."));
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
- if (arquivo) {
2747
- writeFileSync(resolve(process.cwd(), arquivo), envContent + "\n", "utf-8");
2748
- success(`Variáveis exportadas para ${chalk.bold(arquivo)}`);
2749
- console.log(chalk.dim("Nota: Valores estão mascarados por segurança."));
2750
- } else {
2751
- console.log(chalk.bold("\n# Variáveis de Ambiente (valores mascarados)\n"));
2752
- console.log(envContent);
2753
- console.log();
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.slice(0, 8),
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
- success(`Domínio ${chalk.bold(dominio)} adicionado com sucesso!`);
2816
- console.log();
2817
- console.log(chalk.yellow.bold(" Instruções de DNS:"));
2818
- console.log(chalk.white(` ${result.dnsInstruction}`));
2819
- console.log();
2820
- info(`Após configurar o DNS, execute: ${chalk.bold(`veloz domains verify ${result.id.slice(0, 8)}`)}`);
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
- if (result.verified) {
2832
- success(`Domínio ${chalk.bold(result.domain.domain)} verificado com sucesso!`);
2833
- info("O certificado TLS será provisionado automaticamente.");
2834
- } else {
2835
- console.log(chalk.yellow(`\n⚠ Domínio ${chalk.bold(result.domain.domain)} ainda não verificado.`));
2836
- info("Verifique se o CNAME foi propagado e tente novamente.");
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
- for (const { service: svcConfig, index } of services) {
2881
- const service = await client.services.get({ serviceId: svcConfig.id });
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
- console.log(chalk.dim("\nValores atualizados:"));
2919
- for (const [key, value] of Object.entries(updates)) {
2920
- const displayKey = key.replace(/([A-Z])/g, " $1").replace(/^./, (str) => str.toUpperCase()).trim();
2921
- console.log(` ${chalk.bold(displayKey)}: ${formatValue(value)}`);
2922
- }
2923
- console.log();
2924
- info("Execute 'veloz deploy' para aplicar as mudanças.");
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$1 = await prompt(`Start command ${chalk.dim(`(${service.startCommand || "—"})`)}: `);
2944
- if (startCmd$1) updates.startCommand = startCmd$1 === "none" ? null : startCmd$1;
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
- console.error(chalk.red(`\n✗ Serviço '${servicoArg}' não encontrado.`));
3025
- console.log(chalk.dim("\nServiços disponíveis:"));
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
- success(`Serviço padrão: ${chalk.bold(selectedService.name)} (${selectedKey})`);
3040
- console.log(chalk.dim(`\nComandos como ${chalk.white("logs")}, ${chalk.white("env")}, ${chalk.white("config")} vão usar este serviço automaticamente.`));
3041
- console.log(chalk.dim(`Use ${chalk.white("--service <nome>")} para sobrescrever pontualmente.`));
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
- success("Chave de API criada!");
3081
- console.log();
3082
- console.log(chalk.bold(" Chave:"), chalk.green(data.key));
3083
- console.log(chalk.bold(" Nome:"), data.name);
3084
- if (data.expiresAt) console.log(chalk.bold(" Expira:"), new Date(data.expiresAt).toLocaleDateString("pt-BR"));
3085
- else console.log(chalk.bold(" Expira:"), "Nunca");
3086
- console.log();
3087
- console.log(chalk.yellow(" Guarde esta chave — ela não será exibida novamente."));
3088
- console.log();
3089
- console.log(chalk.dim(" Uso em CI:"));
3090
- console.log(chalk.dim(` VELOZ_API_KEY=${data.key} veloz deploy -y --service web`));
3091
- console.log();
3092
- console.log(chalk.dim(" GitHub Actions:"));
3093
- console.log(chalk.dim(` gh secret set VELOZ_API_KEY --body "${data.key}"`));
3094
- console.log();
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
- console.log();
3144
- console.log(` ${chalk.bold("Nome:")} ${user.name}`);
3145
- console.log(` ${chalk.bold("Email:")} ${user.email}`);
3146
- console.log();
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.7");
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);