@zenifra/cli 0.2.2 → 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 +24 -1
- package/bin/zenifra.mjs +211 -50
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -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`
|
|
@@ -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',
|
|
@@ -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]
|
|
@@ -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]) {
|
|
@@ -2505,7 +2563,8 @@ async function getProject(session, flags, projectId, orgId) {
|
|
|
2505
2563
|
}
|
|
2506
2564
|
|
|
2507
2565
|
async function handleProjectInfo(session, flags) {
|
|
2508
|
-
const projectId = requireProjectId(flags);
|
|
2566
|
+
const projectId = requireProjectId(flags, 'project info');
|
|
2567
|
+
if (!projectId) return
|
|
2509
2568
|
const orgId = await resolveOrgId(session, flags);
|
|
2510
2569
|
const project = await getProject(session, flags, projectId, orgId);
|
|
2511
2570
|
|
|
@@ -2514,7 +2573,8 @@ async function handleProjectInfo(session, flags) {
|
|
|
2514
2573
|
}
|
|
2515
2574
|
|
|
2516
2575
|
async function handleProjectUrl(session, flags) {
|
|
2517
|
-
const projectId = requireProjectId(flags);
|
|
2576
|
+
const projectId = requireProjectId(flags, 'project url');
|
|
2577
|
+
if (!projectId) return
|
|
2518
2578
|
const orgId = await resolveOrgId(session, flags);
|
|
2519
2579
|
const project = await getProject(session, flags, projectId, orgId);
|
|
2520
2580
|
const url = projectUrlOf(project);
|
|
@@ -2531,7 +2591,8 @@ async function handleProjectUrl(session, flags) {
|
|
|
2531
2591
|
}
|
|
2532
2592
|
|
|
2533
2593
|
async function handleProjectLogs(session, flags) {
|
|
2534
|
-
const projectId = requireProjectId(flags);
|
|
2594
|
+
const projectId = requireProjectId(flags, 'project logs');
|
|
2595
|
+
if (!projectId) return
|
|
2535
2596
|
const orgId = await resolveOrgId(session, flags);
|
|
2536
2597
|
const query = buildQuery(flags, ['instance']);
|
|
2537
2598
|
const logs = unwrapData(await request(session, flags, 'GET', `/project/${projectId}/logs${query}`, { orgId }));
|
|
@@ -2540,7 +2601,8 @@ async function handleProjectLogs(session, flags) {
|
|
|
2540
2601
|
}
|
|
2541
2602
|
|
|
2542
2603
|
async function handleProjectMetrics(session, flags) {
|
|
2543
|
-
const projectId = requireProjectId(flags);
|
|
2604
|
+
const projectId = requireProjectId(flags, 'project metrics');
|
|
2605
|
+
if (!projectId) return
|
|
2544
2606
|
const orgId = await resolveOrgId(session, flags);
|
|
2545
2607
|
const query = buildQuery(flags, ['instance']);
|
|
2546
2608
|
const metrics = unwrapData(await request(session, flags, 'GET', `/project/${projectId}/metrics${query}`, { orgId }));
|
|
@@ -2558,7 +2620,8 @@ async function handleProjectMetrics(session, flags) {
|
|
|
2558
2620
|
}
|
|
2559
2621
|
|
|
2560
2622
|
async function handleProjectNetwork(session, flags) {
|
|
2561
|
-
const projectId = requireProjectId(flags);
|
|
2623
|
+
const projectId = requireProjectId(flags, 'project network');
|
|
2624
|
+
if (!projectId) return
|
|
2562
2625
|
const orgId = await resolveOrgId(session, flags);
|
|
2563
2626
|
const view = String(flags.view || 'summary');
|
|
2564
2627
|
const allowedViews = new Set(['summary', 'status-codes', 'routes', 'user-agents', 'request-events', 'source-ips']);
|
|
@@ -2573,9 +2636,13 @@ async function handleProjectNetwork(session, flags) {
|
|
|
2573
2636
|
}
|
|
2574
2637
|
|
|
2575
2638
|
async function handleProjectImageSet(session, flags) {
|
|
2576
|
-
const projectId = requireProjectId(flags);
|
|
2639
|
+
const projectId = requireProjectId(flags, 'project image set');
|
|
2640
|
+
if (!projectId) return
|
|
2577
2641
|
const image = flags.image || await prompt('Imagem');
|
|
2578
|
-
if (!image)
|
|
2642
|
+
if (!image) {
|
|
2643
|
+
printCommandHelpAndFail('project image set')
|
|
2644
|
+
return
|
|
2645
|
+
}
|
|
2579
2646
|
|
|
2580
2647
|
const orgId = await resolveOrgId(session, flags);
|
|
2581
2648
|
const payload = await request(session, flags, 'PATCH', `/project/${projectId}/image`, {
|
|
@@ -2599,7 +2666,8 @@ async function patchProjectEnvs(session, flags, projectId, orgId, envs) {
|
|
|
2599
2666
|
}
|
|
2600
2667
|
|
|
2601
2668
|
async function handleProjectEnvs(session, flags) {
|
|
2602
|
-
const projectId = requireProjectId(flags);
|
|
2669
|
+
const projectId = requireProjectId(flags, 'project envs');
|
|
2670
|
+
if (!projectId) return
|
|
2603
2671
|
const orgId = await resolveOrgId(session, flags);
|
|
2604
2672
|
const envs = await getProjectEnvs(session, flags, projectId, orgId);
|
|
2605
2673
|
|
|
@@ -2607,13 +2675,26 @@ async function handleProjectEnvs(session, flags) {
|
|
|
2607
2675
|
}
|
|
2608
2676
|
|
|
2609
2677
|
async function handleProjectEnvMutation(session, flags, action) {
|
|
2610
|
-
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
|
|
2611
2686
|
const name = String(flags.name || '').trim();
|
|
2612
|
-
if (!name)
|
|
2687
|
+
if (!name) {
|
|
2688
|
+
printCommandHelpAndFail(helpKey)
|
|
2689
|
+
return
|
|
2690
|
+
}
|
|
2613
2691
|
|
|
2614
2692
|
const needsValue = action === 'add' || action === 'update';
|
|
2615
2693
|
const value = needsValue ? String(flags.value ?? await promptHidden('Valor')) : undefined;
|
|
2616
|
-
if (needsValue && !value)
|
|
2694
|
+
if (needsValue && !value) {
|
|
2695
|
+
printCommandHelpAndFail(helpKey)
|
|
2696
|
+
return
|
|
2697
|
+
}
|
|
2617
2698
|
|
|
2618
2699
|
const orgId = await resolveOrgId(session, flags);
|
|
2619
2700
|
const envs = await getProjectEnvs(session, flags, projectId, orgId);
|
|
@@ -2646,7 +2727,8 @@ async function handleProjectEnvMutation(session, flags, action) {
|
|
|
2646
2727
|
}
|
|
2647
2728
|
|
|
2648
2729
|
async function handleProjectInstances(session, flags) {
|
|
2649
|
-
const projectId = requireProjectId(flags);
|
|
2730
|
+
const projectId = requireProjectId(flags, 'project instances');
|
|
2731
|
+
if (!projectId) return
|
|
2650
2732
|
const orgId = await resolveOrgId(session, flags);
|
|
2651
2733
|
const instances = unwrapData(await request(session, flags, 'GET', `/project/${projectId}/instances`, { orgId }));
|
|
2652
2734
|
|
|
@@ -2657,7 +2739,12 @@ async function handleProjectInstances(session, flags) {
|
|
|
2657
2739
|
}
|
|
2658
2740
|
|
|
2659
2741
|
async function handleProjectInstancesSet(session, flags) {
|
|
2660
|
-
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
|
+
}
|
|
2661
2748
|
const instances = Number(flags.count);
|
|
2662
2749
|
if (!Number.isInteger(instances) || instances <= 0) {
|
|
2663
2750
|
throw new CliError('Informe --count <n> com um inteiro positivo.');
|
|
@@ -2687,7 +2774,10 @@ function buildQuery(flags, allowedKeys) {
|
|
|
2687
2774
|
|
|
2688
2775
|
async function handleDeployments(session, flags) {
|
|
2689
2776
|
const projectId = flags.project;
|
|
2690
|
-
if (!projectId)
|
|
2777
|
+
if (!projectId) {
|
|
2778
|
+
printCommandHelpAndFail(flags.__commandKey || 'builds')
|
|
2779
|
+
return
|
|
2780
|
+
}
|
|
2691
2781
|
const orgId = await resolveOrgId(session, flags);
|
|
2692
2782
|
const query = buildQuery(flags, ['page', 'limit', 'branch', 'status']);
|
|
2693
2783
|
const data = unwrapData(await request(session, flags, 'GET', `/project/${projectId}/github/builds${query}`, { orgId }));
|
|
@@ -2705,7 +2795,11 @@ async function handleDeployments(session, flags) {
|
|
|
2705
2795
|
|
|
2706
2796
|
async function handleDeploy(session, flags) {
|
|
2707
2797
|
const projectId = flags.project;
|
|
2708
|
-
if (!projectId)
|
|
2798
|
+
if (!projectId) {
|
|
2799
|
+
process.stdout.write(commandHelp(['deploy']));
|
|
2800
|
+
process.exitCode = 1;
|
|
2801
|
+
return;
|
|
2802
|
+
}
|
|
2709
2803
|
const orgId = await resolveOrgId(session, flags);
|
|
2710
2804
|
const body = {};
|
|
2711
2805
|
if (flags.branch) body.branch = String(flags.branch);
|
|
@@ -2721,44 +2815,110 @@ async function handleDeploy(session, flags) {
|
|
|
2721
2815
|
process.stdout.write(`Deploy iniciado${buildId ? `: ${buildId}` : '.'}\n`);
|
|
2722
2816
|
}
|
|
2723
2817
|
|
|
2724
|
-
async function
|
|
2725
|
-
|
|
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 }))
|
|
2726
2821
|
}
|
|
2727
2822
|
|
|
2728
|
-
|
|
2729
|
-
const
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2823
|
+
function printBuildLogs(logs) {
|
|
2824
|
+
for (const log of asArray(logs)) {
|
|
2825
|
+
process.stdout.write(`${buildLogLineOf(log)}\n`)
|
|
2826
|
+
}
|
|
2827
|
+
}
|
|
2733
2828
|
|
|
2734
|
-
|
|
2735
|
-
const
|
|
2736
|
-
|
|
2737
|
-
|
|
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()
|
|
2738
2846
|
|
|
2739
2847
|
while (true) {
|
|
2740
|
-
const
|
|
2741
|
-
|
|
2848
|
+
const result = await getBuildLogs(session, flags, projectId, buildId, orgId, {
|
|
2849
|
+
cursor,
|
|
2850
|
+
limit: pollLimit,
|
|
2851
|
+
})
|
|
2852
|
+
const logs = asArray(result?.logs)
|
|
2742
2853
|
|
|
2743
2854
|
if (flags.json) {
|
|
2744
|
-
|
|
2855
|
+
printBuildLogEventsJson(logs)
|
|
2745
2856
|
} else {
|
|
2746
|
-
|
|
2857
|
+
printBuildLogs(logs)
|
|
2747
2858
|
}
|
|
2748
2859
|
|
|
2749
|
-
if (
|
|
2750
|
-
|
|
2751
|
-
|
|
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
|
|
2752
2869
|
}
|
|
2753
|
-
return
|
|
2870
|
+
return result
|
|
2754
2871
|
}
|
|
2755
2872
|
|
|
2756
2873
|
if (Date.now() - startedAt > timeoutMs) {
|
|
2757
|
-
throw new CliError(`Timeout aguardando build ${buildId}. Ultimo status: ${status}`)
|
|
2874
|
+
throw new CliError(`Timeout aguardando build ${buildId}. Ultimo status: ${result?.status || 'unknown'}`)
|
|
2758
2875
|
}
|
|
2759
2876
|
|
|
2760
|
-
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
|
|
2761
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
|
+
})
|
|
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
|
+
})
|
|
2762
2922
|
}
|
|
2763
2923
|
|
|
2764
2924
|
async function main() {
|
|
@@ -2818,8 +2978,9 @@ async function main() {
|
|
|
2818
2978
|
if (command === 'project' && subcommand === 'env' && positional[2] === 'remove') return handleProjectEnvMutation(session, flags, 'remove');
|
|
2819
2979
|
if (command === 'project' && subcommand === 'instances' && positional[2] === 'set') return handleProjectInstancesSet(session, flags);
|
|
2820
2980
|
if (command === 'project' && subcommand === 'instances') return handleProjectInstances(session, flags);
|
|
2821
|
-
if (command === 'builds') return
|
|
2822
|
-
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' });
|
|
2823
2984
|
if (command === 'deploy' && subcommand === 'watch') return handleDeployWatch(session, flags);
|
|
2824
2985
|
if (command === 'deploy') return handleDeploy(session, flags);
|
|
2825
2986
|
|