@zenifra/cli 0.3.1 → 0.3.3
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/README.md +9 -0
- package/bin/zenifra.mjs +164 -17
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -47,6 +47,7 @@ zenifra profile add --name staging --description Homologacao --api-base https://
|
|
|
47
47
|
zenifra profile use staging
|
|
48
48
|
zenifra profile edit staging --description "Homologacao interna"
|
|
49
49
|
zenifra profile remove staging
|
|
50
|
+
zenifra whoami
|
|
50
51
|
zenifra orgs
|
|
51
52
|
zenifra org set
|
|
52
53
|
zenifra plans
|
|
@@ -65,6 +66,9 @@ zenifra create project --name <name> --plan queue-free --payment-mode hourly --c
|
|
|
65
66
|
zenifra projects --type http --page 1 --limit 15
|
|
66
67
|
zenifra projects --type valkey --page 1 --limit 15
|
|
67
68
|
zenifra project info --project <project-id>
|
|
69
|
+
zenifra project stop --project <project-id>
|
|
70
|
+
zenifra project resume --project <project-id>
|
|
71
|
+
zenifra project delete --project <project-id> --yes
|
|
68
72
|
zenifra valkey status --project <project-id>
|
|
69
73
|
zenifra valkey connection --project <project-id>
|
|
70
74
|
zenifra valkey credentials rotate --project <project-id> --wait
|
|
@@ -148,6 +152,8 @@ Fluxos:
|
|
|
148
152
|
- `zenifra deploy`: dispara o build/deploy GitHub e retorna o `build_id`
|
|
149
153
|
- `zenifra deploy watch`: usa esse `build_id` para acompanhar o build em tempo real e imprimir os logs incrementais ate o fim
|
|
150
154
|
|
|
155
|
+
Alguns builds antigos podem disponibilizar somente um resumo terminal. Nesse caso, a saida legivel avisa que eventos detalhados nao estavam disponiveis; `--json` preserva a resposta recebida da API.
|
|
156
|
+
|
|
151
157
|
Antes de uma mutacao, confirme o perfil, a API e a organizacao ativa. Para criacoes, confirme tambem o catalogo, plano, pagamento, exposicao, dominio, origem do deploy, porta, instancias e ambientes. Depois, leia o projeto de volta e acompanhe o build/deployment ate um estado terminal.
|
|
152
158
|
|
|
153
159
|
O dominio principal e um dominio personalizado sao campos diferentes. Nao repita o dominio principal em `config.custom_domains`; depois de adicionar um dominio personalizado, valide DNS/TLS e leia a URL final antes de considerar a operacao concluida.
|
|
@@ -174,6 +180,7 @@ Para executar a copia local deste repositorio, substitua `zenifra` por
|
|
|
174
180
|
|
|
175
181
|
- `--read-only` solicita somente leitura; as permissoes da sua organizacao continuam valendo.
|
|
176
182
|
- `--no-browser` mostra o endereco para abrir manualmente no navegador da mesma maquina.
|
|
183
|
+
- Em um terminal sem navegador, use `--no-browser` e abra o endereco em uma sessao grafica da mesma maquina. O retorno de autenticacao precisa ser concluido nessa maquina.
|
|
177
184
|
- O retorno usa uma porta local temporaria em `127.0.0.1`; nao precisa cadastrar uma porta fixa.
|
|
178
185
|
- O login aguarda ate tres minutos. Use `Ctrl+C` para cancelar.
|
|
179
186
|
- Os tokens ficam no perfil local privado e sao renovados automaticamente antes de expirar.
|
|
@@ -213,6 +220,8 @@ zenifra plans --type storage --json
|
|
|
213
220
|
|
|
214
221
|
`zenifra plans` funciona sem autenticacao e mostra os catalogos publicos de HTTP, banco, armazenamento e Valkey. Use `--type valkey` para consultar Key Value, Cache e Queue.
|
|
215
222
|
|
|
223
|
+
Para planos HTTP, a saida legivel tambem mostra as capacidades disponiveis, como logs, metricas, verificacao de saude, auto-scaling, subdominio personalizado e acesso de rede. Use `--json` quando precisar consumir o catalogo sem formatacao.
|
|
224
|
+
|
|
216
225
|
Valores de variaveis de ambiente sao mascarados por padrao, inclusive em `--json`.
|
|
217
226
|
Use `--show-values` apenas quando precisar inspecionar os valores completos.
|
|
218
227
|
|
package/bin/zenifra.mjs
CHANGED
|
@@ -80,6 +80,7 @@ const KNOWN_FLAG_NAMES = new Set([
|
|
|
80
80
|
'value',
|
|
81
81
|
'view',
|
|
82
82
|
'wait',
|
|
83
|
+
'yes',
|
|
83
84
|
]);
|
|
84
85
|
const ALLOWED_PLAN_VALUES = new Set([
|
|
85
86
|
'free',
|
|
@@ -175,6 +176,7 @@ Usage:
|
|
|
175
176
|
zenifra profile edit <name> [--description <text>] [--api-base <url>] [--json]
|
|
176
177
|
zenifra profile use <name> [--json]
|
|
177
178
|
zenifra profile remove <name> [--json]
|
|
179
|
+
zenifra whoami [--json]
|
|
178
180
|
zenifra orgs [--json]
|
|
179
181
|
zenifra org set [--org <id>]
|
|
180
182
|
zenifra plans [--type <all|http|database|storage|valkey>] [--json]
|
|
@@ -186,6 +188,9 @@ Usage:
|
|
|
186
188
|
zenifra valkey credentials rotate --project <id> [--idempotency-key <key>] [--wait] [--interval <seconds>] [--timeout <seconds>] [--json]
|
|
187
189
|
zenifra valkey credentials status --project <id> --operation <id> [--json]
|
|
188
190
|
zenifra project info --project <id> [--json]
|
|
191
|
+
zenifra project stop --project <id> [--json]
|
|
192
|
+
zenifra project resume --project <id> [--json]
|
|
193
|
+
zenifra project delete --project <id> --yes [--json]
|
|
189
194
|
zenifra project url --project <id> [--json]
|
|
190
195
|
zenifra project logs --project <id> [--instance <id>] [--json]
|
|
191
196
|
zenifra project metrics --project <id> [--instance <id>] [--json]
|
|
@@ -246,7 +251,7 @@ const HELP_SPECS = [
|
|
|
246
251
|
],
|
|
247
252
|
examples: ['zenifra auth login', 'zenifra auth login --profile staging --code 123456'],
|
|
248
253
|
output: 'Login realizado com sucesso.',
|
|
249
|
-
notes: ['Exige email, senha e, quando habilitado, codigo de desafio.'],
|
|
254
|
+
notes: ['Exige email, senha e, quando habilitado, codigo de desafio.', 'Em um terminal sem navegador, use --oauth --no-browser e abra o endereco em uma sessao grafica da mesma maquina.'],
|
|
250
255
|
},
|
|
251
256
|
{
|
|
252
257
|
command: 'auth api-key',
|
|
@@ -345,6 +350,16 @@ const HELP_SPECS = [
|
|
|
345
350
|
output: 'ID Nome Role Status\n507f1f77bcf86cd799439011 Minha org owner active',
|
|
346
351
|
jsonOutput: '[{"_id":"507f1f77bcf86cd799439011","name":"Minha org","role":"owner","status":"active"}]',
|
|
347
352
|
},
|
|
353
|
+
{
|
|
354
|
+
command: 'whoami',
|
|
355
|
+
usage: 'zenifra whoami [--json]',
|
|
356
|
+
description: 'Mostra o perfil ativo e a organizacao selecionada para os proximos comandos.',
|
|
357
|
+
flags: ['--json Imprime a identificacao em JSON.'],
|
|
358
|
+
examples: ['zenifra whoami', 'zenifra whoami --json'],
|
|
359
|
+
output: 'Perfil: staging\nAPI base: https://api.zenifra.com/v1\nAutenticacao: access_token\nOrganizacao: Minha organizacao (507f1f77bcf86cd799439011)',
|
|
360
|
+
jsonOutput: '{"profile":"staging","api_base_url":"https://api.zenifra.com/v1","authentication_mode":"access_token","selected_organization_id":"507f1f77bcf86cd799439011","organization_name":"Minha organizacao"}',
|
|
361
|
+
notes: ['Nenhuma credencial e exibida. Quando nao houver organizacao selecionada, use "zenifra org set" ou informe --org no comando.'],
|
|
362
|
+
},
|
|
348
363
|
{
|
|
349
364
|
command: 'org set',
|
|
350
365
|
usage: 'zenifra org set [--org <id>]',
|
|
@@ -426,17 +441,17 @@ const HELP_SPECS = [
|
|
|
426
441
|
description: 'Cria um projeto via wizard interativo ou via payload de configuracao JSON.',
|
|
427
442
|
flags: ['--name <name> Nome do projeto.', '--plan <plan> Plano do projeto.', '--payment-mode <mode> Modo de pagamento.', '--config <json|@file> JSON inline ou arquivo.', '--description <text> Descricao opcional.', '--idempotency-key <key> Chave para repetir a mesma criacao com seguranca.', '--json Imprime a resposta em JSON.'],
|
|
428
443
|
examples: ['zenifra create project', 'zenifra create project --name api-web --plan free --payment-mode hourly --config @examples/http-project.json'],
|
|
429
|
-
output: 'Campo Valor\n------- --------------------------------------\nProjeto 507f1f77bcf86cd799439012\nDominio https://api-web.
|
|
444
|
+
output: 'Campo Valor\n------- --------------------------------------\nProjeto 507f1f77bcf86cd799439012\nDominio https://api-web.clients.zenifra.com',
|
|
430
445
|
jsonOutput: '{"status":"success","data":{"id":"507f1f77bcf86cd799439012","name":"api-web"}}',
|
|
431
446
|
notes: ['Sem flags de criacao, a CLI abre um wizard guiado com docs, exemplos e indicacao de campos obrigatorios.'],
|
|
432
447
|
},
|
|
433
448
|
{
|
|
434
449
|
command: 'project',
|
|
435
|
-
usage: 'zenifra project\n zenifra project info --project <id> [--json]\n zenifra project url --project <id> [--json]\n zenifra project logs --project <id> [--instance <id>] [--json]\n zenifra project metrics --project <id> [--instance <id>] [--json]\n zenifra project metrics capabilities --project <id> [--json]\n zenifra project healthcheck get --project <id> [--json]\n zenifra project healthcheck set --project <id> --path /health [--json]\n zenifra project healthcheck disable --project <id> [--json]\n zenifra project healthcheck failures --project <id> [--page <n>] [--limit <n>] [--json]\n zenifra project network --project <id> [--view <summary|status-codes|routes|user-agents|request-events|source-ips>] [--json]\n zenifra project image set --project <id> --image <image> [--json]\n zenifra project exposure set --project <id> --exposure <public|private> [--json]\n zenifra project envs --project <id> [--json] [--show-values]\n zenifra project env add --project <id> --name <name> --value <value> [--json]\n zenifra project env update --project <id> --name <name> --value <value> [--json]\n zenifra project env remove --project <id> --name <name> [--json]\n zenifra project autoscaling --project <id> [--json]\n zenifra project autoscaling set --project <id> --min <n> --max <n> [--cpu <percent>] [--memory <percent>] [--json]\n zenifra project autoscaling disable --project <id> [--json]\n zenifra project autoscaling events --project <id> [--direction <scale_up|scale_down>] [--from <iso>] [--to <iso>] [--page <n>] [--limit <n>] [--json]\n zenifra project billing usage --project <id> [--from <iso>] [--to <iso>] [--page <n>] [--limit <n>] [--json]\n zenifra project instances --project <id> [--json]\n zenifra project instances set --project <id> --count <n> [--json]',
|
|
450
|
+
usage: 'zenifra project\n zenifra project info --project <id> [--json]\n zenifra project stop --project <id> [--json]\n zenifra project resume --project <id> [--json]\n zenifra project delete --project <id> --yes [--json]\n zenifra project url --project <id> [--json]\n zenifra project logs --project <id> [--instance <id>] [--json]\n zenifra project metrics --project <id> [--instance <id>] [--json]\n zenifra project metrics capabilities --project <id> [--json]\n zenifra project healthcheck get --project <id> [--json]\n zenifra project healthcheck set --project <id> --path /health [--json]\n zenifra project healthcheck disable --project <id> [--json]\n zenifra project healthcheck failures --project <id> [--page <n>] [--limit <n>] [--json]\n zenifra project network --project <id> [--view <summary|status-codes|routes|user-agents|request-events|source-ips>] [--json]\n zenifra project image set --project <id> --image <image> [--json]\n zenifra project exposure set --project <id> --exposure <public|private> [--json]\n zenifra project envs --project <id> [--json] [--show-values]\n zenifra project env add --project <id> --name <name> --value <value> [--json]\n zenifra project env update --project <id> --name <name> --value <value> [--json]\n zenifra project env remove --project <id> --name <name> [--json]\n zenifra project autoscaling --project <id> [--json]\n zenifra project autoscaling set --project <id> --min <n> --max <n> [--cpu <percent>] [--memory <percent>] [--json]\n zenifra project autoscaling disable --project <id> [--json]\n zenifra project autoscaling events --project <id> [--direction <scale_up|scale_down>] [--from <iso>] [--to <iso>] [--page <n>] [--limit <n>] [--json]\n zenifra project billing usage --project <id> [--from <iso>] [--to <iso>] [--page <n>] [--limit <n>] [--json]\n zenifra project instances --project <id> [--json]\n zenifra project instances set --project <id> --count <n> [--json]',
|
|
436
451
|
description: 'Agrupa comandos operacionais e de introspecao sobre um projeto especifico.',
|
|
437
452
|
examples: ['zenifra project', 'zenifra project info --project proj_1', 'zenifra project env add --project proj_1 --name NODE_ENV --value production'],
|
|
438
453
|
output: 'Zenifra CLI - project',
|
|
439
|
-
notes: ['Use "zenifra help project <subcomando>" para detalhes de info, url, logs, metrics, capabilities, network, image, exposure, autoscaling, billing, envs e instances.'],
|
|
454
|
+
notes: ['Use "zenifra help project <subcomando>" para detalhes de info, stop, resume, delete, url, logs, metrics, capabilities, network, image, exposure, autoscaling, billing, envs e instances.'],
|
|
440
455
|
},
|
|
441
456
|
{
|
|
442
457
|
command: 'project info',
|
|
@@ -444,8 +459,36 @@ const HELP_SPECS = [
|
|
|
444
459
|
description: 'Mostra dados principais de um projeto.',
|
|
445
460
|
flags: ['--project <id> ID do projeto.', '--json Imprime a resposta em JSON.'],
|
|
446
461
|
examples: ['zenifra project info --project 507f1f77bcf86cd799439012'],
|
|
447
|
-
output: 'Nome: api-web\nStatus: running\nURL: https://api-web.
|
|
448
|
-
jsonOutput: '{"name":"api-web","status":"running","domain":"api-web.
|
|
462
|
+
output: 'Nome: api-web\nStatus: running\nURL: https://api-web.clients.zenifra.com\nInstancias: 2',
|
|
463
|
+
jsonOutput: '{"name":"api-web","status":"running","domain":"api-web.clients.zenifra.com","instances":2}',
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
command: 'project stop',
|
|
467
|
+
usage: 'zenifra project stop --project <id> [--json]',
|
|
468
|
+
description: 'Para um projeto e mostra o estado confirmado em seguida.',
|
|
469
|
+
flags: ['--project <id> ID do projeto.', '--json Imprime o estado confirmado em JSON.'],
|
|
470
|
+
examples: ['zenifra project stop --project 507f1f77bcf86cd799439012'],
|
|
471
|
+
output: 'Nome: api-web\nStatus: stopped',
|
|
472
|
+
jsonOutput: '{"id":"507f1f77bcf86cd799439012","status":"stopped"}',
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
command: 'project resume',
|
|
476
|
+
usage: 'zenifra project resume --project <id> [--json]',
|
|
477
|
+
description: 'Retoma um projeto e mostra o estado confirmado em seguida.',
|
|
478
|
+
flags: ['--project <id> ID do projeto.', '--json Imprime o estado confirmado em JSON.'],
|
|
479
|
+
examples: ['zenifra project resume --project 507f1f77bcf86cd799439012'],
|
|
480
|
+
output: 'Nome: api-web\nStatus: running',
|
|
481
|
+
jsonOutput: '{"id":"507f1f77bcf86cd799439012","status":"running"}',
|
|
482
|
+
},
|
|
483
|
+
{
|
|
484
|
+
command: 'project delete',
|
|
485
|
+
usage: 'zenifra project delete --project <id> --yes [--json]',
|
|
486
|
+
description: 'Remove um projeto quando a confirmacao explicita e informada.',
|
|
487
|
+
flags: ['--project <id> ID do projeto.', '--yes Confirma a remocao.', '--json Imprime a resposta da API em JSON.'],
|
|
488
|
+
examples: ['zenifra project delete --project 507f1f77bcf86cd799439012 --yes'],
|
|
489
|
+
output: 'Projeto removido.',
|
|
490
|
+
jsonOutput: '{"status":"success","message":"project removed"}',
|
|
491
|
+
notes: ['Sem --yes, a CLI nao envia nenhuma solicitacao de remocao.'],
|
|
449
492
|
},
|
|
450
493
|
{
|
|
451
494
|
command: 'project url',
|
|
@@ -453,8 +496,8 @@ const HELP_SPECS = [
|
|
|
453
496
|
description: 'Imprime a URL publica principal do projeto.',
|
|
454
497
|
flags: ['--project <id> ID do projeto.', '--json Inclui dominio e custom domains em JSON.'],
|
|
455
498
|
examples: ['zenifra project url --project 507f1f77bcf86cd799439012'],
|
|
456
|
-
output: 'https://api-web.
|
|
457
|
-
jsonOutput: '{"project_id":"507f1f77bcf86cd799439012","url":"https://api-web.
|
|
499
|
+
output: 'https://api-web.clients.zenifra.com',
|
|
500
|
+
jsonOutput: '{"project_id":"507f1f77bcf86cd799439012","url":"https://api-web.clients.zenifra.com","domain":"api-web.clients.zenifra.com","custom_domains":[]}',
|
|
458
501
|
},
|
|
459
502
|
{
|
|
460
503
|
command: 'project logs',
|
|
@@ -1813,6 +1856,18 @@ function envsForOutput(envs, { showValues = false } = {}) {
|
|
|
1813
1856
|
}));
|
|
1814
1857
|
}
|
|
1815
1858
|
|
|
1859
|
+
const PROJECT_INFO_SECRET_FIELD = /(?:^|_)(?:api_?key|access_?token|refresh_?token|secret|password|credential|connection_string|private_?jwk)(?:$|_)/i;
|
|
1860
|
+
|
|
1861
|
+
function projectInfoForOutput(value, key = '') {
|
|
1862
|
+
if (key === 'envs' && Array.isArray(value)) return envsForOutput(value);
|
|
1863
|
+
if (Array.isArray(value)) return value.map((item) => projectInfoForOutput(item, key));
|
|
1864
|
+
if (!value || typeof value !== 'object') return PROJECT_INFO_SECRET_FIELD.test(key) ? maskEnvValue(value) : value;
|
|
1865
|
+
return Object.fromEntries(Object.entries(value).map(([entryKey, entryValue]) => [
|
|
1866
|
+
entryKey,
|
|
1867
|
+
PROJECT_INFO_SECRET_FIELD.test(entryKey) ? maskEnvValue(entryValue) : projectInfoForOutput(entryValue, entryKey),
|
|
1868
|
+
]));
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1816
1871
|
function formatPublicUrl(value) {
|
|
1817
1872
|
const raw = String(value || '').trim();
|
|
1818
1873
|
if (!raw) return '';
|
|
@@ -2252,6 +2307,38 @@ async function handleOrgs(session, flags) {
|
|
|
2252
2307
|
]);
|
|
2253
2308
|
}
|
|
2254
2309
|
|
|
2310
|
+
async function handleWhoami(session, flags) {
|
|
2311
|
+
if (!getProfileName(session)) {
|
|
2312
|
+
throw new CliError('Nenhum perfil ativo configurado. Crie um perfil com "zenifra profile add" ou autentique com "zenifra auth login".');
|
|
2313
|
+
}
|
|
2314
|
+
const profile = profileOutput(session, getStore(session).activeProfile);
|
|
2315
|
+
const effectiveCredential = resolveCredential(session);
|
|
2316
|
+
const usingApiKey = effectiveCredential?.type === 'api_key';
|
|
2317
|
+
const selectedOrganizationId = usingApiKey ? null : profile.selected_organization_id || null;
|
|
2318
|
+
let organizationName = null;
|
|
2319
|
+
|
|
2320
|
+
if (selectedOrganizationId && !hasApiKeyCredential(session) && session.accessToken) {
|
|
2321
|
+
const organizations = await getOrganizations(session, flags);
|
|
2322
|
+
organizationName = organizations.find((organization) => idOf(organization) === selectedOrganizationId)?.name
|
|
2323
|
+
|| organizations.find((organization) => idOf(organization) === selectedOrganizationId)?.organization?.name
|
|
2324
|
+
|| null;
|
|
2325
|
+
}
|
|
2326
|
+
|
|
2327
|
+
const output = {
|
|
2328
|
+
profile: profile.name,
|
|
2329
|
+
api_base_url: apiBaseUrl(session, flags),
|
|
2330
|
+
authentication_mode: usingApiKey ? 'api_key' : profile.auth_mode,
|
|
2331
|
+
selected_organization_id: selectedOrganizationId,
|
|
2332
|
+
organization_name: organizationName,
|
|
2333
|
+
};
|
|
2334
|
+
|
|
2335
|
+
if (flags.json) return printJson(output);
|
|
2336
|
+
process.stdout.write(`Perfil: ${output.profile}\n`);
|
|
2337
|
+
process.stdout.write(`API base: ${output.api_base_url}\n`);
|
|
2338
|
+
process.stdout.write(`Autenticacao: ${output.authentication_mode}\n`);
|
|
2339
|
+
process.stdout.write(`Organizacao: ${output.organization_name ? `${output.organization_name} (${selectedOrganizationId})` : selectedOrganizationId || 'nao selecionada'}\n`);
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2255
2342
|
async function handleOrgSet(session, flags) {
|
|
2256
2343
|
requireUserSession(session, 'Selecionar organizacao');
|
|
2257
2344
|
|
|
@@ -2679,6 +2766,22 @@ function printValkeyCatalog(catalog, profileFilter) {
|
|
|
2679
2766
|
});
|
|
2680
2767
|
}
|
|
2681
2768
|
|
|
2769
|
+
const HTTP_CAPABILITY_LABELS = {
|
|
2770
|
+
logs: 'logs',
|
|
2771
|
+
metrics: 'metricas',
|
|
2772
|
+
healthcheck: 'verificacao de saude',
|
|
2773
|
+
autoscaling: 'auto-scaling',
|
|
2774
|
+
custom_subdomain: 'subdominio personalizado',
|
|
2775
|
+
network_access: 'acesso de rede',
|
|
2776
|
+
};
|
|
2777
|
+
|
|
2778
|
+
function formatPlanCapabilities(capabilities) {
|
|
2779
|
+
const available = Object.entries(HTTP_CAPABILITY_LABELS)
|
|
2780
|
+
.filter(([key]) => capabilities?.[key] === true)
|
|
2781
|
+
.map(([, label]) => label);
|
|
2782
|
+
return available.join(', ') || '-';
|
|
2783
|
+
}
|
|
2784
|
+
|
|
2682
2785
|
function printPlansCatalogs(payload, type) {
|
|
2683
2786
|
if (type === 'all' || type === 'http') {
|
|
2684
2787
|
process.stdout.write('HTTP\n');
|
|
@@ -2688,6 +2791,7 @@ function printPlansCatalogs(payload, type) {
|
|
|
2688
2791
|
{ label: 'Mes', value: (plan) => formatBrlFromCents(plan.prices?.monthly) },
|
|
2689
2792
|
{ label: 'Ano', value: (plan) => formatBrlFromCents(plan.prices?.yearly) },
|
|
2690
2793
|
{ label: 'Recursos', value: (plan) => asArray(plan.features).join(', ') || '-' },
|
|
2794
|
+
{ label: 'Capacidades', value: (plan) => formatPlanCapabilities(plan.capabilities) },
|
|
2691
2795
|
]);
|
|
2692
2796
|
}
|
|
2693
2797
|
|
|
@@ -3493,6 +3597,7 @@ async function handleProjectCreate(session, flags) {
|
|
|
3493
3597
|
const plan = wizardPayload?.plan || flags.plan || await prompt('Plano');
|
|
3494
3598
|
const paymentMode = wizardPayload?.payment_mode || flags.paymentMode || await prompt('Modo de pagamento');
|
|
3495
3599
|
const config = wizardPayload?.config || await parseConfig(flags.config || await prompt('Config JSON ou @arquivo'));
|
|
3600
|
+
const idempotencyKey = flags.idempotencyKey === undefined ? undefined : validateIdempotencyKey(flags.idempotencyKey);
|
|
3496
3601
|
if (Array.isArray(config?.custom_domains)) {
|
|
3497
3602
|
config.custom_domains = normalizeCustomDomains(config.custom_domains, { primaryDomain: config.domain });
|
|
3498
3603
|
}
|
|
@@ -3500,14 +3605,11 @@ async function handleProjectCreate(session, flags) {
|
|
|
3500
3605
|
const valkeyCatalog = typeProject === 'valkey'
|
|
3501
3606
|
? requireValkeyCatalog(unwrapData(await request(session, flags, 'GET', '/managed-services/catalog')))
|
|
3502
3607
|
: undefined;
|
|
3503
|
-
if (flags.idempotencyKey !== undefined && !/^[A-Za-z0-9._-]{16,200}$/.test(String(flags.idempotencyKey))) {
|
|
3504
|
-
throw new CliError('--idempotency-key deve ter entre 16 e 200 caracteres: letras, numeros, ponto, underscore ou hifen.');
|
|
3505
|
-
}
|
|
3506
3608
|
const validated = validateCreateInput({ plan, paymentMode, config, valkeyCatalog });
|
|
3507
3609
|
|
|
3508
3610
|
const payload = await request(session, flags, 'POST', '/project', {
|
|
3509
3611
|
orgId,
|
|
3510
|
-
headers:
|
|
3612
|
+
headers: idempotencyKey === undefined ? {} : { 'Idempotency-Key': idempotencyKey },
|
|
3511
3613
|
body: {
|
|
3512
3614
|
name,
|
|
3513
3615
|
...(description ? { description: String(description) } : {}),
|
|
@@ -3559,10 +3661,37 @@ async function handleProjectInfo(session, flags) {
|
|
|
3559
3661
|
const orgId = await resolveOrgId(session, flags);
|
|
3560
3662
|
const project = await getProject(session, flags, projectId, orgId);
|
|
3561
3663
|
|
|
3562
|
-
if (flags.json) return printJson(project);
|
|
3664
|
+
if (flags.json) return printJson(projectInfoForOutput(project));
|
|
3563
3665
|
printProject({ ...project, id: projectId });
|
|
3564
3666
|
}
|
|
3565
3667
|
|
|
3668
|
+
async function handleProjectLifecycleMutation(session, flags, action) {
|
|
3669
|
+
const commandKey = `project ${action}`;
|
|
3670
|
+
const projectId = requireProjectId(flags, commandKey);
|
|
3671
|
+
if (!projectId) return;
|
|
3672
|
+
|
|
3673
|
+
const orgId = await resolveOrgId(session, flags);
|
|
3674
|
+
await request(session, flags, 'PATCH', `/project/${projectId}/${action}`, { orgId });
|
|
3675
|
+
const project = await getProject(session, flags, projectId, orgId);
|
|
3676
|
+
|
|
3677
|
+
if (flags.json) return printJson(projectInfoForOutput(project));
|
|
3678
|
+
printProject({ ...project, id: projectId });
|
|
3679
|
+
}
|
|
3680
|
+
|
|
3681
|
+
async function handleProjectDelete(session, flags) {
|
|
3682
|
+
const projectId = requireProjectId(flags, 'project delete');
|
|
3683
|
+
if (!projectId) return;
|
|
3684
|
+
if (flags.yes !== true) {
|
|
3685
|
+
throw new CliError('A remocao exige --yes. Nenhuma solicitacao foi enviada.');
|
|
3686
|
+
}
|
|
3687
|
+
|
|
3688
|
+
const orgId = await resolveOrgId(session, flags);
|
|
3689
|
+
const payload = await request(session, flags, 'DELETE', `/project/${projectId}`, { orgId });
|
|
3690
|
+
|
|
3691
|
+
if (flags.json) return printJson(payload);
|
|
3692
|
+
process.stdout.write('Projeto removido.\n');
|
|
3693
|
+
}
|
|
3694
|
+
|
|
3566
3695
|
async function handleProjectUrl(session, flags) {
|
|
3567
3696
|
const projectId = requireProjectId(flags, 'project url');
|
|
3568
3697
|
if (!projectId) return
|
|
@@ -4250,10 +4379,7 @@ async function handleValkeyCredentialStatus(session, flags) {
|
|
|
4250
4379
|
async function handleValkeyCredentialRotate(session, flags) {
|
|
4251
4380
|
const projectId = requireProjectId(flags, 'valkey credentials rotate');
|
|
4252
4381
|
if (!projectId) return;
|
|
4253
|
-
const idempotencyKey =
|
|
4254
|
-
if (!/^[A-Za-z0-9._-]{16,200}$/.test(idempotencyKey)) {
|
|
4255
|
-
throw new CliError('--idempotency-key deve ter entre 16 e 200 caracteres: letras, numeros, ponto, underscore ou hifen.');
|
|
4256
|
-
}
|
|
4382
|
+
const idempotencyKey = validateIdempotencyKey(flags.idempotencyKey === undefined ? randomUUID() : flags.idempotencyKey);
|
|
4257
4383
|
const orgId = await resolveOrgId(session, flags);
|
|
4258
4384
|
const acceptedPayload = await request(session, flags, 'PATCH', `/managed-services/${projectId}/credentials`, {
|
|
4259
4385
|
orgId,
|
|
@@ -4364,8 +4490,25 @@ function parseSecondsOption(value, flagName, { defaultValue, min }) {
|
|
|
4364
4490
|
return parsed;
|
|
4365
4491
|
}
|
|
4366
4492
|
|
|
4493
|
+
function validateIdempotencyKey(value) {
|
|
4494
|
+
const key = String(value);
|
|
4495
|
+
if (key.length < 16 || key.length > 200) {
|
|
4496
|
+
throw new CliError('--idempotency-key deve ter entre 16 e 200 caracteres.');
|
|
4497
|
+
}
|
|
4498
|
+
|
|
4499
|
+
const invalidIndex = Array.from(key).findIndex((character) => !/[A-Za-z0-9._-]/.test(character));
|
|
4500
|
+
if (invalidIndex !== -1) {
|
|
4501
|
+
throw new CliError(`--idempotency-key contem caractere invalido na posicao ${invalidIndex + 1}. Use apenas letras, numeros, ponto, underscore ou hifen.`);
|
|
4502
|
+
}
|
|
4503
|
+
|
|
4504
|
+
return key;
|
|
4505
|
+
}
|
|
4506
|
+
|
|
4367
4507
|
function printBuildLogs(logs) {
|
|
4368
4508
|
for (const log of asArray(logs)) {
|
|
4509
|
+
if (log?.source === 'summary') {
|
|
4510
|
+
process.stdout.write('Este build possui apenas um resumo; eventos detalhados nao estavam disponiveis.\n');
|
|
4511
|
+
}
|
|
4369
4512
|
process.stdout.write(`${buildLogLineOf(log)}\n`)
|
|
4370
4513
|
}
|
|
4371
4514
|
}
|
|
@@ -4509,6 +4652,7 @@ async function main() {
|
|
|
4509
4652
|
if (command === 'plans') return handlePlans(session, flags);
|
|
4510
4653
|
if (command === 'create' && subcommand === 'project') return handleProjectCreate(session, flags);
|
|
4511
4654
|
if (command === 'orgs') return handleOrgs(session, flags);
|
|
4655
|
+
if (command === 'whoami') return handleWhoami(session, flags);
|
|
4512
4656
|
if (command === 'org' && subcommand === 'set') return handleOrgSet(session, flags);
|
|
4513
4657
|
if (command === 'projects') return handleProjects(session, flags);
|
|
4514
4658
|
if (command === 'valkey' && subcommand === 'credentials' && positional.length === 2) {
|
|
@@ -4528,6 +4672,9 @@ async function main() {
|
|
|
4528
4672
|
if (command === 'project' && subcommand === 'healthcheck' && positional[2] === 'disable') return handleProjectHealthcheckDisable(session, flags);
|
|
4529
4673
|
if (command === 'project' && subcommand === 'healthcheck' && positional[2] === 'failures') return handleProjectHealthcheckFailures(session, flags);
|
|
4530
4674
|
if (command === 'project' && subcommand === 'info') return handleProjectInfo(session, flags);
|
|
4675
|
+
if (command === 'project' && subcommand === 'stop') return handleProjectLifecycleMutation(session, flags, 'stop');
|
|
4676
|
+
if (command === 'project' && subcommand === 'resume') return handleProjectLifecycleMutation(session, flags, 'resume');
|
|
4677
|
+
if (command === 'project' && subcommand === 'delete') return handleProjectDelete(session, flags);
|
|
4531
4678
|
if (command === 'project' && subcommand === 'url') return handleProjectUrl(session, flags);
|
|
4532
4679
|
if (command === 'project' && subcommand === 'logs') return handleProjectLogs(session, flags);
|
|
4533
4680
|
if (command === 'project' && subcommand === 'metrics' && positional[2] === 'capabilities') return handleProjectMetricsCapabilities(session, flags);
|