@zenifra/cli 0.2.1 → 0.2.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 +27 -4
- package/bin/zenifra.mjs +226 -56
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -56,7 +56,7 @@ zenifra create project --name <name> --plan free --payment-mode hourly --config
|
|
|
56
56
|
zenifra create project --name <name> --plan basic --payment-mode hourly --config @examples/http-github-project.json
|
|
57
57
|
zenifra create project --name <name> --plan db-basic --payment-mode monthly --config @examples/postgresql-project.json
|
|
58
58
|
zenifra create project --name <name> --plan db-basic --payment-mode monthly --config @examples/mariadb-project.json
|
|
59
|
-
zenifra projects --type http
|
|
59
|
+
zenifra projects --type http --page 1 --limit 15
|
|
60
60
|
zenifra project info --project <project-id>
|
|
61
61
|
zenifra project url --project <project-id>
|
|
62
62
|
zenifra project logs --project <project-id> --instance <instance-id>
|
|
@@ -70,11 +70,34 @@ zenifra project env remove --project <project-id> --name NODE_ENV
|
|
|
70
70
|
zenifra project instances --project <project-id>
|
|
71
71
|
zenifra project instances set --project <project-id> --count 3
|
|
72
72
|
zenifra builds --project <project-id>
|
|
73
|
+
zenifra builds logs --project <project-id> --build <build-id>
|
|
74
|
+
zenifra builds logs --project <project-id> --build <build-id> --follow
|
|
73
75
|
zenifra deployments --project <project-id>
|
|
74
76
|
zenifra deploy --project <project-id> --branch main
|
|
75
77
|
zenifra deploy watch --project <project-id> --build <build-id>
|
|
76
78
|
```
|
|
77
79
|
|
|
80
|
+
## Builds GitHub
|
|
81
|
+
|
|
82
|
+
Use `zenifra builds` para listar o historico de builds e `zenifra builds logs` para ler os logs do pipeline GitHub de um build especifico.
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
zenifra builds --project <project-id>
|
|
86
|
+
zenifra builds logs --project <project-id> --build <build-id>
|
|
87
|
+
zenifra builds logs --project <project-id> --build <build-id> --follow
|
|
88
|
+
zenifra deploy --project <project-id> --branch main
|
|
89
|
+
zenifra deploy watch --project <project-id> --build <build-id>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Fluxos:
|
|
93
|
+
|
|
94
|
+
- `zenifra project logs`: logs da aplicacao em execucao
|
|
95
|
+
- `zenifra builds logs`: logs do build GitHub
|
|
96
|
+
- `zenifra deploy`: dispara o build/deploy GitHub e retorna o `build_id`
|
|
97
|
+
- `zenifra deploy watch`: usa esse `build_id` para acompanhar o build em tempo real e imprimir os logs incrementais ate o fim
|
|
98
|
+
|
|
99
|
+
Se voce rodar apenas `zenifra deploy`, a CLI mostra a ajuda especifica do comando com uso, flags, exemplos e exemplo de retorno.
|
|
100
|
+
|
|
78
101
|
## Configuracao
|
|
79
102
|
|
|
80
103
|
- API padrao: `https://api.zenifra.com/v1`
|
|
@@ -83,7 +106,7 @@ zenifra deploy watch --project <project-id> --build <build-id>
|
|
|
83
106
|
- Store local de perfis: `~/.config/zenifra-cli/profiles.json`
|
|
84
107
|
- Override do diretorio local: `ZENIFRA_CONFIG_DIR=/path/custom`
|
|
85
108
|
|
|
86
|
-
Todos os comandos de listagem aceitam `--json`.
|
|
109
|
+
Todos os comandos de listagem aceitam `--json`. `zenifra projects` e paginado por padrao com 15 itens por pagina; use `--page <n>` e `--limit <n>` para navegar.
|
|
87
110
|
|
|
88
111
|
Para comparar custo antes de criar um projeto, use:
|
|
89
112
|
|
|
@@ -137,7 +160,7 @@ Crie uma API key global no painel da organizacao e use-a em jobs, pipelines e sc
|
|
|
137
160
|
|
|
138
161
|
```bash
|
|
139
162
|
export ZENIFRA_API_KEY=znf_sua_chave
|
|
140
|
-
zenifra projects --type http
|
|
163
|
+
zenifra projects --type http --page 1 --limit 15
|
|
141
164
|
zenifra deploy --project <project-id> --branch main
|
|
142
165
|
```
|
|
143
166
|
|
|
@@ -147,7 +170,7 @@ Tambem e possivel salvar a chave localmente no perfil ativo:
|
|
|
147
170
|
zenifra auth api-key --key znf_sua_chave
|
|
148
171
|
```
|
|
149
172
|
|
|
150
|
-
API keys globais ja sao vinculadas a uma organizacao, entao comandos como `projects`, `deploy`, `builds` e `deployments` nao precisam de `org set`. Comandos pessoais como `orgs` e `org set` continuam exigindo `zenifra auth login` em um perfil com token de usuario.
|
|
173
|
+
API keys globais ja sao vinculadas a uma organizacao, entao comandos como `projects`, `deploy`, `builds`, `builds logs` e `deployments` nao precisam de `org set`. Comandos pessoais como `orgs` e `org set` continuam exigindo `zenifra auth login` em um perfil com token de usuario.
|
|
151
174
|
|
|
152
175
|
Para reduzir impacto de vazamento, configure IPs permitidos na criacao da API key sempre que a automacao tiver origem fixa.
|
|
153
176
|
|
package/bin/zenifra.mjs
CHANGED
|
@@ -21,7 +21,6 @@ const SESSION_FILE = join(SESSION_DIR, 'session.json');
|
|
|
21
21
|
const PROFILES_FILE = join(SESSION_DIR, 'profiles.json');
|
|
22
22
|
const PROFILE_STORE_VERSION = 1;
|
|
23
23
|
const DEFAULT_PROFILE_NAME = 'default';
|
|
24
|
-
const FINAL_BUILD_STATUSES = new Set(['success', 'failed']);
|
|
25
24
|
const ALLOWED_PLAN_VALUES = new Set([
|
|
26
25
|
'free',
|
|
27
26
|
'static',
|
|
@@ -108,7 +107,7 @@ Usage:
|
|
|
108
107
|
zenifra plans [--type <all|http|database|storage>] [--json]
|
|
109
108
|
zenifra create project
|
|
110
109
|
zenifra create project --name <name> --plan <plan> --payment-mode <mode> --config <json|@file> [--description <text>] [--org <id>] [--json]
|
|
111
|
-
zenifra projects [--json] [--org <id>] [--type <http|postgresql|mariadb>]
|
|
110
|
+
zenifra projects [--json] [--org <id>] [--type <http|postgresql|mariadb>] [--page <n>] [--limit <n>]
|
|
112
111
|
zenifra project info --project <id> [--json]
|
|
113
112
|
zenifra project url --project <id> [--json]
|
|
114
113
|
zenifra project logs --project <id> [--instance <id>] [--json]
|
|
@@ -122,6 +121,7 @@ Usage:
|
|
|
122
121
|
zenifra project instances --project <id> [--json]
|
|
123
122
|
zenifra project instances set --project <id> --count <n> [--json]
|
|
124
123
|
zenifra builds --project <id> [--page <n>] [--limit <n>] [--branch <name>] [--status <status>] [--json]
|
|
124
|
+
zenifra builds logs --project <id> --build <id> [--cursor <n>] [--limit <n>] [--follow] [--interval <seconds>] [--timeout <seconds>] [--json]
|
|
125
125
|
zenifra deployments --project <id> [--page <n>] [--limit <n>] [--branch <name>] [--status <status>] [--json]
|
|
126
126
|
zenifra deploy --project <id> [--branch <name>] [--commit-sha <sha>] [--json]
|
|
127
127
|
zenifra deploy watch --project <id> --build <id> [--interval <seconds>] [--timeout <seconds>] [--json]
|
|
@@ -272,12 +272,12 @@ const HELP_SPECS = [
|
|
|
272
272
|
},
|
|
273
273
|
{
|
|
274
274
|
command: 'projects',
|
|
275
|
-
usage: 'zenifra projects [--json] [--org <id>] [--type <http|postgresql|mariadb>]',
|
|
275
|
+
usage: 'zenifra projects [--json] [--org <id>] [--type <http|postgresql|mariadb>] [--page <n>] [--limit <n>]',
|
|
276
276
|
description: 'Lista projetos da organizacao ativa ou da API key.',
|
|
277
|
-
flags: ['--json
|
|
278
|
-
examples: ['zenifra projects --type http', 'zenifra projects --json'],
|
|
277
|
+
flags: ['--json Imprime a resposta em JSON.', '--org <id> Usa uma organizacao especifica com sessao de usuario.', '--type <type> Filtra por tipo de projeto.', '--page <n> Pagina. Padrao: 1.', '--limit <n> Itens por pagina. Padrao: 15.'],
|
|
278
|
+
examples: ['zenifra projects --type http --page 1 --limit 15', 'zenifra projects --json'],
|
|
279
279
|
output: 'ID Nome Status Plano Tipo\n507f1f77bcf86cd799439012 api-web running free http',
|
|
280
|
-
jsonOutput: '[{"id":"507f1f77bcf86cd799439012","name":"api-web","status":"running","type_project":"http"}]',
|
|
280
|
+
jsonOutput: '{"projects":[{"id":"507f1f77bcf86cd799439012","name":"api-web","status":"running","type_project":"http"}],"pagination":{"page":1,"limit":15,"total":1,"pages":1}}',
|
|
281
281
|
},
|
|
282
282
|
{
|
|
283
283
|
command: 'create project',
|
|
@@ -419,6 +419,24 @@ const HELP_SPECS = [
|
|
|
419
419
|
output: 'Build Status Branch Commit\nbuild_123 success main abc123',
|
|
420
420
|
jsonOutput: '[{"id":"build_123","status":"success","branch":"main","commit_sha":"abc123"}]',
|
|
421
421
|
},
|
|
422
|
+
{
|
|
423
|
+
command: 'builds logs',
|
|
424
|
+
usage: 'zenifra builds logs --project <id> --build <id> [--cursor <n>] [--limit <n>] [--follow] [--interval <seconds>] [--timeout <seconds>] [--json]',
|
|
425
|
+
description: 'Lê os logs de um build GitHub existente e opcionalmente acompanha novos chunks em tempo real.',
|
|
426
|
+
flags: [
|
|
427
|
+
'--project <id> ID do projeto.',
|
|
428
|
+
'--build <id> ID do build.',
|
|
429
|
+
'--cursor <n> Cursor inicial. Padrao: 0.',
|
|
430
|
+
'--limit <n> Quantidade de logs por request. Padrao: 200.',
|
|
431
|
+
'--follow Continua em polling ate o build finalizar.',
|
|
432
|
+
'--interval <seconds> Intervalo de polling com --follow. Padrao: 5.',
|
|
433
|
+
'--timeout <seconds> Timeout total com --follow. Padrao: 900.',
|
|
434
|
+
'--json Imprime a resposta em JSON.',
|
|
435
|
+
],
|
|
436
|
+
examples: ['zenifra builds logs --project 507f1f77bcf86cd799439012 --build build_123', 'zenifra builds logs --project 507f1f77bcf86cd799439012 --build build_123 --follow'],
|
|
437
|
+
output: '[2026-05-27T12:00:00.000Z] install: npm ci\n[2026-05-27T12:00:05.000Z] build: npm run build',
|
|
438
|
+
jsonOutput: '{"logs":[{"sequence":1,"timestamp":"2026-05-27T12:00:00.000Z","level":"info","step":"install","message":"npm ci","final":false}],"next_cursor":1,"status":"running","finished":false,"truncated":false}',
|
|
439
|
+
},
|
|
422
440
|
{
|
|
423
441
|
command: 'deployments',
|
|
424
442
|
usage: 'zenifra deployments --project <id> [--page <n>] [--limit <n>] [--branch <name>] [--status <status>] [--json]',
|
|
@@ -431,20 +449,35 @@ const HELP_SPECS = [
|
|
|
431
449
|
{
|
|
432
450
|
command: 'deploy',
|
|
433
451
|
usage: 'zenifra deploy --project <id> [--branch <name>] [--commit-sha <sha>] [--json]',
|
|
434
|
-
description: 'Dispara um deploy GitHub para o projeto.',
|
|
452
|
+
description: 'Dispara um build/deploy GitHub para o projeto e retorna o build_id para acompanhamento posterior.',
|
|
435
453
|
flags: ['--project <id> ID do projeto.', '--branch <name> Branch a publicar.', '--commit-sha <sha> Commit especifico.', '--json Imprime a resposta em JSON.'],
|
|
436
|
-
examples: [
|
|
454
|
+
examples: [
|
|
455
|
+
'zenifra deploy --project 507f1f77bcf86cd799439012 --branch main',
|
|
456
|
+
'zenifra deploy watch --project 507f1f77bcf86cd799439012 --build build_123',
|
|
457
|
+
],
|
|
437
458
|
output: 'Deploy iniciado: build_123',
|
|
438
459
|
jsonOutput: '{"status":"success","data":{"build_id":"build_123"}}',
|
|
460
|
+
notes: [
|
|
461
|
+
'Use o build_id retornado para acompanhar a execucao com "zenifra deploy watch --project <id> --build <build_id>".',
|
|
462
|
+
'O subcomando "deploy watch" acompanha status e logs incrementais do build ate o estado terminal.',
|
|
463
|
+
],
|
|
439
464
|
},
|
|
440
465
|
{
|
|
441
466
|
command: 'deploy watch',
|
|
442
467
|
usage: 'zenifra deploy watch --project <id> --build <id> [--interval <seconds>] [--timeout <seconds>] [--json]',
|
|
443
|
-
description: 'Acompanha um build
|
|
468
|
+
description: 'Acompanha um build em tempo real, fazendo polling do status e imprimindo os logs incrementais do build ate o estado terminal.',
|
|
444
469
|
flags: ['--project <id> ID do projeto.', '--build <id> ID do build.', '--interval <seconds> Intervalo de polling. Padrao: 5.', '--timeout <seconds> Timeout total. Padrao: 900.', '--json Imprime cada resposta em JSON.'],
|
|
445
|
-
examples: [
|
|
446
|
-
|
|
447
|
-
|
|
470
|
+
examples: [
|
|
471
|
+
'zenifra deploy watch --project 507f1f77bcf86cd799439012 --build build_123',
|
|
472
|
+
'zenifra deploy watch --project 507f1f77bcf86cd799439012 --build build_123 --interval 2',
|
|
473
|
+
'zenifra deploy watch --project 507f1f77bcf86cd799439012 --build build_123 --json',
|
|
474
|
+
],
|
|
475
|
+
output: '[2026-05-27T12:00:00.000Z] install: npm ci\n[2026-05-27T12:00:05.000Z] build: npm run build\n[2026-05-27T12:00:09.000Z] publish: Build concluida e publicada',
|
|
476
|
+
jsonOutput: '{"sequence":1,"timestamp":"2026-05-27T12:00:00.000Z","level":"info","step":"install","message":"npm ci","final":false}',
|
|
477
|
+
notes: [
|
|
478
|
+
'Use este subcomando logo apos "zenifra deploy" para acompanhar o build pelo build_id retornado.',
|
|
479
|
+
'No modo --json, o comando imprime eventos de log em streaming, um JSON por linha.',
|
|
480
|
+
],
|
|
448
481
|
},
|
|
449
482
|
];
|
|
450
483
|
|
|
@@ -495,6 +528,12 @@ function commandHelp(positionals) {
|
|
|
495
528
|
return formatHelp(spec);
|
|
496
529
|
}
|
|
497
530
|
|
|
531
|
+
function printCommandHelpAndFail(commandKey) {
|
|
532
|
+
const positionals = Array.isArray(commandKey) ? commandKey : String(commandKey).split(' ')
|
|
533
|
+
process.stdout.write(commandHelp(positionals))
|
|
534
|
+
process.exitCode = 1
|
|
535
|
+
}
|
|
536
|
+
|
|
498
537
|
function isRemovedProjectsCreate(positionals) {
|
|
499
538
|
return positionals[0] === 'projects' && positionals[1] === 'create';
|
|
500
539
|
}
|
|
@@ -1262,8 +1301,18 @@ function buildIdOf(build) {
|
|
|
1262
1301
|
return build?.id || build?._id || build?.build_id || '';
|
|
1263
1302
|
}
|
|
1264
1303
|
|
|
1265
|
-
function
|
|
1266
|
-
|
|
1304
|
+
function buildLogLineOf(log) {
|
|
1305
|
+
const timestamp = log?.timestamp ? new Date(log.timestamp).toISOString() : new Date().toISOString()
|
|
1306
|
+
const step = log?.step || 'build'
|
|
1307
|
+
const message = log?.message || ''
|
|
1308
|
+
return `[${timestamp}] ${step}: ${message}`
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
function requireProjectId(flags, commandKey) {
|
|
1312
|
+
if (!flags.project) {
|
|
1313
|
+
printCommandHelpAndFail(commandKey)
|
|
1314
|
+
return null
|
|
1315
|
+
}
|
|
1267
1316
|
return String(flags.project);
|
|
1268
1317
|
}
|
|
1269
1318
|
|
|
@@ -1528,7 +1577,10 @@ async function handleProfileShow(session, flags, positional = []) {
|
|
|
1528
1577
|
|
|
1529
1578
|
async function handleProfileUse(session, flags, positional = []) {
|
|
1530
1579
|
const targetName = positional[2] || flags.name;
|
|
1531
|
-
if (!targetName)
|
|
1580
|
+
if (!targetName) {
|
|
1581
|
+
printCommandHelpAndFail('profile use')
|
|
1582
|
+
return
|
|
1583
|
+
}
|
|
1532
1584
|
activateProfileSession(session, targetName);
|
|
1533
1585
|
await writeProfileStore(getStore(session));
|
|
1534
1586
|
if (flags.json) return printJson({ active_profile: getProfileName(session) });
|
|
@@ -1578,7 +1630,10 @@ async function handleProfileAdd(session, flags) {
|
|
|
1578
1630
|
|
|
1579
1631
|
async function handleProfileEdit(session, flags, positional = []) {
|
|
1580
1632
|
const targetName = positional[2] || flags.name;
|
|
1581
|
-
if (!targetName)
|
|
1633
|
+
if (!targetName) {
|
|
1634
|
+
printCommandHelpAndFail('profile edit')
|
|
1635
|
+
return
|
|
1636
|
+
}
|
|
1582
1637
|
const target = requireExistingProfile(session, targetName);
|
|
1583
1638
|
target.description = String(flags.description ?? await prompt('Descricao do perfil', target.description || ''));
|
|
1584
1639
|
target.apiBaseUrl = normalizeApiBaseUrl(flags.apiBase || await prompt('API base', target.apiBaseUrl || DEFAULT_API_BASE_URL));
|
|
@@ -1594,7 +1649,10 @@ async function handleProfileEdit(session, flags, positional = []) {
|
|
|
1594
1649
|
|
|
1595
1650
|
async function handleProfileRemove(session, flags, positional = []) {
|
|
1596
1651
|
const targetName = positional[2] || flags.name;
|
|
1597
|
-
if (!targetName)
|
|
1652
|
+
if (!targetName) {
|
|
1653
|
+
printCommandHelpAndFail('profile remove')
|
|
1654
|
+
return
|
|
1655
|
+
}
|
|
1598
1656
|
const normalizedName = normalizeProfileName(targetName);
|
|
1599
1657
|
const store = getStore(session);
|
|
1600
1658
|
if (!store.profiles[normalizedName]) {
|
|
@@ -1636,7 +1694,12 @@ async function handleOrgSet(session, flags) {
|
|
|
1636
1694
|
|
|
1637
1695
|
async function handleProjects(session, flags) {
|
|
1638
1696
|
const orgId = await resolveOrgId(session, flags);
|
|
1639
|
-
const
|
|
1697
|
+
const projectFlags = {
|
|
1698
|
+
...flags,
|
|
1699
|
+
page: flags.page ?? 1,
|
|
1700
|
+
limit: flags.limit ?? 15,
|
|
1701
|
+
};
|
|
1702
|
+
const query = buildQuery(projectFlags, ['type', 'page', 'limit']);
|
|
1640
1703
|
const data = unwrapData(await request(session, flags, 'GET', `/project${query}`, { orgId }));
|
|
1641
1704
|
const projects = asArray(data);
|
|
1642
1705
|
|
|
@@ -1648,6 +1711,10 @@ async function handleProjects(session, flags) {
|
|
|
1648
1711
|
{ label: 'Plano', value: (project) => project.plan || '-' },
|
|
1649
1712
|
{ label: 'Tipo', value: (project) => project.type_project || project.config?.type_project || '-' },
|
|
1650
1713
|
]);
|
|
1714
|
+
if (data?.pagination) {
|
|
1715
|
+
const { page = 1, pages = 1, total = projects.length } = data.pagination;
|
|
1716
|
+
process.stdout.write(`\nPagina ${page} de ${Math.max(pages, 1)} (${total} projeto(s))\n`);
|
|
1717
|
+
}
|
|
1651
1718
|
}
|
|
1652
1719
|
|
|
1653
1720
|
async function parseConfig(input) {
|
|
@@ -2496,7 +2563,8 @@ async function getProject(session, flags, projectId, orgId) {
|
|
|
2496
2563
|
}
|
|
2497
2564
|
|
|
2498
2565
|
async function handleProjectInfo(session, flags) {
|
|
2499
|
-
const projectId = requireProjectId(flags);
|
|
2566
|
+
const projectId = requireProjectId(flags, 'project info');
|
|
2567
|
+
if (!projectId) return
|
|
2500
2568
|
const orgId = await resolveOrgId(session, flags);
|
|
2501
2569
|
const project = await getProject(session, flags, projectId, orgId);
|
|
2502
2570
|
|
|
@@ -2505,7 +2573,8 @@ async function handleProjectInfo(session, flags) {
|
|
|
2505
2573
|
}
|
|
2506
2574
|
|
|
2507
2575
|
async function handleProjectUrl(session, flags) {
|
|
2508
|
-
const projectId = requireProjectId(flags);
|
|
2576
|
+
const projectId = requireProjectId(flags, 'project url');
|
|
2577
|
+
if (!projectId) return
|
|
2509
2578
|
const orgId = await resolveOrgId(session, flags);
|
|
2510
2579
|
const project = await getProject(session, flags, projectId, orgId);
|
|
2511
2580
|
const url = projectUrlOf(project);
|
|
@@ -2522,7 +2591,8 @@ async function handleProjectUrl(session, flags) {
|
|
|
2522
2591
|
}
|
|
2523
2592
|
|
|
2524
2593
|
async function handleProjectLogs(session, flags) {
|
|
2525
|
-
const projectId = requireProjectId(flags);
|
|
2594
|
+
const projectId = requireProjectId(flags, 'project logs');
|
|
2595
|
+
if (!projectId) return
|
|
2526
2596
|
const orgId = await resolveOrgId(session, flags);
|
|
2527
2597
|
const query = buildQuery(flags, ['instance']);
|
|
2528
2598
|
const logs = unwrapData(await request(session, flags, 'GET', `/project/${projectId}/logs${query}`, { orgId }));
|
|
@@ -2531,7 +2601,8 @@ async function handleProjectLogs(session, flags) {
|
|
|
2531
2601
|
}
|
|
2532
2602
|
|
|
2533
2603
|
async function handleProjectMetrics(session, flags) {
|
|
2534
|
-
const projectId = requireProjectId(flags);
|
|
2604
|
+
const projectId = requireProjectId(flags, 'project metrics');
|
|
2605
|
+
if (!projectId) return
|
|
2535
2606
|
const orgId = await resolveOrgId(session, flags);
|
|
2536
2607
|
const query = buildQuery(flags, ['instance']);
|
|
2537
2608
|
const metrics = unwrapData(await request(session, flags, 'GET', `/project/${projectId}/metrics${query}`, { orgId }));
|
|
@@ -2549,7 +2620,8 @@ async function handleProjectMetrics(session, flags) {
|
|
|
2549
2620
|
}
|
|
2550
2621
|
|
|
2551
2622
|
async function handleProjectNetwork(session, flags) {
|
|
2552
|
-
const projectId = requireProjectId(flags);
|
|
2623
|
+
const projectId = requireProjectId(flags, 'project network');
|
|
2624
|
+
if (!projectId) return
|
|
2553
2625
|
const orgId = await resolveOrgId(session, flags);
|
|
2554
2626
|
const view = String(flags.view || 'summary');
|
|
2555
2627
|
const allowedViews = new Set(['summary', 'status-codes', 'routes', 'user-agents', 'request-events', 'source-ips']);
|
|
@@ -2564,9 +2636,13 @@ async function handleProjectNetwork(session, flags) {
|
|
|
2564
2636
|
}
|
|
2565
2637
|
|
|
2566
2638
|
async function handleProjectImageSet(session, flags) {
|
|
2567
|
-
const projectId = requireProjectId(flags);
|
|
2639
|
+
const projectId = requireProjectId(flags, 'project image set');
|
|
2640
|
+
if (!projectId) return
|
|
2568
2641
|
const image = flags.image || await prompt('Imagem');
|
|
2569
|
-
if (!image)
|
|
2642
|
+
if (!image) {
|
|
2643
|
+
printCommandHelpAndFail('project image set')
|
|
2644
|
+
return
|
|
2645
|
+
}
|
|
2570
2646
|
|
|
2571
2647
|
const orgId = await resolveOrgId(session, flags);
|
|
2572
2648
|
const payload = await request(session, flags, 'PATCH', `/project/${projectId}/image`, {
|
|
@@ -2590,7 +2666,8 @@ async function patchProjectEnvs(session, flags, projectId, orgId, envs) {
|
|
|
2590
2666
|
}
|
|
2591
2667
|
|
|
2592
2668
|
async function handleProjectEnvs(session, flags) {
|
|
2593
|
-
const projectId = requireProjectId(flags);
|
|
2669
|
+
const projectId = requireProjectId(flags, 'project envs');
|
|
2670
|
+
if (!projectId) return
|
|
2594
2671
|
const orgId = await resolveOrgId(session, flags);
|
|
2595
2672
|
const envs = await getProjectEnvs(session, flags, projectId, orgId);
|
|
2596
2673
|
|
|
@@ -2598,13 +2675,26 @@ async function handleProjectEnvs(session, flags) {
|
|
|
2598
2675
|
}
|
|
2599
2676
|
|
|
2600
2677
|
async function handleProjectEnvMutation(session, flags, action) {
|
|
2601
|
-
const
|
|
2678
|
+
const helpKeyByAction = {
|
|
2679
|
+
add: 'project env add',
|
|
2680
|
+
update: 'project env update',
|
|
2681
|
+
remove: 'project env remove',
|
|
2682
|
+
}
|
|
2683
|
+
const helpKey = helpKeyByAction[action]
|
|
2684
|
+
const projectId = requireProjectId(flags, helpKey);
|
|
2685
|
+
if (!projectId) return
|
|
2602
2686
|
const name = String(flags.name || '').trim();
|
|
2603
|
-
if (!name)
|
|
2687
|
+
if (!name) {
|
|
2688
|
+
printCommandHelpAndFail(helpKey)
|
|
2689
|
+
return
|
|
2690
|
+
}
|
|
2604
2691
|
|
|
2605
2692
|
const needsValue = action === 'add' || action === 'update';
|
|
2606
2693
|
const value = needsValue ? String(flags.value ?? await promptHidden('Valor')) : undefined;
|
|
2607
|
-
if (needsValue && !value)
|
|
2694
|
+
if (needsValue && !value) {
|
|
2695
|
+
printCommandHelpAndFail(helpKey)
|
|
2696
|
+
return
|
|
2697
|
+
}
|
|
2608
2698
|
|
|
2609
2699
|
const orgId = await resolveOrgId(session, flags);
|
|
2610
2700
|
const envs = await getProjectEnvs(session, flags, projectId, orgId);
|
|
@@ -2637,7 +2727,8 @@ async function handleProjectEnvMutation(session, flags, action) {
|
|
|
2637
2727
|
}
|
|
2638
2728
|
|
|
2639
2729
|
async function handleProjectInstances(session, flags) {
|
|
2640
|
-
const projectId = requireProjectId(flags);
|
|
2730
|
+
const projectId = requireProjectId(flags, 'project instances');
|
|
2731
|
+
if (!projectId) return
|
|
2641
2732
|
const orgId = await resolveOrgId(session, flags);
|
|
2642
2733
|
const instances = unwrapData(await request(session, flags, 'GET', `/project/${projectId}/instances`, { orgId }));
|
|
2643
2734
|
|
|
@@ -2648,7 +2739,12 @@ async function handleProjectInstances(session, flags) {
|
|
|
2648
2739
|
}
|
|
2649
2740
|
|
|
2650
2741
|
async function handleProjectInstancesSet(session, flags) {
|
|
2651
|
-
const projectId = requireProjectId(flags);
|
|
2742
|
+
const projectId = requireProjectId(flags, 'project instances set');
|
|
2743
|
+
if (!projectId) return
|
|
2744
|
+
if (flags.count === undefined) {
|
|
2745
|
+
printCommandHelpAndFail('project instances set')
|
|
2746
|
+
return
|
|
2747
|
+
}
|
|
2652
2748
|
const instances = Number(flags.count);
|
|
2653
2749
|
if (!Number.isInteger(instances) || instances <= 0) {
|
|
2654
2750
|
throw new CliError('Informe --count <n> com um inteiro positivo.');
|
|
@@ -2678,7 +2774,10 @@ function buildQuery(flags, allowedKeys) {
|
|
|
2678
2774
|
|
|
2679
2775
|
async function handleDeployments(session, flags) {
|
|
2680
2776
|
const projectId = flags.project;
|
|
2681
|
-
if (!projectId)
|
|
2777
|
+
if (!projectId) {
|
|
2778
|
+
printCommandHelpAndFail(flags.__commandKey || 'builds')
|
|
2779
|
+
return
|
|
2780
|
+
}
|
|
2682
2781
|
const orgId = await resolveOrgId(session, flags);
|
|
2683
2782
|
const query = buildQuery(flags, ['page', 'limit', 'branch', 'status']);
|
|
2684
2783
|
const data = unwrapData(await request(session, flags, 'GET', `/project/${projectId}/github/builds${query}`, { orgId }));
|
|
@@ -2696,7 +2795,11 @@ async function handleDeployments(session, flags) {
|
|
|
2696
2795
|
|
|
2697
2796
|
async function handleDeploy(session, flags) {
|
|
2698
2797
|
const projectId = flags.project;
|
|
2699
|
-
if (!projectId)
|
|
2798
|
+
if (!projectId) {
|
|
2799
|
+
process.stdout.write(commandHelp(['deploy']));
|
|
2800
|
+
process.exitCode = 1;
|
|
2801
|
+
return;
|
|
2802
|
+
}
|
|
2700
2803
|
const orgId = await resolveOrgId(session, flags);
|
|
2701
2804
|
const body = {};
|
|
2702
2805
|
if (flags.branch) body.branch = String(flags.branch);
|
|
@@ -2712,44 +2815,110 @@ async function handleDeploy(session, flags) {
|
|
|
2712
2815
|
process.stdout.write(`Deploy iniciado${buildId ? `: ${buildId}` : '.'}\n`);
|
|
2713
2816
|
}
|
|
2714
2817
|
|
|
2715
|
-
async function
|
|
2716
|
-
|
|
2818
|
+
async function getBuildLogs(session, flags, projectId, buildId, orgId, { cursor = 0, limit = 200 } = {}) {
|
|
2819
|
+
const query = buildQuery({ cursor, limit }, ['cursor', 'limit'])
|
|
2820
|
+
return unwrapData(await request(session, flags, 'GET', `/project/${projectId}/github/builds/${buildId}/logs${query}`, { orgId }))
|
|
2717
2821
|
}
|
|
2718
2822
|
|
|
2719
|
-
|
|
2720
|
-
const
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2823
|
+
function printBuildLogs(logs) {
|
|
2824
|
+
for (const log of asArray(logs)) {
|
|
2825
|
+
process.stdout.write(`${buildLogLineOf(log)}\n`)
|
|
2826
|
+
}
|
|
2827
|
+
}
|
|
2724
2828
|
|
|
2725
|
-
|
|
2726
|
-
const
|
|
2727
|
-
|
|
2728
|
-
|
|
2829
|
+
function printBuildLogEventsJson(logs) {
|
|
2830
|
+
for (const log of asArray(logs)) {
|
|
2831
|
+
process.stdout.write(`${JSON.stringify(log)}\n`)
|
|
2832
|
+
}
|
|
2833
|
+
}
|
|
2834
|
+
|
|
2835
|
+
async function followBuildLogs(session, flags, projectId, buildId, orgId, {
|
|
2836
|
+
initialCursor = 0,
|
|
2837
|
+
limit = 200,
|
|
2838
|
+
intervalSeconds = 5,
|
|
2839
|
+
timeoutSeconds = 900,
|
|
2840
|
+
} = {}) {
|
|
2841
|
+
let cursor = Math.max(Number(initialCursor || 0), 0)
|
|
2842
|
+
const pollLimit = Math.max(Number(limit || 200), 1)
|
|
2843
|
+
const intervalMs = Math.max(Number(intervalSeconds || 5), 0.1) * 1000
|
|
2844
|
+
const timeoutMs = Math.max(Number(timeoutSeconds || 900), 1) * 1000
|
|
2845
|
+
const startedAt = Date.now()
|
|
2729
2846
|
|
|
2730
2847
|
while (true) {
|
|
2731
|
-
const
|
|
2732
|
-
|
|
2848
|
+
const result = await getBuildLogs(session, flags, projectId, buildId, orgId, {
|
|
2849
|
+
cursor,
|
|
2850
|
+
limit: pollLimit,
|
|
2851
|
+
})
|
|
2852
|
+
const logs = asArray(result?.logs)
|
|
2733
2853
|
|
|
2734
2854
|
if (flags.json) {
|
|
2735
|
-
|
|
2855
|
+
printBuildLogEventsJson(logs)
|
|
2736
2856
|
} else {
|
|
2737
|
-
|
|
2857
|
+
printBuildLogs(logs)
|
|
2738
2858
|
}
|
|
2739
2859
|
|
|
2740
|
-
if (
|
|
2741
|
-
|
|
2742
|
-
|
|
2860
|
+
if (typeof result?.next_cursor === 'number') {
|
|
2861
|
+
cursor = result.next_cursor
|
|
2862
|
+
} else if (logs.length > 0) {
|
|
2863
|
+
cursor = logs[logs.length - 1].sequence || cursor
|
|
2864
|
+
}
|
|
2865
|
+
|
|
2866
|
+
if (result?.finished) {
|
|
2867
|
+
if (result?.status === 'failed') {
|
|
2868
|
+
process.exitCode = 1
|
|
2743
2869
|
}
|
|
2744
|
-
return
|
|
2870
|
+
return result
|
|
2745
2871
|
}
|
|
2746
2872
|
|
|
2747
2873
|
if (Date.now() - startedAt > timeoutMs) {
|
|
2748
|
-
throw new CliError(`Timeout aguardando build ${buildId}. Ultimo status: ${status}`)
|
|
2874
|
+
throw new CliError(`Timeout aguardando build ${buildId}. Ultimo status: ${result?.status || 'unknown'}`)
|
|
2749
2875
|
}
|
|
2750
2876
|
|
|
2751
|
-
await new Promise((resolve) => setTimeout(resolve, intervalMs))
|
|
2877
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs))
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2880
|
+
|
|
2881
|
+
async function handleBuildLogs(session, flags) {
|
|
2882
|
+
const projectId = flags.project;
|
|
2883
|
+
const buildId = flags.build;
|
|
2884
|
+
if (!projectId || !buildId) {
|
|
2885
|
+
printCommandHelpAndFail('builds logs')
|
|
2886
|
+
return
|
|
2887
|
+
}
|
|
2888
|
+
|
|
2889
|
+
const orgId = await resolveOrgId(session, flags);
|
|
2890
|
+
const cursor = Math.max(Number(flags.cursor || 0), 0)
|
|
2891
|
+
const limit = Math.max(Number(flags.limit || 200), 1)
|
|
2892
|
+
|
|
2893
|
+
if (flags.follow) {
|
|
2894
|
+
return followBuildLogs(session, flags, projectId, buildId, orgId, {
|
|
2895
|
+
initialCursor: cursor,
|
|
2896
|
+
limit,
|
|
2897
|
+
intervalSeconds: Number(flags.interval || 5),
|
|
2898
|
+
timeoutSeconds: Number(flags.timeout || 900),
|
|
2899
|
+
})
|
|
2752
2900
|
}
|
|
2901
|
+
|
|
2902
|
+
const result = await getBuildLogs(session, flags, projectId, buildId, orgId, { cursor, limit })
|
|
2903
|
+
if (flags.json) return printJson(result)
|
|
2904
|
+
printBuildLogs(result?.logs)
|
|
2905
|
+
}
|
|
2906
|
+
|
|
2907
|
+
async function handleDeployWatch(session, flags) {
|
|
2908
|
+
const projectId = flags.project;
|
|
2909
|
+
const buildId = flags.build;
|
|
2910
|
+
if (!projectId || !buildId) {
|
|
2911
|
+
printCommandHelpAndFail('deploy watch')
|
|
2912
|
+
return;
|
|
2913
|
+
}
|
|
2914
|
+
|
|
2915
|
+
const orgId = await resolveOrgId(session, flags);
|
|
2916
|
+
return followBuildLogs(session, flags, projectId, buildId, orgId, {
|
|
2917
|
+
initialCursor: 0,
|
|
2918
|
+
limit: 200,
|
|
2919
|
+
intervalSeconds: Number(flags.interval || 5),
|
|
2920
|
+
timeoutSeconds: Number(flags.timeout || 900),
|
|
2921
|
+
})
|
|
2753
2922
|
}
|
|
2754
2923
|
|
|
2755
2924
|
async function main() {
|
|
@@ -2809,8 +2978,9 @@ async function main() {
|
|
|
2809
2978
|
if (command === 'project' && subcommand === 'env' && positional[2] === 'remove') return handleProjectEnvMutation(session, flags, 'remove');
|
|
2810
2979
|
if (command === 'project' && subcommand === 'instances' && positional[2] === 'set') return handleProjectInstancesSet(session, flags);
|
|
2811
2980
|
if (command === 'project' && subcommand === 'instances') return handleProjectInstances(session, flags);
|
|
2812
|
-
if (command === 'builds') return
|
|
2813
|
-
if (command === '
|
|
2981
|
+
if (command === 'builds' && subcommand === 'logs') return handleBuildLogs(session, flags);
|
|
2982
|
+
if (command === 'builds') return handleDeployments(session, { ...flags, __commandKey: 'builds' });
|
|
2983
|
+
if (command === 'deployments') return handleDeployments(session, { ...flags, __commandKey: 'deployments' });
|
|
2814
2984
|
if (command === 'deploy' && subcommand === 'watch') return handleDeployWatch(session, flags);
|
|
2815
2985
|
if (command === 'deploy') return handleDeploy(session, flags);
|
|
2816
2986
|
|