onveloz 0.0.0-beta.1 → 0.0.0-beta.2

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 +191 -88
  2. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -65,9 +65,16 @@ function isAuthenticated() {
65
65
  return loadConfig$1().apiKey.length > 0;
66
66
  }
67
67
  let envApiUrlLogged = false;
68
- async function requireAuth() {
68
+ async function requireAuth(options) {
69
+ const envApiKey = process.env.VELOZ_API_KEY;
70
+ if (envApiKey) return {
71
+ apiKey: envApiKey,
72
+ apiUrl: ENV_API_URL ?? DEFAULT_API_URL
73
+ };
74
+ if ("VELOZ_API_KEY" in process.env) throw new Error("VELOZ_API_KEY está definida mas vazia. Verifique o valor do secret.");
69
75
  let config = loadConfig$1();
70
76
  if (!config.apiKey) {
77
+ if (options?.nonInteractive) throw new Error("Nenhuma autenticação encontrada. Defina VELOZ_API_KEY ou execute `veloz login`.");
71
78
  await performLogin(config.apiUrl);
72
79
  config = loadConfig$1();
73
80
  }
@@ -1033,21 +1040,11 @@ async function createTarball(directory, extraFiles) {
1033
1040
  } catch {}
1034
1041
  }
1035
1042
  }
1036
- async function uploadSource(apiUrl, deploymentId, directory, token, extraFiles) {
1043
+ async function uploadSource(deploymentId, directory, extraFiles) {
1037
1044
  const { path: tarPath } = await createTarball(directory, extraFiles);
1045
+ const client = await getClient();
1038
1046
  try {
1039
- const urlResponse = await fetch(`${apiUrl}/api/deployments/${deploymentId}/upload-url`, {
1040
- method: "POST",
1041
- headers: {
1042
- Authorization: `Bearer ${token}`,
1043
- "Content-Type": "application/json"
1044
- }
1045
- });
1046
- if (!urlResponse.ok) {
1047
- const err = await urlResponse.json().catch(() => ({ error: "Unknown error" }));
1048
- throw new Error(`Failed to get upload URL: ${err.error || urlResponse.statusText}`);
1049
- }
1050
- const { uploadUrl, objectKey } = await urlResponse.json();
1047
+ const { uploadUrl, objectKey } = await client.deployments.getUploadUrl({ deploymentId });
1051
1048
  const fileBuffer = readFileSync(tarPath);
1052
1049
  const putResponse = await fetch(uploadUrl, {
1053
1050
  method: "PUT",
@@ -1055,18 +1052,10 @@ async function uploadSource(apiUrl, deploymentId, directory, token, extraFiles)
1055
1052
  body: fileBuffer
1056
1053
  });
1057
1054
  if (!putResponse.ok) throw new Error(`S3 upload failed: ${putResponse.status} ${putResponse.statusText}`);
1058
- const buildResponse = await fetch(`${apiUrl}/api/deployments/${deploymentId}/build`, {
1059
- method: "POST",
1060
- headers: {
1061
- Authorization: `Bearer ${token}`,
1062
- "Content-Type": "application/json"
1063
- },
1064
- body: JSON.stringify({ objectKey })
1055
+ await client.deployments.startBuild({
1056
+ deploymentId,
1057
+ objectKey
1065
1058
  });
1066
- if (!buildResponse.ok) {
1067
- const err = await buildResponse.json().catch(() => ({ error: "Unknown error" }));
1068
- throw new Error(`Failed to trigger build: ${err.error || buildResponse.statusText}`);
1069
- }
1070
1059
  } finally {
1071
1060
  await rm(tarPath, {
1072
1061
  force: true,
@@ -1101,7 +1090,8 @@ const TERMINAL_STATUSES$1 = new Set([
1101
1090
  "FAILED",
1102
1091
  "CANCELLED"
1103
1092
  ]);
1104
- async function streamDeploymentLogs(client, deploymentId, _serviceId, serviceName) {
1093
+ async function streamDeploymentLogs(deploymentId, serviceName) {
1094
+ const client = await getClient();
1105
1095
  const isVerbose = process.env.VELOZ_VERBOSE === "true";
1106
1096
  const logHeader = serviceName ? `📦 Build logs para ${chalk.bold(serviceName)}:` : "📦 Build logs:";
1107
1097
  console.log(chalk.cyan(`\n${logHeader}`));
@@ -1216,7 +1206,8 @@ function renderProgress(progressMap, prevLineCount) {
1216
1206
  }
1217
1207
  return lineCount;
1218
1208
  }
1219
- async function deployServicesInParallel(client, services) {
1209
+ async function deployServicesInParallel(services) {
1210
+ const client = await getClient();
1220
1211
  console.log(chalk.cyan("\n🚀 Iniciando deploy paralelo de múltiplos serviços...\n"));
1221
1212
  const progressMap = /* @__PURE__ */ new Map();
1222
1213
  const projectRoot = process.cwd();
@@ -1225,8 +1216,7 @@ async function deployServicesInParallel(client, services) {
1225
1216
  const deploymentPromises = services.map(async (service) => {
1226
1217
  try {
1227
1218
  const deployment = await withRetry$1(() => client.deployments.create({ serviceId: service.serviceId }));
1228
- const authConfig = await requireAuth();
1229
- await withRetry$1(() => uploadSource(authConfig.apiUrl, deployment.id, projectRoot, authConfig.apiKey, service.extraFiles));
1219
+ await withRetry$1(() => uploadSource(deployment.id, projectRoot, service.extraFiles));
1230
1220
  progressMap.set(service.serviceId, {
1231
1221
  serviceName: service.serviceName,
1232
1222
  deploymentId: deployment.id,
@@ -1779,7 +1769,6 @@ async function triggerDeploy(serviceId, serviceName) {
1779
1769
  const sizeMB = Math.round(sizeInBytes / (1024 * 1024) * 10) / 10;
1780
1770
  if (sizeMB > 5) spinUpload.text = `Fazendo upload (${sizeMB} MB)...`;
1781
1771
  const client = await getClient();
1782
- const authConfig = await requireAuth();
1783
1772
  const velozConfig = loadConfig();
1784
1773
  let serviceConf;
1785
1774
  if (velozConfig) {
@@ -1800,10 +1789,10 @@ async function triggerDeploy(serviceId, serviceName) {
1800
1789
  spinUpload.text = "Iniciando deploy...";
1801
1790
  const deployment = await withRetry(() => client.deployments.create({ serviceId }));
1802
1791
  spinUpload.text = "Fazendo upload do código...";
1803
- await withRetry(() => uploadSource(authConfig.apiUrl, deployment.id, process.cwd(), authConfig.apiKey, extraFiles));
1792
+ await withRetry(() => uploadSource(deployment.id, process.cwd(), extraFiles));
1804
1793
  spinUpload.stop();
1805
1794
  success("Deploy iniciado com sucesso!");
1806
- await streamDeploymentLogs(client, deployment.id, serviceId, serviceName);
1795
+ await streamDeploymentLogs(deployment.id, serviceName);
1807
1796
  } catch (error) {
1808
1797
  spinUpload.stop();
1809
1798
  handleError(error);
@@ -1914,7 +1903,7 @@ function detectLocalRepo(basePath = ".") {
1914
1903
  }
1915
1904
  return analyzeRepo(files);
1916
1905
  }
1917
- async function promptEnvVars(client, serviceId, detectedVars) {
1906
+ async function promptEnvVars(serviceId, detectedVars) {
1918
1907
  if (detectedVars.length === 0) return;
1919
1908
  console.log(chalk.cyan(`\n📝 ${detectedVars.length} variável(is) de ambiente detectada(s):\n`));
1920
1909
  for (const v of detectedVars) console.log(` • ${v.key}`);
@@ -1928,6 +1917,7 @@ async function promptEnvVars(client, serviceId, detectedVars) {
1928
1917
  const filled = Object.keys(vars).length;
1929
1918
  if (filled > 0) {
1930
1919
  const spinVars = spinner("Definindo variáveis de ambiente...");
1920
+ const client = await getClient();
1931
1921
  await withRetry(() => client.envVars.setBulk({
1932
1922
  serviceId,
1933
1923
  vars
@@ -1936,7 +1926,8 @@ async function promptEnvVars(client, serviceId, detectedVars) {
1936
1926
  success(`${filled} variável(is) definida(s).`);
1937
1927
  }
1938
1928
  }
1939
- async function createServiceFlow(client, projectId, projectName, repoName) {
1929
+ async function createServiceFlow(projectId, projectName, repoName, opts = {}) {
1930
+ const client = await getClient();
1940
1931
  const branch = getGitBranch();
1941
1932
  const spinDetect = spinner("Detectando framework...");
1942
1933
  const detection = detectLocalRepo();
@@ -1944,11 +1935,22 @@ async function createServiceFlow(client, projectId, projectName, repoName) {
1944
1935
  const pm = detection.packageManager;
1945
1936
  if (detection.isMonorepo && detection.monorepoApps.length > 0) {
1946
1937
  info(`Monorepo detectado (${pm})`);
1947
- const selectedPaths = await promptMultiSelect("Quais apps deseja fazer o deploy?", detection.monorepoApps.map((app) => ({
1948
- label: `${app.name}${app.framework ? ` (${app.framework.label})` : ""} — ${app.path}`,
1949
- value: app.path
1950
- })));
1951
- const selectedApps = detection.monorepoApps.filter((a) => selectedPaths.includes(a.path));
1938
+ let selectedApps;
1939
+ if (opts.yes) if (opts.app) {
1940
+ selectedApps = detection.monorepoApps.filter((a) => a.path === opts.app);
1941
+ if (selectedApps.length === 0) {
1942
+ const available = detection.monorepoApps.map((a) => ` • ${a.path}`).join("\n");
1943
+ console.error(chalk.red(`\n✗ App '${opts.app}' não encontrado.\n\nApps disponíveis:\n${available}`));
1944
+ process.exit(1);
1945
+ }
1946
+ } else selectedApps = detection.monorepoApps;
1947
+ else {
1948
+ const selectedPaths = await promptMultiSelect("Quais apps deseja fazer o deploy?", detection.monorepoApps.map((app) => ({
1949
+ label: `${app.name}${app.framework ? ` (${app.framework.label})` : ""} — ${app.path}`,
1950
+ value: app.path
1951
+ })));
1952
+ selectedApps = detection.monorepoApps.filter((a) => selectedPaths.includes(a.path));
1953
+ }
1952
1954
  for (const app of selectedApps) {
1953
1955
  const fw$1 = app.framework;
1954
1956
  console.log(chalk.cyan(`\n── ${app.name} ──`));
@@ -1965,15 +1967,17 @@ async function createServiceFlow(client, projectId, projectName, repoName) {
1965
1967
  port: fw$1?.port ?? 3e3
1966
1968
  });
1967
1969
  }
1968
- if (!await promptConfirm("Confirmar e fazer deploy?")) for (const app of selectedApps) {
1969
- const fw$1 = app.framework;
1970
- console.log(chalk.cyan(`\n── Editar: ${app.name} ──`));
1971
- const newBuild = await prompt(` Build command: ${chalk.dim(`(${fw$1?.buildCommand ?? "—"})`)}`);
1972
- if (newBuild && fw$1) fw$1.buildCommand = newBuild;
1973
- const newStart = await prompt(` Start command: ${chalk.dim(`(${fw$1?.startCommand ?? "—"})`)}`);
1974
- if (newStart && fw$1) fw$1.startCommand = newStart;
1975
- const newPort = await prompt(` Port: ${chalk.dim(`(${fw$1?.port ?? 3e3})`)}`);
1976
- if (newPort && fw$1) fw$1.port = parseInt(newPort, 10) || fw$1.port;
1970
+ if (!opts.yes) {
1971
+ if (!await promptConfirm("Confirmar e fazer deploy?")) for (const app of selectedApps) {
1972
+ const fw$1 = app.framework;
1973
+ console.log(chalk.cyan(`\n── Editar: ${app.name} ──`));
1974
+ const newBuild = await prompt(` Build command: ${chalk.dim(`(${fw$1?.buildCommand ?? "—"})`)}`);
1975
+ if (newBuild && fw$1) fw$1.buildCommand = newBuild;
1976
+ const newStart = await prompt(` Start command: ${chalk.dim(`(${fw$1?.startCommand ?? "—"})`)}`);
1977
+ if (newStart && fw$1) fw$1.startCommand = newStart;
1978
+ const newPort = await prompt(` Port: ${chalk.dim(`(${fw$1?.port ?? 3e3})`)}`);
1979
+ if (newPort && fw$1) fw$1.port = parseInt(newPort, 10) || fw$1.port;
1980
+ }
1977
1981
  }
1978
1982
  const config = {
1979
1983
  version: "1.0",
@@ -2023,12 +2027,12 @@ async function createServiceFlow(client, projectId, projectName, repoName) {
2023
2027
  }
2024
2028
  saveConfig(config);
2025
2029
  info(`Arquivo ${getConfigFileName()} criado na raiz do projeto.`);
2026
- for (const { service: service$1, app } of createdServices) {
2030
+ if (!opts.yes) for (const { service: service$1, app } of createdServices) {
2027
2031
  console.log(chalk.cyan(`\n── Configurando variáveis: ${app.name} ──\n`));
2028
2032
  const serviceDetection = detectLocalRepo(app.path);
2029
- await promptEnvVars(client, service$1.id, serviceDetection.envVars);
2033
+ await promptEnvVars(service$1.id, serviceDetection.envVars);
2030
2034
  }
2031
- await deployServicesInParallel(client, createdServices.map(({ service: service$1, app }) => ({
2035
+ await deployServicesInParallel(createdServices.map(({ service: service$1, app }) => ({
2032
2036
  serviceId: service$1.id,
2033
2037
  serviceName: app.name,
2034
2038
  path: resolve(process.cwd(), app.path),
@@ -2058,22 +2062,24 @@ async function createServiceFlow(client, projectId, projectName, repoName) {
2058
2062
  };
2059
2063
  if (fw) info(`Framework detectado: ${chalk.bold(fw.label)}`);
2060
2064
  printSummary(settings);
2061
- if (!await promptConfirm("Confirmar e fazer deploy?")) {
2062
- const newName = await prompt(`Nome do serviço: ${chalk.dim(`(${settings.name})`)}`);
2063
- if (newName) settings.name = newName;
2064
- settings.type = await promptSelect("Tipo de serviço:", [{
2065
- label: "Serviço Web",
2066
- value: "WEB"
2067
- }, {
2068
- label: "Site Estático",
2069
- value: "STATIC"
2070
- }]);
2071
- const newBuild = await prompt(`Build command: ${chalk.dim(`(${settings.buildCommand ?? "—"})`)}`);
2072
- if (newBuild) settings.buildCommand = newBuild;
2073
- const newStart = await prompt(`Start command: ${chalk.dim(`(${settings.startCommand ?? "—"})`)}`);
2074
- if (newStart) settings.startCommand = newStart;
2075
- const newPort = await prompt(`Port: ${chalk.dim(`(${settings.port})`)}`);
2076
- if (newPort) settings.port = parseInt(newPort, 10) || settings.port;
2065
+ if (!opts.yes) {
2066
+ if (!await promptConfirm("Confirmar e fazer deploy?")) {
2067
+ const newName = await prompt(`Nome do serviço: ${chalk.dim(`(${settings.name})`)}`);
2068
+ if (newName) settings.name = newName;
2069
+ settings.type = await promptSelect("Tipo de serviço:", [{
2070
+ label: "Serviço Web",
2071
+ value: "WEB"
2072
+ }, {
2073
+ label: "Site Estático",
2074
+ value: "STATIC"
2075
+ }]);
2076
+ const newBuild = await prompt(`Build command: ${chalk.dim(`(${settings.buildCommand ?? "—"})`)}`);
2077
+ if (newBuild) settings.buildCommand = newBuild;
2078
+ const newStart = await prompt(`Start command: ${chalk.dim(`(${settings.startCommand ?? "—"})`)}`);
2079
+ if (newStart) settings.startCommand = newStart;
2080
+ const newPort = await prompt(`Port: ${chalk.dim(`(${settings.port})`)}`);
2081
+ if (newPort) settings.port = parseInt(newPort, 10) || settings.port;
2082
+ }
2077
2083
  }
2078
2084
  const spinService = spinner("Criando serviço...");
2079
2085
  const service = await withRetry(() => client.services.create({
@@ -2088,7 +2094,7 @@ async function createServiceFlow(client, projectId, projectName, repoName) {
2088
2094
  }));
2089
2095
  spinService.stop();
2090
2096
  success(`Serviço criado: ${chalk.bold(service.name)}`);
2091
- await promptEnvVars(client, service.id, detection.envVars);
2097
+ if (!opts.yes) await promptEnvVars(service.id, detection.envVars);
2092
2098
  saveConfig({
2093
2099
  version: "1.0",
2094
2100
  project: {
@@ -2115,10 +2121,10 @@ async function createServiceFlow(client, projectId, projectName, repoName) {
2115
2121
  info(`Arquivo ${getConfigFileName()} criado na raiz do projeto.`);
2116
2122
  return service.id;
2117
2123
  }
2118
- const deployCommand = new Command("deploy").description("Fazer deploy do serviço (auto-detecta projeto pelo git)").option("-a, --all", "Deploy todos os serviços do monorepo").option("--service <service>", "Deploy de um serviço específico (chave ou nome)").option("-v, --verbose", "Mostrar logs detalhados do servidor").action(async (options) => {
2124
+ const deployCommand = new Command("deploy").description("Fazer deploy do serviço (auto-detecta projeto pelo git)").option("-a, --all", "Deploy todos os serviços do monorepo").option("--service <service>", "Deploy de um serviço específico (chave ou nome)").option("--app <path>", "App do monorepo para deploy (ex: apps/web)").option("-y, --yes", "Auto-confirmar tudo (modo não-interativo)").option("-v, --verbose", "Mostrar logs detalhados do servidor").action(async (options) => {
2119
2125
  if (options.verbose) process.env.VELOZ_VERBOSE = "true";
2120
2126
  try {
2121
- await requireAuth();
2127
+ await requireAuth({ nonInteractive: options.yes });
2122
2128
  const configuredServices = await findServicesFromConfig();
2123
2129
  if (configuredServices.length > 0) {
2124
2130
  if (options.service) {
@@ -2131,8 +2137,8 @@ const deployCommand = new Command("deploy").description("Fazer deploy do serviç
2131
2137
  await triggerDeploy(found.serviceId, found.serviceName);
2132
2138
  return;
2133
2139
  }
2134
- if (options.all || configuredServices.length === 1) {
2135
- if (configuredServices.length > 1) {
2140
+ if (options.all || options.yes || configuredServices.length === 1) {
2141
+ if (configuredServices.length > 1 && !options.yes) {
2136
2142
  console.log(chalk.cyan(`\n🚀 Fazendo deploy de ${configuredServices.length} serviço(s):\n`));
2137
2143
  for (const service of configuredServices) {
2138
2144
  const relPath = relative(process.cwd(), service.path) || ".";
@@ -2145,10 +2151,7 @@ const deployCommand = new Command("deploy").description("Fazer deploy do serviç
2145
2151
  }
2146
2152
  }
2147
2153
  if (configuredServices.length === 1) await triggerDeploy(configuredServices[0].serviceId, configuredServices[0].serviceName);
2148
- else {
2149
- const servicesWithExtraFiles = computeExtraFilesForServices(configuredServices);
2150
- await deployServicesInParallel(await getClient(), servicesWithExtraFiles);
2151
- }
2154
+ else await deployServicesInParallel(computeExtraFilesForServices(configuredServices));
2152
2155
  return;
2153
2156
  } else {
2154
2157
  console.log(chalk.bold("\n📦 Serviços disponíveis:\n"));
@@ -2165,10 +2168,7 @@ const deployCommand = new Command("deploy").description("Fazer deploy do serviç
2165
2168
  return;
2166
2169
  }
2167
2170
  if (selectedServices.length === 1) await triggerDeploy(selectedServices[0].serviceId, selectedServices[0].serviceName);
2168
- else {
2169
- const servicesWithExtraFiles = computeExtraFilesForServices(selectedServices);
2170
- await deployServicesInParallel(await getClient(), servicesWithExtraFiles);
2171
- }
2171
+ else await deployServicesInParallel(computeExtraFilesForServices(selectedServices));
2172
2172
  return;
2173
2173
  }
2174
2174
  }
@@ -2198,6 +2198,10 @@ const deployCommand = new Command("deploy").description("Fazer deploy do serviç
2198
2198
  const svc = project.services[0];
2199
2199
  serviceId = svc.id;
2200
2200
  serviceName = svc.name;
2201
+ } else if (options.yes) {
2202
+ const svc = project.services[0];
2203
+ serviceId = svc.id;
2204
+ serviceName = svc.name;
2201
2205
  } else {
2202
2206
  serviceId = await promptSelect("Selecione o serviço:", project.services.map((s) => ({
2203
2207
  label: `${s.name} (${s.type} — branch: ${s.branch})`,
@@ -2228,11 +2232,14 @@ const deployCommand = new Command("deploy").description("Fazer deploy do serviç
2228
2232
  if (project && project.services.length === 0) {
2229
2233
  info(`Projeto encontrado: ${chalk.bold(project.name)}`);
2230
2234
  info("Nenhum serviço configurado. Vamos criar um.");
2231
- await triggerDeploy(await createServiceFlow(client, project.id, project.name, remote.repo));
2235
+ await triggerDeploy(await createServiceFlow(project.id, project.name, remote.repo, {
2236
+ yes: options.yes,
2237
+ app: options.app
2238
+ }));
2232
2239
  return;
2233
2240
  }
2234
2241
  info("Projeto não encontrado. Vamos criar um novo.");
2235
- const projectName = await prompt(`Nome do projeto: ${chalk.dim(`(${remote.repo})`)}`) || remote.repo;
2242
+ const projectName = options.yes ? remote.repo : await prompt(`Nome do projeto: ${chalk.dim(`(${remote.repo})`)}`) || remote.repo;
2236
2243
  const spinProject = spinner("Criando projeto...");
2237
2244
  const newProject = await withRetry(() => client.projects.create({
2238
2245
  name: projectName,
@@ -2241,7 +2248,10 @@ const deployCommand = new Command("deploy").description("Fazer deploy do serviç
2241
2248
  }));
2242
2249
  spinProject.stop();
2243
2250
  success(`Projeto criado: ${chalk.bold(newProject.name)}`);
2244
- await triggerDeploy(await createServiceFlow(client, newProject.id, newProject.name, remote.repo));
2251
+ await triggerDeploy(await createServiceFlow(newProject.id, newProject.name, remote.repo, {
2252
+ yes: options.yes,
2253
+ app: options.app
2254
+ }));
2245
2255
  } catch (error) {
2246
2256
  handleError(error);
2247
2257
  }
@@ -2385,7 +2395,8 @@ function resolveAllServices(serviceFlag) {
2385
2395
  function formatTime(timestamp) {
2386
2396
  return chalk.dim(new Date(timestamp).toLocaleTimeString("pt-BR"));
2387
2397
  }
2388
- async function streamFollow(client, services, maxNameLen, tailLines) {
2398
+ async function streamFollow(services, maxNameLen, tailLines) {
2399
+ const client = await getClient();
2389
2400
  const showTags = services.length > 1;
2390
2401
  const streams = services.map(async ({ service, index }) => {
2391
2402
  const tag = showTags ? `${getServiceTag(service.name, maxNameLen, index)} ` : "";
@@ -2401,7 +2412,8 @@ async function streamFollow(client, services, maxNameLen, tailLines) {
2401
2412
  });
2402
2413
  await Promise.allSettled(streams);
2403
2414
  }
2404
- async function fetchRecent(client, services, maxNameLen, tailLines) {
2415
+ async function fetchRecent(services, maxNameLen, tailLines) {
2416
+ const client = await getClient();
2405
2417
  const showTags = services.length > 1;
2406
2418
  const allEntries = (await Promise.allSettled(services.map(async ({ service, index }) => {
2407
2419
  return (await client.logs.getRecent({
@@ -2429,16 +2441,15 @@ const logsCommand = new Command("logs").description("Visualizar logs dos serviç
2429
2441
  const spin = spinner("Carregando logs...");
2430
2442
  try {
2431
2443
  const { services, maxNameLen } = resolveAllServices(opts.service);
2432
- const client = await getClient();
2433
2444
  const tailLines = parseInt(opts.tail, 10) || 50;
2434
2445
  if (opts.follow) {
2435
2446
  spin.text = services.length > 1 ? `Conectando a ${services.length} serviço(s)...` : "Conectando ao streaming de logs...";
2436
2447
  spin.stop();
2437
2448
  info("Streaming de logs ativo. Pressione Ctrl+C para sair.\n");
2438
- await streamFollow(client, services, maxNameLen, tailLines);
2449
+ await streamFollow(services, maxNameLen, tailLines);
2439
2450
  } else {
2440
2451
  spin.stop();
2441
- await fetchRecent(client, services, maxNameLen, tailLines);
2452
+ await fetchRecent(services, maxNameLen, tailLines);
2442
2453
  }
2443
2454
  } catch (error) {
2444
2455
  spin.stop();
@@ -2929,6 +2940,97 @@ const useCommand = new Command("use").description("Selecionar qual serviço usar
2929
2940
  }
2930
2941
  });
2931
2942
 
2943
+ //#endregion
2944
+ //#region src/commands/apikey.ts
2945
+ async function authFetch(path, options = {}) {
2946
+ const config = await requireAuth();
2947
+ const url = `${config.apiUrl}/api/auth${path}`;
2948
+ const res = await fetch(url, {
2949
+ ...options,
2950
+ headers: {
2951
+ "Content-Type": "application/json",
2952
+ Authorization: `Bearer ${config.apiKey}`,
2953
+ ...options.headers
2954
+ }
2955
+ });
2956
+ if (!res.ok) {
2957
+ const body = await res.text().catch(() => "");
2958
+ throw new Error(`Erro ${res.status}: ${body || res.statusText}`);
2959
+ }
2960
+ return res.json();
2961
+ }
2962
+ const apikeyCommand = new Command("apikey").description("Gerenciar chaves de API");
2963
+ apikeyCommand.command("create").description("Criar uma nova chave de API").option("--name <name>", "Nome da chave", "cli").option("--no-expire", "Chave sem expiração").action(async (opts) => {
2964
+ try {
2965
+ const s = spinner("Criando chave de API...");
2966
+ const body = {
2967
+ name: opts.name,
2968
+ prefix: "veloz"
2969
+ };
2970
+ if (opts.expire === false) body.expiresIn = null;
2971
+ const data = await authFetch("/api-key/create", {
2972
+ method: "POST",
2973
+ body: JSON.stringify(body)
2974
+ });
2975
+ s.stop();
2976
+ success("Chave de API criada!");
2977
+ console.log();
2978
+ console.log(chalk.bold(" Chave:"), chalk.green(data.key));
2979
+ console.log(chalk.bold(" Nome:"), data.name);
2980
+ if (data.expiresAt) console.log(chalk.bold(" Expira:"), new Date(data.expiresAt).toLocaleDateString("pt-BR"));
2981
+ else console.log(chalk.bold(" Expira:"), "Nunca");
2982
+ console.log();
2983
+ console.log(chalk.yellow(" Guarde esta chave — ela não será exibida novamente."));
2984
+ console.log();
2985
+ console.log(chalk.dim(" Uso em CI:"));
2986
+ console.log(chalk.dim(` VELOZ_API_KEY=${data.key} veloz deploy -y --service web`));
2987
+ console.log();
2988
+ console.log(chalk.dim(" GitHub Actions:"));
2989
+ console.log(chalk.dim(` gh secret set VELOZ_API_KEY --body "${data.key}"`));
2990
+ console.log();
2991
+ } catch (error) {
2992
+ handleError(error);
2993
+ }
2994
+ });
2995
+ apikeyCommand.command("list").alias("listar").description("Listar chaves de API").action(async () => {
2996
+ try {
2997
+ const s = spinner("Buscando chaves...");
2998
+ const data = await authFetch("/api-key/list", { method: "GET" });
2999
+ s.stop();
3000
+ const keys = Array.isArray(data) ? data : data.apiKeys ?? [];
3001
+ if (!keys || keys.length === 0) {
3002
+ info("Nenhuma chave de API encontrada.");
3003
+ return;
3004
+ }
3005
+ printTable([
3006
+ "Nome",
3007
+ "ID",
3008
+ "Criado",
3009
+ "Expira"
3010
+ ], keys.map((k) => [
3011
+ k.name ?? "-",
3012
+ k.id,
3013
+ new Date(k.createdAt).toLocaleDateString("pt-BR"),
3014
+ k.expiresAt ? new Date(k.expiresAt).toLocaleDateString("pt-BR") : "Nunca"
3015
+ ]));
3016
+ } catch (error) {
3017
+ handleError(error);
3018
+ }
3019
+ });
3020
+ apikeyCommand.command("delete <keyId>").alias("deletar").description("Deletar uma chave de API").action(async (keyId) => {
3021
+ try {
3022
+ const s = spinner("Deletando chave...");
3023
+ await authFetch("/api-key/delete", {
3024
+ method: "POST",
3025
+ body: JSON.stringify({ keyId })
3026
+ });
3027
+ s.stop();
3028
+ success("Chave deletada.");
3029
+ } catch (error) {
3030
+ handleError(error);
3031
+ }
3032
+ });
3033
+
2932
3034
  //#endregion
2933
3035
  //#region src/index.ts
2934
3036
  const version = process.env.npm_package_version;
@@ -2943,6 +3045,7 @@ program.addCommand(envCommand);
2943
3045
  program.addCommand(domainsCommand);
2944
3046
  program.addCommand(configCommand);
2945
3047
  program.addCommand(useCommand);
3048
+ program.addCommand(apikeyCommand);
2946
3049
  program.parse();
2947
3050
 
2948
3051
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "onveloz",
3
- "version": "0.0.0-beta.1",
3
+ "version": "0.0.0-beta.2",
4
4
  "description": "CLI da plataforma Veloz — deploy rápido para o Brasil",
5
5
  "type": "module",
6
6
  "license": "MIT",