@zenifra/cli 0.3.2 → 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 +151 -16
- 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',
|
|
@@ -2264,6 +2307,38 @@ async function handleOrgs(session, flags) {
|
|
|
2264
2307
|
]);
|
|
2265
2308
|
}
|
|
2266
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
|
+
|
|
2267
2342
|
async function handleOrgSet(session, flags) {
|
|
2268
2343
|
requireUserSession(session, 'Selecionar organizacao');
|
|
2269
2344
|
|
|
@@ -2691,6 +2766,22 @@ function printValkeyCatalog(catalog, profileFilter) {
|
|
|
2691
2766
|
});
|
|
2692
2767
|
}
|
|
2693
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
|
+
|
|
2694
2785
|
function printPlansCatalogs(payload, type) {
|
|
2695
2786
|
if (type === 'all' || type === 'http') {
|
|
2696
2787
|
process.stdout.write('HTTP\n');
|
|
@@ -2700,6 +2791,7 @@ function printPlansCatalogs(payload, type) {
|
|
|
2700
2791
|
{ label: 'Mes', value: (plan) => formatBrlFromCents(plan.prices?.monthly) },
|
|
2701
2792
|
{ label: 'Ano', value: (plan) => formatBrlFromCents(plan.prices?.yearly) },
|
|
2702
2793
|
{ label: 'Recursos', value: (plan) => asArray(plan.features).join(', ') || '-' },
|
|
2794
|
+
{ label: 'Capacidades', value: (plan) => formatPlanCapabilities(plan.capabilities) },
|
|
2703
2795
|
]);
|
|
2704
2796
|
}
|
|
2705
2797
|
|
|
@@ -3505,6 +3597,7 @@ async function handleProjectCreate(session, flags) {
|
|
|
3505
3597
|
const plan = wizardPayload?.plan || flags.plan || await prompt('Plano');
|
|
3506
3598
|
const paymentMode = wizardPayload?.payment_mode || flags.paymentMode || await prompt('Modo de pagamento');
|
|
3507
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);
|
|
3508
3601
|
if (Array.isArray(config?.custom_domains)) {
|
|
3509
3602
|
config.custom_domains = normalizeCustomDomains(config.custom_domains, { primaryDomain: config.domain });
|
|
3510
3603
|
}
|
|
@@ -3512,14 +3605,11 @@ async function handleProjectCreate(session, flags) {
|
|
|
3512
3605
|
const valkeyCatalog = typeProject === 'valkey'
|
|
3513
3606
|
? requireValkeyCatalog(unwrapData(await request(session, flags, 'GET', '/managed-services/catalog')))
|
|
3514
3607
|
: undefined;
|
|
3515
|
-
if (flags.idempotencyKey !== undefined && !/^[A-Za-z0-9._-]{16,200}$/.test(String(flags.idempotencyKey))) {
|
|
3516
|
-
throw new CliError('--idempotency-key deve ter entre 16 e 200 caracteres: letras, numeros, ponto, underscore ou hifen.');
|
|
3517
|
-
}
|
|
3518
3608
|
const validated = validateCreateInput({ plan, paymentMode, config, valkeyCatalog });
|
|
3519
3609
|
|
|
3520
3610
|
const payload = await request(session, flags, 'POST', '/project', {
|
|
3521
3611
|
orgId,
|
|
3522
|
-
headers:
|
|
3612
|
+
headers: idempotencyKey === undefined ? {} : { 'Idempotency-Key': idempotencyKey },
|
|
3523
3613
|
body: {
|
|
3524
3614
|
name,
|
|
3525
3615
|
...(description ? { description: String(description) } : {}),
|
|
@@ -3575,6 +3665,33 @@ async function handleProjectInfo(session, flags) {
|
|
|
3575
3665
|
printProject({ ...project, id: projectId });
|
|
3576
3666
|
}
|
|
3577
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
|
+
|
|
3578
3695
|
async function handleProjectUrl(session, flags) {
|
|
3579
3696
|
const projectId = requireProjectId(flags, 'project url');
|
|
3580
3697
|
if (!projectId) return
|
|
@@ -4262,10 +4379,7 @@ async function handleValkeyCredentialStatus(session, flags) {
|
|
|
4262
4379
|
async function handleValkeyCredentialRotate(session, flags) {
|
|
4263
4380
|
const projectId = requireProjectId(flags, 'valkey credentials rotate');
|
|
4264
4381
|
if (!projectId) return;
|
|
4265
|
-
const idempotencyKey =
|
|
4266
|
-
if (!/^[A-Za-z0-9._-]{16,200}$/.test(idempotencyKey)) {
|
|
4267
|
-
throw new CliError('--idempotency-key deve ter entre 16 e 200 caracteres: letras, numeros, ponto, underscore ou hifen.');
|
|
4268
|
-
}
|
|
4382
|
+
const idempotencyKey = validateIdempotencyKey(flags.idempotencyKey === undefined ? randomUUID() : flags.idempotencyKey);
|
|
4269
4383
|
const orgId = await resolveOrgId(session, flags);
|
|
4270
4384
|
const acceptedPayload = await request(session, flags, 'PATCH', `/managed-services/${projectId}/credentials`, {
|
|
4271
4385
|
orgId,
|
|
@@ -4376,8 +4490,25 @@ function parseSecondsOption(value, flagName, { defaultValue, min }) {
|
|
|
4376
4490
|
return parsed;
|
|
4377
4491
|
}
|
|
4378
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
|
+
|
|
4379
4507
|
function printBuildLogs(logs) {
|
|
4380
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
|
+
}
|
|
4381
4512
|
process.stdout.write(`${buildLogLineOf(log)}\n`)
|
|
4382
4513
|
}
|
|
4383
4514
|
}
|
|
@@ -4521,6 +4652,7 @@ async function main() {
|
|
|
4521
4652
|
if (command === 'plans') return handlePlans(session, flags);
|
|
4522
4653
|
if (command === 'create' && subcommand === 'project') return handleProjectCreate(session, flags);
|
|
4523
4654
|
if (command === 'orgs') return handleOrgs(session, flags);
|
|
4655
|
+
if (command === 'whoami') return handleWhoami(session, flags);
|
|
4524
4656
|
if (command === 'org' && subcommand === 'set') return handleOrgSet(session, flags);
|
|
4525
4657
|
if (command === 'projects') return handleProjects(session, flags);
|
|
4526
4658
|
if (command === 'valkey' && subcommand === 'credentials' && positional.length === 2) {
|
|
@@ -4540,6 +4672,9 @@ async function main() {
|
|
|
4540
4672
|
if (command === 'project' && subcommand === 'healthcheck' && positional[2] === 'disable') return handleProjectHealthcheckDisable(session, flags);
|
|
4541
4673
|
if (command === 'project' && subcommand === 'healthcheck' && positional[2] === 'failures') return handleProjectHealthcheckFailures(session, flags);
|
|
4542
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);
|
|
4543
4678
|
if (command === 'project' && subcommand === 'url') return handleProjectUrl(session, flags);
|
|
4544
4679
|
if (command === 'project' && subcommand === 'logs') return handleProjectLogs(session, flags);
|
|
4545
4680
|
if (command === 'project' && subcommand === 'metrics' && positional[2] === 'capabilities') return handleProjectMetricsCapabilities(session, flags);
|