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.
- package/dist/index.mjs +191 -88
- 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(
|
|
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
|
|
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
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
1792
|
+
await withRetry(() => uploadSource(deployment.id, process.cwd(), extraFiles));
|
|
1804
1793
|
spinUpload.stop();
|
|
1805
1794
|
success("Deploy iniciado com sucesso!");
|
|
1806
|
-
await streamDeploymentLogs(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
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 (!
|
|
1969
|
-
const
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
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(
|
|
2033
|
+
await promptEnvVars(service$1.id, serviceDetection.envVars);
|
|
2030
2034
|
}
|
|
2031
|
-
await deployServicesInParallel(
|
|
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 (!
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
2449
|
+
await streamFollow(services, maxNameLen, tailLines);
|
|
2439
2450
|
} else {
|
|
2440
2451
|
spin.stop();
|
|
2441
|
-
await fetchRecent(
|
|
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
|