@zenifra/cli 0.3.2 → 0.3.4
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 +15 -2
- package/bin/zenifra.mjs +166 -17
- package/examples/clickhouse-project.json +19 -0
- 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
|
|
@@ -59,12 +60,16 @@ zenifra create project --name <name> --plan basic --payment-mode hourly --config
|
|
|
59
60
|
zenifra create project --name <name> --plan premium --payment-mode hourly --config @examples/http-autoscaling-project.json
|
|
60
61
|
zenifra create project --name <name> --plan db-basic --payment-mode monthly --config @examples/postgresql-project.json
|
|
61
62
|
zenifra create project --name <name> --plan db-basic --payment-mode monthly --config @examples/mariadb-project.json
|
|
63
|
+
zenifra create project --name <name> --plan analytics-starter --payment-mode hourly --config @examples/clickhouse-project.json
|
|
62
64
|
zenifra create project --name <name> --plan db-free --payment-mode hourly --config @examples/valkey-key-value-project.json
|
|
63
65
|
zenifra create project --name <name> --plan cache-free --payment-mode hourly --config @examples/valkey-cache-project.json
|
|
64
66
|
zenifra create project --name <name> --plan queue-free --payment-mode hourly --config @examples/valkey-queue-project.json
|
|
65
67
|
zenifra projects --type http --page 1 --limit 15
|
|
66
68
|
zenifra projects --type valkey --page 1 --limit 15
|
|
67
69
|
zenifra project info --project <project-id>
|
|
70
|
+
zenifra project stop --project <project-id>
|
|
71
|
+
zenifra project resume --project <project-id>
|
|
72
|
+
zenifra project delete --project <project-id> --yes
|
|
68
73
|
zenifra valkey status --project <project-id>
|
|
69
74
|
zenifra valkey connection --project <project-id>
|
|
70
75
|
zenifra valkey credentials rotate --project <project-id> --wait
|
|
@@ -148,6 +153,8 @@ Fluxos:
|
|
|
148
153
|
- `zenifra deploy`: dispara o build/deploy GitHub e retorna o `build_id`
|
|
149
154
|
- `zenifra deploy watch`: usa esse `build_id` para acompanhar o build em tempo real e imprimir os logs incrementais ate o fim
|
|
150
155
|
|
|
156
|
+
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.
|
|
157
|
+
|
|
151
158
|
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
159
|
|
|
153
160
|
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 +181,7 @@ Para executar a copia local deste repositorio, substitua `zenifra` por
|
|
|
174
181
|
|
|
175
182
|
- `--read-only` solicita somente leitura; as permissoes da sua organizacao continuam valendo.
|
|
176
183
|
- `--no-browser` mostra o endereco para abrir manualmente no navegador da mesma maquina.
|
|
184
|
+
- 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
185
|
- O retorno usa uma porta local temporaria em `127.0.0.1`; nao precisa cadastrar uma porta fixa.
|
|
178
186
|
- O login aguarda ate tres minutos. Use `Ctrl+C` para cancelar.
|
|
179
187
|
- Os tokens ficam no perfil local privado e sao renovados automaticamente antes de expirar.
|
|
@@ -213,6 +221,8 @@ zenifra plans --type storage --json
|
|
|
213
221
|
|
|
214
222
|
`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
223
|
|
|
224
|
+
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.
|
|
225
|
+
|
|
216
226
|
Valores de variaveis de ambiente sao mascarados por padrao, inclusive em `--json`.
|
|
217
227
|
Use `--show-values` apenas quando precisar inspecionar os valores completos.
|
|
218
228
|
|
|
@@ -280,6 +290,7 @@ Use os arquivos em `examples/` como base para `zenifra create project`:
|
|
|
280
290
|
- `examples/http-autoscaling-project.json`: projeto HTTP pago criado com auto-scaling
|
|
281
291
|
- `examples/postgresql-project.json`: projeto PostgreSQL
|
|
282
292
|
- `examples/mariadb-project.json`: projeto MariaDB
|
|
293
|
+
- `examples/clickhouse-project.json`: projeto ClickHouse com Native TLS e armazenamento persistente
|
|
283
294
|
- `examples/valkey-key-value-project.json`: projeto Valkey Key Value com armazenamento persistente
|
|
284
295
|
- `examples/valkey-cache-project.json`: projeto Valkey Cache sem armazenamento persistente
|
|
285
296
|
- `examples/valkey-queue-project.json`: projeto Valkey Queue com armazenamento persistente
|
|
@@ -297,6 +308,8 @@ O wizard atual cobre:
|
|
|
297
308
|
- projetos `mariadb`
|
|
298
309
|
- projetos `valkey` nos perfis `key_value`, `cache` e `queue`
|
|
299
310
|
|
|
311
|
+
Projetos `clickhouse` usam atualmente o fluxo nao interativo com `--config`, como em `examples/clickhouse-project.json`.
|
|
312
|
+
|
|
300
313
|
`zenifra create project` nao assume valores default para `--plan` e `--payment-mode`.
|
|
301
314
|
Configs HTTP nao interativas tambem devem informar `config.exposure`; use `public` para criar rota/dominio publico ou `private` para manter a aplicacao sem exposicao na internet.
|
|
302
315
|
Antes de escolher um plano com o usuario, compare os catalogos com `zenifra plans` para evitar suposicoes sobre custo.
|
|
@@ -304,9 +317,9 @@ Antes de escolher um plano com o usuario, compare os catalogos com `zenifra plan
|
|
|
304
317
|
Valores aceitos:
|
|
305
318
|
|
|
306
319
|
- `payment_mode`: `hourly`, `monthly`, `yearly`
|
|
307
|
-
- `type_project` no `config`: `http`, `postgresql`, `mariadb`, `valkey`
|
|
320
|
+
- `type_project` no `config`: `http`, `postgresql`, `mariadb`, `valkey`, `clickhouse`
|
|
308
321
|
- `exposure` no `config` HTTP: `public`, `private`
|
|
309
|
-
- `plan`: consulte `zenifra plans` para os planos atuais; Valkey usa `db-*` para Key Value, `cache-*` para Cache e `queue-*` para Queue
|
|
322
|
+
- `plan`: consulte `zenifra plans` para os planos atuais; ClickHouse usa `analytics-*`; Valkey usa `db-*` para Key Value, `cache-*` para Cache e `queue-*` para Queue
|
|
310
323
|
- `config.profile` em projetos Valkey: `key_value`, `cache` ou `queue`
|
|
311
324
|
- `config.version` em projetos Valkey: a versão retornada por `zenifra plans --type valkey`
|
|
312
325
|
- `config.storage` em projetos Valkey: obrigatório e persistente para Key Value/Queue; omitido para Cache
|
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',
|
|
@@ -95,6 +96,10 @@ const ALLOWED_PLAN_VALUES = new Set([
|
|
|
95
96
|
'db-basic',
|
|
96
97
|
'db-premium',
|
|
97
98
|
'db-enterprise',
|
|
99
|
+
'analytics-sandbox',
|
|
100
|
+
'analytics-starter',
|
|
101
|
+
'analytics-production',
|
|
102
|
+
'analytics-enterprise',
|
|
98
103
|
'cache-free',
|
|
99
104
|
'cache-starter',
|
|
100
105
|
'cache-basic',
|
|
@@ -107,7 +112,7 @@ const ALLOWED_PLAN_VALUES = new Set([
|
|
|
107
112
|
'queue-enterprise',
|
|
108
113
|
]);
|
|
109
114
|
const ALLOWED_PAYMENT_MODE_VALUES = new Set(['hourly', 'monthly', 'yearly']);
|
|
110
|
-
const ALLOWED_TYPE_PROJECT_VALUES = new Set(['http', 'postgresql', 'mariadb', 'valkey']);
|
|
115
|
+
const ALLOWED_TYPE_PROJECT_VALUES = new Set(['http', 'postgresql', 'mariadb', 'valkey', 'clickhouse']);
|
|
111
116
|
const ALLOWED_RUNTIME_VALUES = new Set(['nodejs', 'python']);
|
|
112
117
|
const ALLOWED_EXPOSURE_VALUES = new Set(['public', 'private']);
|
|
113
118
|
const GITHUB_RUNTIME_VERSIONS = {
|
|
@@ -175,6 +180,7 @@ Usage:
|
|
|
175
180
|
zenifra profile edit <name> [--description <text>] [--api-base <url>] [--json]
|
|
176
181
|
zenifra profile use <name> [--json]
|
|
177
182
|
zenifra profile remove <name> [--json]
|
|
183
|
+
zenifra whoami [--json]
|
|
178
184
|
zenifra orgs [--json]
|
|
179
185
|
zenifra org set [--org <id>]
|
|
180
186
|
zenifra plans [--type <all|http|database|storage|valkey>] [--json]
|
|
@@ -186,6 +192,9 @@ Usage:
|
|
|
186
192
|
zenifra valkey credentials rotate --project <id> [--idempotency-key <key>] [--wait] [--interval <seconds>] [--timeout <seconds>] [--json]
|
|
187
193
|
zenifra valkey credentials status --project <id> --operation <id> [--json]
|
|
188
194
|
zenifra project info --project <id> [--json]
|
|
195
|
+
zenifra project stop --project <id> [--json]
|
|
196
|
+
zenifra project resume --project <id> [--json]
|
|
197
|
+
zenifra project delete --project <id> --yes [--json]
|
|
189
198
|
zenifra project url --project <id> [--json]
|
|
190
199
|
zenifra project logs --project <id> [--instance <id>] [--json]
|
|
191
200
|
zenifra project metrics --project <id> [--instance <id>] [--json]
|
|
@@ -246,7 +255,7 @@ const HELP_SPECS = [
|
|
|
246
255
|
],
|
|
247
256
|
examples: ['zenifra auth login', 'zenifra auth login --profile staging --code 123456'],
|
|
248
257
|
output: 'Login realizado com sucesso.',
|
|
249
|
-
notes: ['Exige email, senha e, quando habilitado, codigo de desafio.'],
|
|
258
|
+
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
259
|
},
|
|
251
260
|
{
|
|
252
261
|
command: 'auth api-key',
|
|
@@ -345,6 +354,16 @@ const HELP_SPECS = [
|
|
|
345
354
|
output: 'ID Nome Role Status\n507f1f77bcf86cd799439011 Minha org owner active',
|
|
346
355
|
jsonOutput: '[{"_id":"507f1f77bcf86cd799439011","name":"Minha org","role":"owner","status":"active"}]',
|
|
347
356
|
},
|
|
357
|
+
{
|
|
358
|
+
command: 'whoami',
|
|
359
|
+
usage: 'zenifra whoami [--json]',
|
|
360
|
+
description: 'Mostra o perfil ativo e a organizacao selecionada para os proximos comandos.',
|
|
361
|
+
flags: ['--json Imprime a identificacao em JSON.'],
|
|
362
|
+
examples: ['zenifra whoami', 'zenifra whoami --json'],
|
|
363
|
+
output: 'Perfil: staging\nAPI base: https://api.zenifra.com/v1\nAutenticacao: access_token\nOrganizacao: Minha organizacao (507f1f77bcf86cd799439011)',
|
|
364
|
+
jsonOutput: '{"profile":"staging","api_base_url":"https://api.zenifra.com/v1","authentication_mode":"access_token","selected_organization_id":"507f1f77bcf86cd799439011","organization_name":"Minha organizacao"}',
|
|
365
|
+
notes: ['Nenhuma credencial e exibida. Quando nao houver organizacao selecionada, use "zenifra org set" ou informe --org no comando.'],
|
|
366
|
+
},
|
|
348
367
|
{
|
|
349
368
|
command: 'org set',
|
|
350
369
|
usage: 'zenifra org set [--org <id>]',
|
|
@@ -426,17 +445,17 @@ const HELP_SPECS = [
|
|
|
426
445
|
description: 'Cria um projeto via wizard interativo ou via payload de configuracao JSON.',
|
|
427
446
|
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
447
|
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.
|
|
448
|
+
output: 'Campo Valor\n------- --------------------------------------\nProjeto 507f1f77bcf86cd799439012\nDominio https://api-web.clients.zenifra.com',
|
|
430
449
|
jsonOutput: '{"status":"success","data":{"id":"507f1f77bcf86cd799439012","name":"api-web"}}',
|
|
431
450
|
notes: ['Sem flags de criacao, a CLI abre um wizard guiado com docs, exemplos e indicacao de campos obrigatorios.'],
|
|
432
451
|
},
|
|
433
452
|
{
|
|
434
453
|
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]',
|
|
454
|
+
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
455
|
description: 'Agrupa comandos operacionais e de introspecao sobre um projeto especifico.',
|
|
437
456
|
examples: ['zenifra project', 'zenifra project info --project proj_1', 'zenifra project env add --project proj_1 --name NODE_ENV --value production'],
|
|
438
457
|
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.'],
|
|
458
|
+
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
459
|
},
|
|
441
460
|
{
|
|
442
461
|
command: 'project info',
|
|
@@ -444,8 +463,36 @@ const HELP_SPECS = [
|
|
|
444
463
|
description: 'Mostra dados principais de um projeto.',
|
|
445
464
|
flags: ['--project <id> ID do projeto.', '--json Imprime a resposta em JSON.'],
|
|
446
465
|
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.
|
|
466
|
+
output: 'Nome: api-web\nStatus: running\nURL: https://api-web.clients.zenifra.com\nInstancias: 2',
|
|
467
|
+
jsonOutput: '{"name":"api-web","status":"running","domain":"api-web.clients.zenifra.com","instances":2}',
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
command: 'project stop',
|
|
471
|
+
usage: 'zenifra project stop --project <id> [--json]',
|
|
472
|
+
description: 'Para um projeto e mostra o estado confirmado em seguida.',
|
|
473
|
+
flags: ['--project <id> ID do projeto.', '--json Imprime o estado confirmado em JSON.'],
|
|
474
|
+
examples: ['zenifra project stop --project 507f1f77bcf86cd799439012'],
|
|
475
|
+
output: 'Nome: api-web\nStatus: stopped',
|
|
476
|
+
jsonOutput: '{"id":"507f1f77bcf86cd799439012","status":"stopped"}',
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
command: 'project resume',
|
|
480
|
+
usage: 'zenifra project resume --project <id> [--json]',
|
|
481
|
+
description: 'Retoma um projeto e mostra o estado confirmado em seguida.',
|
|
482
|
+
flags: ['--project <id> ID do projeto.', '--json Imprime o estado confirmado em JSON.'],
|
|
483
|
+
examples: ['zenifra project resume --project 507f1f77bcf86cd799439012'],
|
|
484
|
+
output: 'Nome: api-web\nStatus: running',
|
|
485
|
+
jsonOutput: '{"id":"507f1f77bcf86cd799439012","status":"running"}',
|
|
486
|
+
},
|
|
487
|
+
{
|
|
488
|
+
command: 'project delete',
|
|
489
|
+
usage: 'zenifra project delete --project <id> --yes [--json]',
|
|
490
|
+
description: 'Remove um projeto quando a confirmacao explicita e informada.',
|
|
491
|
+
flags: ['--project <id> ID do projeto.', '--yes Confirma a remocao.', '--json Imprime a resposta da API em JSON.'],
|
|
492
|
+
examples: ['zenifra project delete --project 507f1f77bcf86cd799439012 --yes'],
|
|
493
|
+
output: 'Projeto removido.',
|
|
494
|
+
jsonOutput: '{"status":"success","message":"project removed"}',
|
|
495
|
+
notes: ['Sem --yes, a CLI nao envia nenhuma solicitacao de remocao.'],
|
|
449
496
|
},
|
|
450
497
|
{
|
|
451
498
|
command: 'project url',
|
|
@@ -453,8 +500,8 @@ const HELP_SPECS = [
|
|
|
453
500
|
description: 'Imprime a URL publica principal do projeto.',
|
|
454
501
|
flags: ['--project <id> ID do projeto.', '--json Inclui dominio e custom domains em JSON.'],
|
|
455
502
|
examples: ['zenifra project url --project 507f1f77bcf86cd799439012'],
|
|
456
|
-
output: 'https://api-web.
|
|
457
|
-
jsonOutput: '{"project_id":"507f1f77bcf86cd799439012","url":"https://api-web.
|
|
503
|
+
output: 'https://api-web.clients.zenifra.com',
|
|
504
|
+
jsonOutput: '{"project_id":"507f1f77bcf86cd799439012","url":"https://api-web.clients.zenifra.com","domain":"api-web.clients.zenifra.com","custom_domains":[]}',
|
|
458
505
|
},
|
|
459
506
|
{
|
|
460
507
|
command: 'project logs',
|
|
@@ -2264,6 +2311,38 @@ async function handleOrgs(session, flags) {
|
|
|
2264
2311
|
]);
|
|
2265
2312
|
}
|
|
2266
2313
|
|
|
2314
|
+
async function handleWhoami(session, flags) {
|
|
2315
|
+
if (!getProfileName(session)) {
|
|
2316
|
+
throw new CliError('Nenhum perfil ativo configurado. Crie um perfil com "zenifra profile add" ou autentique com "zenifra auth login".');
|
|
2317
|
+
}
|
|
2318
|
+
const profile = profileOutput(session, getStore(session).activeProfile);
|
|
2319
|
+
const effectiveCredential = resolveCredential(session);
|
|
2320
|
+
const usingApiKey = effectiveCredential?.type === 'api_key';
|
|
2321
|
+
const selectedOrganizationId = usingApiKey ? null : profile.selected_organization_id || null;
|
|
2322
|
+
let organizationName = null;
|
|
2323
|
+
|
|
2324
|
+
if (selectedOrganizationId && !hasApiKeyCredential(session) && session.accessToken) {
|
|
2325
|
+
const organizations = await getOrganizations(session, flags);
|
|
2326
|
+
organizationName = organizations.find((organization) => idOf(organization) === selectedOrganizationId)?.name
|
|
2327
|
+
|| organizations.find((organization) => idOf(organization) === selectedOrganizationId)?.organization?.name
|
|
2328
|
+
|| null;
|
|
2329
|
+
}
|
|
2330
|
+
|
|
2331
|
+
const output = {
|
|
2332
|
+
profile: profile.name,
|
|
2333
|
+
api_base_url: apiBaseUrl(session, flags),
|
|
2334
|
+
authentication_mode: usingApiKey ? 'api_key' : profile.auth_mode,
|
|
2335
|
+
selected_organization_id: selectedOrganizationId,
|
|
2336
|
+
organization_name: organizationName,
|
|
2337
|
+
};
|
|
2338
|
+
|
|
2339
|
+
if (flags.json) return printJson(output);
|
|
2340
|
+
process.stdout.write(`Perfil: ${output.profile}\n`);
|
|
2341
|
+
process.stdout.write(`API base: ${output.api_base_url}\n`);
|
|
2342
|
+
process.stdout.write(`Autenticacao: ${output.authentication_mode}\n`);
|
|
2343
|
+
process.stdout.write(`Organizacao: ${output.organization_name ? `${output.organization_name} (${selectedOrganizationId})` : selectedOrganizationId || 'nao selecionada'}\n`);
|
|
2344
|
+
}
|
|
2345
|
+
|
|
2267
2346
|
async function handleOrgSet(session, flags) {
|
|
2268
2347
|
requireUserSession(session, 'Selecionar organizacao');
|
|
2269
2348
|
|
|
@@ -2347,6 +2426,14 @@ function normalizePlan(value) {
|
|
|
2347
2426
|
['dbpremium', 'db-premium'],
|
|
2348
2427
|
['db_enterprise', 'db-enterprise'],
|
|
2349
2428
|
['dbenterprise', 'db-enterprise'],
|
|
2429
|
+
['analytics_sandbox', 'analytics-sandbox'],
|
|
2430
|
+
['analyticssandbox', 'analytics-sandbox'],
|
|
2431
|
+
['analytics_starter', 'analytics-starter'],
|
|
2432
|
+
['analyticsstarter', 'analytics-starter'],
|
|
2433
|
+
['analytics_production', 'analytics-production'],
|
|
2434
|
+
['analyticsproduction', 'analytics-production'],
|
|
2435
|
+
['analytics_enterprise', 'analytics-enterprise'],
|
|
2436
|
+
['analyticsenterprise', 'analytics-enterprise'],
|
|
2350
2437
|
['cache_free', 'cache-free'],
|
|
2351
2438
|
['cachefree', 'cache-free'],
|
|
2352
2439
|
['cache_starter', 'cache-starter'],
|
|
@@ -2406,6 +2493,8 @@ function normalizeTypeProject(value) {
|
|
|
2406
2493
|
['valkey', 'valkey'],
|
|
2407
2494
|
['key value', 'valkey'],
|
|
2408
2495
|
['key-value', 'valkey'],
|
|
2496
|
+
['clickhouse', 'clickhouse'],
|
|
2497
|
+
['click house', 'clickhouse'],
|
|
2409
2498
|
]);
|
|
2410
2499
|
return aliases.get(raw) || raw;
|
|
2411
2500
|
}
|
|
@@ -2691,6 +2780,22 @@ function printValkeyCatalog(catalog, profileFilter) {
|
|
|
2691
2780
|
});
|
|
2692
2781
|
}
|
|
2693
2782
|
|
|
2783
|
+
const HTTP_CAPABILITY_LABELS = {
|
|
2784
|
+
logs: 'logs',
|
|
2785
|
+
metrics: 'metricas',
|
|
2786
|
+
healthcheck: 'verificacao de saude',
|
|
2787
|
+
autoscaling: 'auto-scaling',
|
|
2788
|
+
custom_subdomain: 'subdominio personalizado',
|
|
2789
|
+
network_access: 'acesso de rede',
|
|
2790
|
+
};
|
|
2791
|
+
|
|
2792
|
+
function formatPlanCapabilities(capabilities) {
|
|
2793
|
+
const available = Object.entries(HTTP_CAPABILITY_LABELS)
|
|
2794
|
+
.filter(([key]) => capabilities?.[key] === true)
|
|
2795
|
+
.map(([, label]) => label);
|
|
2796
|
+
return available.join(', ') || '-';
|
|
2797
|
+
}
|
|
2798
|
+
|
|
2694
2799
|
function printPlansCatalogs(payload, type) {
|
|
2695
2800
|
if (type === 'all' || type === 'http') {
|
|
2696
2801
|
process.stdout.write('HTTP\n');
|
|
@@ -2700,6 +2805,7 @@ function printPlansCatalogs(payload, type) {
|
|
|
2700
2805
|
{ label: 'Mes', value: (plan) => formatBrlFromCents(plan.prices?.monthly) },
|
|
2701
2806
|
{ label: 'Ano', value: (plan) => formatBrlFromCents(plan.prices?.yearly) },
|
|
2702
2807
|
{ label: 'Recursos', value: (plan) => asArray(plan.features).join(', ') || '-' },
|
|
2808
|
+
{ label: 'Capacidades', value: (plan) => formatPlanCapabilities(plan.capabilities) },
|
|
2703
2809
|
]);
|
|
2704
2810
|
}
|
|
2705
2811
|
|
|
@@ -3505,6 +3611,7 @@ async function handleProjectCreate(session, flags) {
|
|
|
3505
3611
|
const plan = wizardPayload?.plan || flags.plan || await prompt('Plano');
|
|
3506
3612
|
const paymentMode = wizardPayload?.payment_mode || flags.paymentMode || await prompt('Modo de pagamento');
|
|
3507
3613
|
const config = wizardPayload?.config || await parseConfig(flags.config || await prompt('Config JSON ou @arquivo'));
|
|
3614
|
+
const idempotencyKey = flags.idempotencyKey === undefined ? undefined : validateIdempotencyKey(flags.idempotencyKey);
|
|
3508
3615
|
if (Array.isArray(config?.custom_domains)) {
|
|
3509
3616
|
config.custom_domains = normalizeCustomDomains(config.custom_domains, { primaryDomain: config.domain });
|
|
3510
3617
|
}
|
|
@@ -3512,14 +3619,11 @@ async function handleProjectCreate(session, flags) {
|
|
|
3512
3619
|
const valkeyCatalog = typeProject === 'valkey'
|
|
3513
3620
|
? requireValkeyCatalog(unwrapData(await request(session, flags, 'GET', '/managed-services/catalog')))
|
|
3514
3621
|
: 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
3622
|
const validated = validateCreateInput({ plan, paymentMode, config, valkeyCatalog });
|
|
3519
3623
|
|
|
3520
3624
|
const payload = await request(session, flags, 'POST', '/project', {
|
|
3521
3625
|
orgId,
|
|
3522
|
-
headers:
|
|
3626
|
+
headers: idempotencyKey === undefined ? {} : { 'Idempotency-Key': idempotencyKey },
|
|
3523
3627
|
body: {
|
|
3524
3628
|
name,
|
|
3525
3629
|
...(description ? { description: String(description) } : {}),
|
|
@@ -3575,6 +3679,33 @@ async function handleProjectInfo(session, flags) {
|
|
|
3575
3679
|
printProject({ ...project, id: projectId });
|
|
3576
3680
|
}
|
|
3577
3681
|
|
|
3682
|
+
async function handleProjectLifecycleMutation(session, flags, action) {
|
|
3683
|
+
const commandKey = `project ${action}`;
|
|
3684
|
+
const projectId = requireProjectId(flags, commandKey);
|
|
3685
|
+
if (!projectId) return;
|
|
3686
|
+
|
|
3687
|
+
const orgId = await resolveOrgId(session, flags);
|
|
3688
|
+
await request(session, flags, 'PATCH', `/project/${projectId}/${action}`, { orgId });
|
|
3689
|
+
const project = await getProject(session, flags, projectId, orgId);
|
|
3690
|
+
|
|
3691
|
+
if (flags.json) return printJson(projectInfoForOutput(project));
|
|
3692
|
+
printProject({ ...project, id: projectId });
|
|
3693
|
+
}
|
|
3694
|
+
|
|
3695
|
+
async function handleProjectDelete(session, flags) {
|
|
3696
|
+
const projectId = requireProjectId(flags, 'project delete');
|
|
3697
|
+
if (!projectId) return;
|
|
3698
|
+
if (flags.yes !== true) {
|
|
3699
|
+
throw new CliError('A remocao exige --yes. Nenhuma solicitacao foi enviada.');
|
|
3700
|
+
}
|
|
3701
|
+
|
|
3702
|
+
const orgId = await resolveOrgId(session, flags);
|
|
3703
|
+
const payload = await request(session, flags, 'DELETE', `/project/${projectId}`, { orgId });
|
|
3704
|
+
|
|
3705
|
+
if (flags.json) return printJson(payload);
|
|
3706
|
+
process.stdout.write('Projeto removido.\n');
|
|
3707
|
+
}
|
|
3708
|
+
|
|
3578
3709
|
async function handleProjectUrl(session, flags) {
|
|
3579
3710
|
const projectId = requireProjectId(flags, 'project url');
|
|
3580
3711
|
if (!projectId) return
|
|
@@ -4262,10 +4393,7 @@ async function handleValkeyCredentialStatus(session, flags) {
|
|
|
4262
4393
|
async function handleValkeyCredentialRotate(session, flags) {
|
|
4263
4394
|
const projectId = requireProjectId(flags, 'valkey credentials rotate');
|
|
4264
4395
|
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
|
-
}
|
|
4396
|
+
const idempotencyKey = validateIdempotencyKey(flags.idempotencyKey === undefined ? randomUUID() : flags.idempotencyKey);
|
|
4269
4397
|
const orgId = await resolveOrgId(session, flags);
|
|
4270
4398
|
const acceptedPayload = await request(session, flags, 'PATCH', `/managed-services/${projectId}/credentials`, {
|
|
4271
4399
|
orgId,
|
|
@@ -4376,8 +4504,25 @@ function parseSecondsOption(value, flagName, { defaultValue, min }) {
|
|
|
4376
4504
|
return parsed;
|
|
4377
4505
|
}
|
|
4378
4506
|
|
|
4507
|
+
function validateIdempotencyKey(value) {
|
|
4508
|
+
const key = String(value);
|
|
4509
|
+
if (key.length < 16 || key.length > 200) {
|
|
4510
|
+
throw new CliError('--idempotency-key deve ter entre 16 e 200 caracteres.');
|
|
4511
|
+
}
|
|
4512
|
+
|
|
4513
|
+
const invalidIndex = Array.from(key).findIndex((character) => !/[A-Za-z0-9._-]/.test(character));
|
|
4514
|
+
if (invalidIndex !== -1) {
|
|
4515
|
+
throw new CliError(`--idempotency-key contem caractere invalido na posicao ${invalidIndex + 1}. Use apenas letras, numeros, ponto, underscore ou hifen.`);
|
|
4516
|
+
}
|
|
4517
|
+
|
|
4518
|
+
return key;
|
|
4519
|
+
}
|
|
4520
|
+
|
|
4379
4521
|
function printBuildLogs(logs) {
|
|
4380
4522
|
for (const log of asArray(logs)) {
|
|
4523
|
+
if (log?.source === 'summary') {
|
|
4524
|
+
process.stdout.write('Este build possui apenas um resumo; eventos detalhados nao estavam disponiveis.\n');
|
|
4525
|
+
}
|
|
4381
4526
|
process.stdout.write(`${buildLogLineOf(log)}\n`)
|
|
4382
4527
|
}
|
|
4383
4528
|
}
|
|
@@ -4521,6 +4666,7 @@ async function main() {
|
|
|
4521
4666
|
if (command === 'plans') return handlePlans(session, flags);
|
|
4522
4667
|
if (command === 'create' && subcommand === 'project') return handleProjectCreate(session, flags);
|
|
4523
4668
|
if (command === 'orgs') return handleOrgs(session, flags);
|
|
4669
|
+
if (command === 'whoami') return handleWhoami(session, flags);
|
|
4524
4670
|
if (command === 'org' && subcommand === 'set') return handleOrgSet(session, flags);
|
|
4525
4671
|
if (command === 'projects') return handleProjects(session, flags);
|
|
4526
4672
|
if (command === 'valkey' && subcommand === 'credentials' && positional.length === 2) {
|
|
@@ -4540,6 +4686,9 @@ async function main() {
|
|
|
4540
4686
|
if (command === 'project' && subcommand === 'healthcheck' && positional[2] === 'disable') return handleProjectHealthcheckDisable(session, flags);
|
|
4541
4687
|
if (command === 'project' && subcommand === 'healthcheck' && positional[2] === 'failures') return handleProjectHealthcheckFailures(session, flags);
|
|
4542
4688
|
if (command === 'project' && subcommand === 'info') return handleProjectInfo(session, flags);
|
|
4689
|
+
if (command === 'project' && subcommand === 'stop') return handleProjectLifecycleMutation(session, flags, 'stop');
|
|
4690
|
+
if (command === 'project' && subcommand === 'resume') return handleProjectLifecycleMutation(session, flags, 'resume');
|
|
4691
|
+
if (command === 'project' && subcommand === 'delete') return handleProjectDelete(session, flags);
|
|
4543
4692
|
if (command === 'project' && subcommand === 'url') return handleProjectUrl(session, flags);
|
|
4544
4693
|
if (command === 'project' && subcommand === 'logs') return handleProjectLogs(session, flags);
|
|
4545
4694
|
if (command === 'project' && subcommand === 'metrics' && positional[2] === 'capabilities') return handleProjectMetricsCapabilities(session, flags);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type_project": "clickhouse",
|
|
3
|
+
"version": "26.8.6.5",
|
|
4
|
+
"instances": 1,
|
|
5
|
+
"storage": {
|
|
6
|
+
"capacity": 20,
|
|
7
|
+
"persistent": true
|
|
8
|
+
},
|
|
9
|
+
"envs": [],
|
|
10
|
+
"network_access": {
|
|
11
|
+
"ingress_white_list": [
|
|
12
|
+
{
|
|
13
|
+
"cidr": "203.0.113.10/32",
|
|
14
|
+
"description": "Application egress IP"
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"ingress_black_list": []
|
|
18
|
+
}
|
|
19
|
+
}
|