@zenifra/cli 0.3.3 → 0.4.0
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
CHANGED
|
@@ -60,6 +60,7 @@ zenifra create project --name <name> --plan basic --payment-mode hourly --config
|
|
|
60
60
|
zenifra create project --name <name> --plan premium --payment-mode hourly --config @examples/http-autoscaling-project.json
|
|
61
61
|
zenifra create project --name <name> --plan db-basic --payment-mode monthly --config @examples/postgresql-project.json
|
|
62
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
|
|
63
64
|
zenifra create project --name <name> --plan db-free --payment-mode hourly --config @examples/valkey-key-value-project.json
|
|
64
65
|
zenifra create project --name <name> --plan cache-free --payment-mode hourly --config @examples/valkey-cache-project.json
|
|
65
66
|
zenifra create project --name <name> --plan queue-free --payment-mode hourly --config @examples/valkey-queue-project.json
|
|
@@ -79,6 +80,10 @@ zenifra project metrics --project <project-id> --instance <instance-id>
|
|
|
79
80
|
zenifra project metrics capabilities --project <project-id>
|
|
80
81
|
zenifra project network --project <project-id> --view summary
|
|
81
82
|
zenifra project image set --project <project-id> --image ghcr.io/zenifra/app:tag
|
|
83
|
+
zenifra project github --project <project-id>
|
|
84
|
+
zenifra project github deploy-settings set --project <project-id> --mode branch
|
|
85
|
+
zenifra project github deploy-settings set --project <project-id> --mode tag --tag-pattern "v*"
|
|
86
|
+
zenifra project github deploy-settings set --project <project-id> --mode release --tag-pattern "v*" --include-prereleases true
|
|
82
87
|
zenifra project envs --project <project-id>
|
|
83
88
|
zenifra project env add --project <project-id> --name NODE_ENV --value production
|
|
84
89
|
zenifra project env update --project <project-id> --name NODE_ENV --value staging
|
|
@@ -154,6 +159,22 @@ Fluxos:
|
|
|
154
159
|
|
|
155
160
|
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
161
|
|
|
162
|
+
### Modos de deploy GitHub
|
|
163
|
+
|
|
164
|
+
Consulte a configuracao publica atual e escolha exatamente um modo de deploy:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
zenifra project github --project <project-id>
|
|
168
|
+
zenifra project github deploy-settings set --project <project-id> --mode manual
|
|
169
|
+
zenifra project github deploy-settings set --project <project-id> --mode branch
|
|
170
|
+
zenifra project github deploy-settings set --project <project-id> --mode tag --tag-pattern "v*"
|
|
171
|
+
zenifra project github deploy-settings set --project <project-id> --mode release --tag-pattern "v*" --include-prereleases false
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Os modos aceitos sao `manual`, `branch`, `tag` e `release`. `tag` e `release` exigem `--tag-pattern`, que aceita curingas `*` e `?`. O padrao de prereleases e `false`; a opcao so esta disponivel no modo `release`. A CLI le a configuracao salva de volta antes de confirmar uma alteracao. O comando `zenifra deploy --project <project-id> --branch <branch>` continua disponivel para iniciar um deploy manual.
|
|
175
|
+
|
|
176
|
+
Para criar um projeto HTTP com deploy por tag ou release, use `examples/http-github-tag-project.json` (padrao exato `v1.2.3`) ou `examples/http-github-release-project.json` (padrao `v*`).
|
|
177
|
+
|
|
157
178
|
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.
|
|
158
179
|
|
|
159
180
|
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.
|
|
@@ -286,9 +307,12 @@ Use os arquivos em `examples/` como base para `zenifra create project`:
|
|
|
286
307
|
|
|
287
308
|
- `examples/http-project.json`: projeto HTTP com imagem OCI publica
|
|
288
309
|
- `examples/http-github-project.json`: projeto HTTP com build via GitHub
|
|
310
|
+
- `examples/http-github-tag-project.json`: projeto HTTP com deploy por tag e padrao exato
|
|
311
|
+
- `examples/http-github-release-project.json`: projeto HTTP com deploy por release e padrao `v*`
|
|
289
312
|
- `examples/http-autoscaling-project.json`: projeto HTTP pago criado com auto-scaling
|
|
290
313
|
- `examples/postgresql-project.json`: projeto PostgreSQL
|
|
291
314
|
- `examples/mariadb-project.json`: projeto MariaDB
|
|
315
|
+
- `examples/clickhouse-project.json`: projeto ClickHouse com Native TLS e armazenamento persistente
|
|
292
316
|
- `examples/valkey-key-value-project.json`: projeto Valkey Key Value com armazenamento persistente
|
|
293
317
|
- `examples/valkey-cache-project.json`: projeto Valkey Cache sem armazenamento persistente
|
|
294
318
|
- `examples/valkey-queue-project.json`: projeto Valkey Queue com armazenamento persistente
|
|
@@ -306,6 +330,8 @@ O wizard atual cobre:
|
|
|
306
330
|
- projetos `mariadb`
|
|
307
331
|
- projetos `valkey` nos perfis `key_value`, `cache` e `queue`
|
|
308
332
|
|
|
333
|
+
Projetos `clickhouse` usam atualmente o fluxo nao interativo com `--config`, como em `examples/clickhouse-project.json`.
|
|
334
|
+
|
|
309
335
|
`zenifra create project` nao assume valores default para `--plan` e `--payment-mode`.
|
|
310
336
|
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.
|
|
311
337
|
Antes de escolher um plano com o usuario, compare os catalogos com `zenifra plans` para evitar suposicoes sobre custo.
|
|
@@ -313,19 +339,22 @@ Antes de escolher um plano com o usuario, compare os catalogos com `zenifra plan
|
|
|
313
339
|
Valores aceitos:
|
|
314
340
|
|
|
315
341
|
- `payment_mode`: `hourly`, `monthly`, `yearly`
|
|
316
|
-
- `type_project` no `config`: `http`, `postgresql`, `mariadb`, `valkey`
|
|
342
|
+
- `type_project` no `config`: `http`, `postgresql`, `mariadb`, `valkey`, `clickhouse`
|
|
317
343
|
- `exposure` no `config` HTTP: `public`, `private`
|
|
318
|
-
- `plan`: consulte `zenifra plans` para os planos atuais; Valkey usa `db-*` para Key Value, `cache-*` para Cache e `queue-*` para Queue
|
|
344
|
+
- `plan`: consulte `zenifra plans` para os planos atuais; ClickHouse usa `analytics-*`; Valkey usa `db-*` para Key Value, `cache-*` para Cache e `queue-*` para Queue
|
|
319
345
|
- `config.profile` em projetos Valkey: `key_value`, `cache` ou `queue`
|
|
320
346
|
- `config.version` em projetos Valkey: a versão retornada por `zenifra plans --type valkey`
|
|
321
347
|
- `config.storage` em projetos Valkey: obrigatório e persistente para Key Value/Queue; omitido para Cache
|
|
322
348
|
- `config.github.runtime` (quando houver GitHub em projeto HTTP): `nodejs` ou `python`
|
|
349
|
+
- `config.github.auto_deploy`: use `true` para o modo `branch`; mantenha `false` ao habilitar `version_deploy`
|
|
350
|
+
- `config.github.version_deploy`: use `enabled: true`, `event: "tag"` ou `"release"` e um `tag_pattern` explicito; `include_prereleases` e opcional e padrao `false`
|
|
323
351
|
- `config.autoscaling` (somente HTTP pago): `enabled: true`, `max_instances` maior ou igual a `config.instances` e alvos opcionais de CPU/memoria entre 1 e 100
|
|
324
352
|
|
|
325
353
|
Observacoes do wizard:
|
|
326
354
|
|
|
327
355
|
- conflitos entre documentacao e API sao validados pelo contrato real aceito pela API
|
|
328
356
|
- o wizard oferece auto-scaling apenas quando o plano selecionado informa essa disponibilidade
|
|
357
|
+
- no modo GitHub, o wizard permite escolher entre `manual`, `branch`, `tag` e `release`; modos por versao exigem um padrao de tags
|
|
329
358
|
- na criacao com auto-scaling, `config.instances` e o minimo inicial e `config.autoscaling.max_instances` e o maximo
|
|
330
359
|
- em projetos de banco, o wizard nao pergunta `username`, `password` nem `database name`
|
|
331
360
|
- em projetos de banco, a CLI preenche apenas campos tecnicos minimos exigidos pela validacao atual da API
|
package/bin/zenifra.mjs
CHANGED
|
@@ -50,6 +50,7 @@ const KNOWN_FLAG_NAMES = new Set([
|
|
|
50
50
|
'from',
|
|
51
51
|
'help',
|
|
52
52
|
'image',
|
|
53
|
+
'includePrereleases',
|
|
53
54
|
'idempotencyKey',
|
|
54
55
|
'instance',
|
|
55
56
|
'interval',
|
|
@@ -73,6 +74,7 @@ const KNOWN_FLAG_NAMES = new Set([
|
|
|
73
74
|
'revoke',
|
|
74
75
|
'showValues',
|
|
75
76
|
'status',
|
|
77
|
+
'tagPattern',
|
|
76
78
|
'timeout',
|
|
77
79
|
'to',
|
|
78
80
|
'totp',
|
|
@@ -96,6 +98,10 @@ const ALLOWED_PLAN_VALUES = new Set([
|
|
|
96
98
|
'db-basic',
|
|
97
99
|
'db-premium',
|
|
98
100
|
'db-enterprise',
|
|
101
|
+
'analytics-sandbox',
|
|
102
|
+
'analytics-starter',
|
|
103
|
+
'analytics-production',
|
|
104
|
+
'analytics-enterprise',
|
|
99
105
|
'cache-free',
|
|
100
106
|
'cache-starter',
|
|
101
107
|
'cache-basic',
|
|
@@ -108,8 +114,23 @@ const ALLOWED_PLAN_VALUES = new Set([
|
|
|
108
114
|
'queue-enterprise',
|
|
109
115
|
]);
|
|
110
116
|
const ALLOWED_PAYMENT_MODE_VALUES = new Set(['hourly', 'monthly', 'yearly']);
|
|
111
|
-
const ALLOWED_TYPE_PROJECT_VALUES = new Set(['http', 'postgresql', 'mariadb', 'valkey']);
|
|
117
|
+
const ALLOWED_TYPE_PROJECT_VALUES = new Set(['http', 'postgresql', 'mariadb', 'valkey', 'clickhouse']);
|
|
112
118
|
const ALLOWED_RUNTIME_VALUES = new Set(['nodejs', 'python']);
|
|
119
|
+
const ALLOWED_GITHUB_VERSION_DEPLOY_EVENTS = new Set(['tag', 'release']);
|
|
120
|
+
const ALLOWED_GITHUB_VERSION_DEPLOY_FIELDS = new Set(['enabled', 'event', 'tag_pattern', 'include_prereleases']);
|
|
121
|
+
const GITHUB_TAG_PATTERN_BLOCKED_CHARACTERS = /[\u0000-\u001f\u007f-\u009f\[\]{}\\]/u;
|
|
122
|
+
const GITHUB_PUBLIC_CONFIG_FIELDS = [
|
|
123
|
+
'repository_owner',
|
|
124
|
+
'repository_name',
|
|
125
|
+
'branch',
|
|
126
|
+
'runtime',
|
|
127
|
+
'version',
|
|
128
|
+
'auto_deploy',
|
|
129
|
+
'start_command',
|
|
130
|
+
'pre_build_command',
|
|
131
|
+
'build_command',
|
|
132
|
+
];
|
|
133
|
+
const GITHUB_PUBLIC_VERSION_DEPLOY_FIELDS = ['enabled', 'event', 'tag_pattern', 'include_prereleases'];
|
|
113
134
|
const ALLOWED_EXPOSURE_VALUES = new Set(['public', 'private']);
|
|
114
135
|
const GITHUB_RUNTIME_VERSIONS = {
|
|
115
136
|
nodejs: ['24', '22', '20'],
|
|
@@ -201,6 +222,8 @@ Usage:
|
|
|
201
222
|
zenifra project healthcheck failures --project <id> [--page <n>] [--limit <n>] [--json]
|
|
202
223
|
zenifra project network --project <id> [--view <summary|status-codes|routes|user-agents|request-events|source-ips>] [--json]
|
|
203
224
|
zenifra project image set --project <id> --image <image> [--json]
|
|
225
|
+
zenifra project github --project <id> [--json]
|
|
226
|
+
zenifra project github deploy-settings set --project <id> --mode <manual|branch|tag|release> [--tag-pattern <pattern>] [--include-prereleases <true|false>] [--json]
|
|
204
227
|
zenifra project exposure set --project <id> --exposure <public|private> [--json]
|
|
205
228
|
zenifra project envs --project <id> [--json] [--show-values]
|
|
206
229
|
zenifra project env add --project <id> --name <name> --value <value> [--json]
|
|
@@ -451,7 +474,41 @@ const HELP_SPECS = [
|
|
|
451
474
|
description: 'Agrupa comandos operacionais e de introspecao sobre um projeto especifico.',
|
|
452
475
|
examples: ['zenifra project', 'zenifra project info --project proj_1', 'zenifra project env add --project proj_1 --name NODE_ENV --value production'],
|
|
453
476
|
output: 'Zenifra CLI - project',
|
|
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.'],
|
|
477
|
+
notes: ['Use "zenifra help project <subcomando>" para detalhes de info, stop, resume, delete, url, logs, metrics, capabilities, network, image, github, exposure, autoscaling, billing, envs e instances.'],
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
command: 'project github',
|
|
481
|
+
usage: 'zenifra project github --project <id> [--json]',
|
|
482
|
+
description: 'Mostra a configuracao GitHub publica e o modo atual de deploy do projeto.',
|
|
483
|
+
flags: ['--project <id> ID do projeto.', '--json Imprime a configuracao publica em JSON.'],
|
|
484
|
+
examples: ['zenifra project github --project proj_1', 'zenifra project github --project proj_1 --json'],
|
|
485
|
+
output: 'Repositorio: example-team/sample-app\nBranch: main\nRuntime: nodejs@24\nModo de deploy: release\nPadrao de tags: v*\nIncluir prereleases: nao',
|
|
486
|
+
jsonOutput: '{"repository_owner":"example-team","repository_name":"sample-app","branch":"main","runtime":"nodejs","version":"24","auto_deploy":false,"version_deploy":{"enabled":true,"event":"release","tag_pattern":"v*","include_prereleases":false}}',
|
|
487
|
+
notes: ['A resposta mostra apenas campos publicos da configuracao do projeto.'],
|
|
488
|
+
},
|
|
489
|
+
{
|
|
490
|
+
command: 'project github deploy-settings set',
|
|
491
|
+
usage: 'zenifra project github deploy-settings set --project <id> --mode <manual|branch|tag|release> [--tag-pattern <pattern>] [--include-prereleases <true|false>] [--json]',
|
|
492
|
+
description: 'Define um unico modo de deploy para o projeto com origem GitHub e confirma a configuracao salva.',
|
|
493
|
+
flags: [
|
|
494
|
+
'--project <id> ID do projeto.',
|
|
495
|
+
'--mode <manual|branch|tag|release> Modo de deploy exclusivo.',
|
|
496
|
+
'--tag-pattern <pattern> Padrao obrigatorio para tag e release. Aceita * e ?.',
|
|
497
|
+
'--include-prereleases <true|false> Inclui prereleases somente no modo release. Padrao: false.',
|
|
498
|
+
'--json Imprime a configuracao confirmada em JSON.',
|
|
499
|
+
],
|
|
500
|
+
examples: [
|
|
501
|
+
'zenifra project github deploy-settings set --project proj_1 --mode manual',
|
|
502
|
+
'zenifra project github deploy-settings set --project proj_1 --mode branch',
|
|
503
|
+
'zenifra project github deploy-settings set --project proj_1 --mode tag --tag-pattern "v*"',
|
|
504
|
+
'zenifra project github deploy-settings set --project proj_1 --mode release --tag-pattern "v*" --include-prereleases true',
|
|
505
|
+
],
|
|
506
|
+
output: 'Configuracao de deploy GitHub confirmada: release (v*).',
|
|
507
|
+
jsonOutput: '{"repository_owner":"example-team","repository_name":"sample-app","branch":"main","runtime":"nodejs","version":"24","auto_deploy":false,"version_deploy":{"enabled":true,"event":"release","tag_pattern":"v*","include_prereleases":true}}',
|
|
508
|
+
notes: [
|
|
509
|
+
'Tag e release exigem --tag-pattern explicito; prereleases so podem ser habilitadas no modo release.',
|
|
510
|
+
'A configuracao salva e lida novamente antes de a CLI informar sucesso. O comando deploy manual continua disponivel.',
|
|
511
|
+
],
|
|
455
512
|
},
|
|
456
513
|
{
|
|
457
514
|
command: 'project info',
|
|
@@ -857,7 +914,9 @@ function parseArgs(argv) {
|
|
|
857
914
|
continue;
|
|
858
915
|
}
|
|
859
916
|
|
|
860
|
-
const
|
|
917
|
+
const argumentValueSeparator = arg.indexOf('=');
|
|
918
|
+
const rawKey = argumentValueSeparator === -1 ? arg.slice(2) : arg.slice(2, argumentValueSeparator);
|
|
919
|
+
const inlineValue = argumentValueSeparator === -1 ? undefined : arg.slice(argumentValueSeparator + 1);
|
|
861
920
|
const key = rawKey.replace(/-([a-z])/g, (_, value) => value.toUpperCase());
|
|
862
921
|
|
|
863
922
|
if (inlineValue !== undefined) {
|
|
@@ -1214,7 +1273,9 @@ function estimateWizardTotal(state = {}) {
|
|
|
1214
1273
|
total += 1;
|
|
1215
1274
|
|
|
1216
1275
|
if (state.httpSource === 'github') {
|
|
1217
|
-
total +=
|
|
1276
|
+
total += state.httpGithubDeployMode === 'release'
|
|
1277
|
+
? 11
|
|
1278
|
+
: state.httpGithubDeployMode === 'tag' ? 10 : 9;
|
|
1218
1279
|
} else if (state.httpSource === 'oci') {
|
|
1219
1280
|
total += 2;
|
|
1220
1281
|
if (state.httpImagePublic === false) {
|
|
@@ -2422,6 +2483,14 @@ function normalizePlan(value) {
|
|
|
2422
2483
|
['dbpremium', 'db-premium'],
|
|
2423
2484
|
['db_enterprise', 'db-enterprise'],
|
|
2424
2485
|
['dbenterprise', 'db-enterprise'],
|
|
2486
|
+
['analytics_sandbox', 'analytics-sandbox'],
|
|
2487
|
+
['analyticssandbox', 'analytics-sandbox'],
|
|
2488
|
+
['analytics_starter', 'analytics-starter'],
|
|
2489
|
+
['analyticsstarter', 'analytics-starter'],
|
|
2490
|
+
['analytics_production', 'analytics-production'],
|
|
2491
|
+
['analyticsproduction', 'analytics-production'],
|
|
2492
|
+
['analytics_enterprise', 'analytics-enterprise'],
|
|
2493
|
+
['analyticsenterprise', 'analytics-enterprise'],
|
|
2425
2494
|
['cache_free', 'cache-free'],
|
|
2426
2495
|
['cachefree', 'cache-free'],
|
|
2427
2496
|
['cache_starter', 'cache-starter'],
|
|
@@ -2481,6 +2550,8 @@ function normalizeTypeProject(value) {
|
|
|
2481
2550
|
['valkey', 'valkey'],
|
|
2482
2551
|
['key value', 'valkey'],
|
|
2483
2552
|
['key-value', 'valkey'],
|
|
2553
|
+
['clickhouse', 'clickhouse'],
|
|
2554
|
+
['click house', 'clickhouse'],
|
|
2484
2555
|
]);
|
|
2485
2556
|
return aliases.get(raw) || raw;
|
|
2486
2557
|
}
|
|
@@ -2929,6 +3000,57 @@ function validateValkeyConfig(config, plan, catalog) {
|
|
|
2929
3000
|
};
|
|
2930
3001
|
}
|
|
2931
3002
|
|
|
3003
|
+
function isValidGithubTagPattern(value) {
|
|
3004
|
+
if (typeof value !== 'string') return false;
|
|
3005
|
+
const length = Array.from(value).length;
|
|
3006
|
+
return length >= 1 && length <= 255 && value.trim().length > 0
|
|
3007
|
+
&& !GITHUB_TAG_PATTERN_BLOCKED_CHARACTERS.test(value);
|
|
3008
|
+
}
|
|
3009
|
+
|
|
3010
|
+
function validateGithubVersionDeployConfig(github) {
|
|
3011
|
+
if (!isRecord(github)) {
|
|
3012
|
+
throw new CliError('config.github deve ser um objeto.');
|
|
3013
|
+
}
|
|
3014
|
+
if (github.auto_deploy !== undefined && typeof github.auto_deploy !== 'boolean') {
|
|
3015
|
+
throw new CliError('config.github.auto_deploy deve ser true ou false.');
|
|
3016
|
+
}
|
|
3017
|
+
|
|
3018
|
+
const versionDeploy = github.version_deploy;
|
|
3019
|
+
if (versionDeploy === undefined) return;
|
|
3020
|
+
if (!isRecord(versionDeploy)) {
|
|
3021
|
+
throw new CliError('config.github.version_deploy deve ser um objeto.');
|
|
3022
|
+
}
|
|
3023
|
+
|
|
3024
|
+
const unsupportedField = Object.keys(versionDeploy).find((field) => !ALLOWED_GITHUB_VERSION_DEPLOY_FIELDS.has(field));
|
|
3025
|
+
if (unsupportedField) {
|
|
3026
|
+
throw new CliError(`Campo invalido: config.github.version_deploy.${unsupportedField}.`);
|
|
3027
|
+
}
|
|
3028
|
+
if (versionDeploy.enabled !== undefined && typeof versionDeploy.enabled !== 'boolean') {
|
|
3029
|
+
throw new CliError('config.github.version_deploy.enabled deve ser true ou false.');
|
|
3030
|
+
}
|
|
3031
|
+
if (versionDeploy.event !== undefined && !ALLOWED_GITHUB_VERSION_DEPLOY_EVENTS.has(versionDeploy.event)) {
|
|
3032
|
+
throw new CliError('config.github.version_deploy.event deve ser tag ou release.');
|
|
3033
|
+
}
|
|
3034
|
+
if (versionDeploy.tag_pattern !== undefined && !isValidGithubTagPattern(versionDeploy.tag_pattern)) {
|
|
3035
|
+
throw new CliError('Tag pattern invalido: use de 1 a 255 caracteres e evite caracteres de controle, [ ] { } e barra invertida.');
|
|
3036
|
+
}
|
|
3037
|
+
if (versionDeploy.include_prereleases !== undefined && typeof versionDeploy.include_prereleases !== 'boolean') {
|
|
3038
|
+
throw new CliError('config.github.version_deploy.include_prereleases deve ser true ou false.');
|
|
3039
|
+
}
|
|
3040
|
+
|
|
3041
|
+
const enabled = versionDeploy.enabled === true;
|
|
3042
|
+
const event = versionDeploy.event || 'tag';
|
|
3043
|
+
if (enabled && versionDeploy.tag_pattern === undefined) {
|
|
3044
|
+
throw new CliError('config.github.version_deploy.tag_pattern e obrigatorio quando o version_deploy esta ativo.');
|
|
3045
|
+
}
|
|
3046
|
+
if (enabled && github.auto_deploy === true) {
|
|
3047
|
+
throw new CliError('github.auto_deploy e github.version_deploy nao podem ficar ativos ao mesmo tempo.');
|
|
3048
|
+
}
|
|
3049
|
+
if (versionDeploy.include_prereleases === true && event !== 'release') {
|
|
3050
|
+
throw new CliError('github.version_deploy.include_prereleases so pode ser true no modo release.');
|
|
3051
|
+
}
|
|
3052
|
+
}
|
|
3053
|
+
|
|
2932
3054
|
function validateCreateInput({ plan, paymentMode, config, valkeyCatalog }) {
|
|
2933
3055
|
const nextTypeProject = normalizeTypeProject(config?.type_project);
|
|
2934
3056
|
const nextPlan = normalizePlan(plan);
|
|
@@ -2955,6 +3077,7 @@ function validateCreateInput({ plan, paymentMode, config, valkeyCatalog }) {
|
|
|
2955
3077
|
}
|
|
2956
3078
|
|
|
2957
3079
|
if (nextTypeProject === 'http' && nextConfig.github) {
|
|
3080
|
+
validateGithubVersionDeployConfig(nextConfig.github);
|
|
2958
3081
|
const nextRuntime = normalizeRuntime(nextConfig.github.runtime);
|
|
2959
3082
|
if (!nextRuntime || !ALLOWED_RUNTIME_VALUES.has(nextRuntime)) {
|
|
2960
3083
|
throw new CliError(`Runtime invalido: "${nextConfig.github.runtime || ''}". Valores aceitos: ${formatAllowedValues(ALLOWED_RUNTIME_VALUES)}. Docs: ${DOCS_RUNTIME_URL}`);
|
|
@@ -3066,11 +3189,45 @@ async function buildHttpGithubConfig() {
|
|
|
3066
3189
|
options: GITHUB_RUNTIME_VERSIONS[runtime].map((value) => ({ value })),
|
|
3067
3190
|
});
|
|
3068
3191
|
|
|
3069
|
-
const
|
|
3070
|
-
label: '
|
|
3192
|
+
const deployMode = await promptWizardSelect({
|
|
3193
|
+
label: 'Modo de deploy GitHub',
|
|
3071
3194
|
docs: DOCS_RUNTIME_URL,
|
|
3072
|
-
examples: ['
|
|
3195
|
+
examples: ['manual', 'branch', 'tag', 'release'],
|
|
3196
|
+
options: [
|
|
3197
|
+
{ value: 'manual', description: 'Deploy iniciado manualmente.' },
|
|
3198
|
+
{ value: 'branch', description: 'Deploy quando a branch configurada for atualizada.' },
|
|
3199
|
+
{ value: 'tag', description: 'Deploy quando uma tag corresponder ao padrao.' },
|
|
3200
|
+
{ value: 'release', description: 'Deploy quando uma release corresponder ao padrao.' },
|
|
3201
|
+
],
|
|
3073
3202
|
});
|
|
3203
|
+
promptWizardSelect.ui.state.httpGithubDeployMode = deployMode;
|
|
3204
|
+
|
|
3205
|
+
let versionDeploy;
|
|
3206
|
+
if (deployMode === 'tag' || deployMode === 'release') {
|
|
3207
|
+
const tagPattern = await promptWizardText({
|
|
3208
|
+
label: 'Padrao de tags',
|
|
3209
|
+
required: true,
|
|
3210
|
+
examples: ['v*', 'release/?*'],
|
|
3211
|
+
docs: DOCS_RUNTIME_URL,
|
|
3212
|
+
validate: (value) => isValidGithubTagPattern(value)
|
|
3213
|
+
? null
|
|
3214
|
+
: 'use de 1 a 255 caracteres; somente * e ? sao aceitos como curingas',
|
|
3215
|
+
});
|
|
3216
|
+
const includePrereleases = deployMode === 'release'
|
|
3217
|
+
? await promptWizardBoolean({
|
|
3218
|
+
label: 'Incluir prereleases',
|
|
3219
|
+
docs: DOCS_RUNTIME_URL,
|
|
3220
|
+
examples: ['nao', 'sim'],
|
|
3221
|
+
})
|
|
3222
|
+
: false;
|
|
3223
|
+
|
|
3224
|
+
versionDeploy = {
|
|
3225
|
+
enabled: true,
|
|
3226
|
+
event: deployMode,
|
|
3227
|
+
tag_pattern: tagPattern,
|
|
3228
|
+
include_prereleases: includePrereleases,
|
|
3229
|
+
};
|
|
3230
|
+
}
|
|
3074
3231
|
|
|
3075
3232
|
const start_command = await promptWizardText({
|
|
3076
3233
|
label: 'Start command',
|
|
@@ -3100,7 +3257,8 @@ async function buildHttpGithubConfig() {
|
|
|
3100
3257
|
branch,
|
|
3101
3258
|
runtime,
|
|
3102
3259
|
version,
|
|
3103
|
-
auto_deploy,
|
|
3260
|
+
auto_deploy: deployMode === 'branch',
|
|
3261
|
+
...(versionDeploy ? { version_deploy: versionDeploy } : {}),
|
|
3104
3262
|
start_command,
|
|
3105
3263
|
pre_build_command: preBuildCommand,
|
|
3106
3264
|
build_command: buildCommandRaw,
|
|
@@ -3665,6 +3823,160 @@ async function handleProjectInfo(session, flags) {
|
|
|
3665
3823
|
printProject({ ...project, id: projectId });
|
|
3666
3824
|
}
|
|
3667
3825
|
|
|
3826
|
+
function githubConfigFromPayload(payload) {
|
|
3827
|
+
const github = isRecord(payload?.github)
|
|
3828
|
+
? payload.github
|
|
3829
|
+
: isRecord(payload?.config?.github) ? payload.config.github : payload;
|
|
3830
|
+
if (!isRecord(github) || !github.repository_owner || !github.repository_name) {
|
|
3831
|
+
throw new CliError('O projeto nao possui uma configuracao GitHub disponivel.');
|
|
3832
|
+
}
|
|
3833
|
+
return github;
|
|
3834
|
+
}
|
|
3835
|
+
|
|
3836
|
+
function publicGithubConfiguration(github) {
|
|
3837
|
+
const output = Object.fromEntries(GITHUB_PUBLIC_CONFIG_FIELDS
|
|
3838
|
+
.filter((field) => github[field] !== undefined)
|
|
3839
|
+
.map((field) => [field, github[field]]));
|
|
3840
|
+
if (isRecord(github.version_deploy)) {
|
|
3841
|
+
output.version_deploy = Object.fromEntries(GITHUB_PUBLIC_VERSION_DEPLOY_FIELDS
|
|
3842
|
+
.filter((field) => github.version_deploy[field] !== undefined)
|
|
3843
|
+
.map((field) => [field, github.version_deploy[field]]));
|
|
3844
|
+
}
|
|
3845
|
+
return output;
|
|
3846
|
+
}
|
|
3847
|
+
|
|
3848
|
+
function githubDeploymentMode(github) {
|
|
3849
|
+
const versionDeployEnabled = github.version_deploy?.enabled === true;
|
|
3850
|
+
if (github.auto_deploy === true && versionDeployEnabled) {
|
|
3851
|
+
throw new CliError('A configuracao GitHub do projeto possui modos de deploy conflitantes.');
|
|
3852
|
+
}
|
|
3853
|
+
if (github.auto_deploy === true) return 'branch';
|
|
3854
|
+
if (!versionDeployEnabled) return 'manual';
|
|
3855
|
+
if (!ALLOWED_GITHUB_VERSION_DEPLOY_EVENTS.has(github.version_deploy.event)) {
|
|
3856
|
+
throw new CliError('A configuracao GitHub do projeto possui um modo de deploy invalido.');
|
|
3857
|
+
}
|
|
3858
|
+
return github.version_deploy.event;
|
|
3859
|
+
}
|
|
3860
|
+
|
|
3861
|
+
function printGithubConfiguration(github) {
|
|
3862
|
+
const mode = githubDeploymentMode(github);
|
|
3863
|
+
const rows = [
|
|
3864
|
+
{ field: 'Repositorio', value: `${github.repository_owner}/${github.repository_name}` },
|
|
3865
|
+
{ field: 'Branch', value: github.branch || '-' },
|
|
3866
|
+
{ field: 'Runtime', value: github.runtime ? `${github.runtime}@${github.version || '-'}` : '-' },
|
|
3867
|
+
{ field: 'Modo de deploy', value: mode },
|
|
3868
|
+
];
|
|
3869
|
+
if (mode === 'tag' || mode === 'release') {
|
|
3870
|
+
rows.push(
|
|
3871
|
+
{ field: 'Padrao de tags', value: github.version_deploy.tag_pattern || '-' },
|
|
3872
|
+
{ field: 'Incluir prereleases', value: github.version_deploy.include_prereleases === true ? 'sim' : 'nao' },
|
|
3873
|
+
);
|
|
3874
|
+
}
|
|
3875
|
+
printTable(rows, [
|
|
3876
|
+
{ label: 'Campo', value: (row) => row.field },
|
|
3877
|
+
{ label: 'Valor', value: (row) => row.value },
|
|
3878
|
+
]);
|
|
3879
|
+
}
|
|
3880
|
+
|
|
3881
|
+
function parseGithubDeploySettings(flags) {
|
|
3882
|
+
const mode = String(flags.mode || '').toLowerCase();
|
|
3883
|
+
if (!['manual', 'branch', 'tag', 'release'].includes(mode)) {
|
|
3884
|
+
throw new CliError('Informe --mode manual, branch, tag ou release.');
|
|
3885
|
+
}
|
|
3886
|
+
|
|
3887
|
+
const usesVersionDeploy = mode === 'tag' || mode === 'release';
|
|
3888
|
+
const hasTagPattern = flags.tagPattern !== undefined;
|
|
3889
|
+
const hasPrereleaseOption = flags.includePrereleases !== undefined;
|
|
3890
|
+
if (hasTagPattern && typeof flags.tagPattern !== 'string') {
|
|
3891
|
+
throw new CliError('--tag-pattern requires a value.');
|
|
3892
|
+
}
|
|
3893
|
+
if (hasPrereleaseOption && typeof flags.includePrereleases !== 'string') {
|
|
3894
|
+
throw new CliError('--include-prereleases requires a value of true or false.');
|
|
3895
|
+
}
|
|
3896
|
+
if (usesVersionDeploy && !hasTagPattern) {
|
|
3897
|
+
throw new CliError('--tag-pattern e obrigatorio para os modos tag e release.');
|
|
3898
|
+
}
|
|
3899
|
+
if (!usesVersionDeploy && hasTagPattern) {
|
|
3900
|
+
throw new CliError('--tag-pattern so pode ser usado nos modos tag e release.');
|
|
3901
|
+
}
|
|
3902
|
+
if (hasPrereleaseOption && mode !== 'release') {
|
|
3903
|
+
throw new CliError('--include-prereleases so pode ser usado com --mode release.');
|
|
3904
|
+
}
|
|
3905
|
+
|
|
3906
|
+
let includePrereleases = false;
|
|
3907
|
+
if (hasPrereleaseOption) {
|
|
3908
|
+
const value = String(flags.includePrereleases).toLowerCase();
|
|
3909
|
+
if (value !== 'true' && value !== 'false') {
|
|
3910
|
+
throw new CliError('--include-prereleases deve ser true ou false.');
|
|
3911
|
+
}
|
|
3912
|
+
includePrereleases = value === 'true';
|
|
3913
|
+
}
|
|
3914
|
+
|
|
3915
|
+
const tagPattern = usesVersionDeploy ? String(flags.tagPattern) : undefined;
|
|
3916
|
+
if (usesVersionDeploy && !isValidGithubTagPattern(tagPattern)) {
|
|
3917
|
+
throw new CliError('Tag pattern invalido: use de 1 a 255 caracteres e evite caracteres de controle, [ ] { } e barra invertida.');
|
|
3918
|
+
}
|
|
3919
|
+
|
|
3920
|
+
return {
|
|
3921
|
+
mode,
|
|
3922
|
+
includePrereleases,
|
|
3923
|
+
body: {
|
|
3924
|
+
auto_deploy: mode === 'branch',
|
|
3925
|
+
version_deploy: usesVersionDeploy
|
|
3926
|
+
? { enabled: true, event: mode, tag_pattern: tagPattern, include_prereleases: includePrereleases }
|
|
3927
|
+
: { enabled: false },
|
|
3928
|
+
},
|
|
3929
|
+
};
|
|
3930
|
+
}
|
|
3931
|
+
|
|
3932
|
+
function githubDeploySettingsMatch(github, desired) {
|
|
3933
|
+
let currentMode;
|
|
3934
|
+
try {
|
|
3935
|
+
currentMode = githubDeploymentMode(github);
|
|
3936
|
+
} catch {
|
|
3937
|
+
return false;
|
|
3938
|
+
}
|
|
3939
|
+
if (currentMode !== desired.mode) return false;
|
|
3940
|
+
if (desired.mode !== 'tag' && desired.mode !== 'release') return true;
|
|
3941
|
+
|
|
3942
|
+
const versionDeploy = github.version_deploy || {};
|
|
3943
|
+
return versionDeploy.enabled === true
|
|
3944
|
+
&& versionDeploy.event === desired.mode
|
|
3945
|
+
&& versionDeploy.tag_pattern === desired.body.version_deploy.tag_pattern
|
|
3946
|
+
&& (versionDeploy.include_prereleases === true) === desired.includePrereleases;
|
|
3947
|
+
}
|
|
3948
|
+
|
|
3949
|
+
async function handleProjectGithub(session, flags) {
|
|
3950
|
+
const projectId = requireProjectId(flags, 'project github');
|
|
3951
|
+
if (!projectId) return;
|
|
3952
|
+
const orgId = await resolveOrgId(session, flags);
|
|
3953
|
+
const github = githubConfigFromPayload(unwrapData(await request(session, flags, 'GET', `/project/${projectId}/github`, { orgId })));
|
|
3954
|
+
const publicConfig = publicGithubConfiguration(github);
|
|
3955
|
+
|
|
3956
|
+
if (flags.json) return printJson(publicConfig);
|
|
3957
|
+
printGithubConfiguration(github);
|
|
3958
|
+
}
|
|
3959
|
+
|
|
3960
|
+
async function handleProjectGithubDeploySettingsSet(session, flags) {
|
|
3961
|
+
const projectId = requireProjectId(flags, 'project github deploy-settings set');
|
|
3962
|
+
if (!projectId) return;
|
|
3963
|
+
const desired = parseGithubDeploySettings(flags);
|
|
3964
|
+
const orgId = await resolveOrgId(session, flags);
|
|
3965
|
+
|
|
3966
|
+
await request(session, flags, 'PATCH', `/project/${projectId}/github/deploy-settings`, {
|
|
3967
|
+
orgId,
|
|
3968
|
+
body: desired.body,
|
|
3969
|
+
});
|
|
3970
|
+
const github = githubConfigFromPayload(unwrapData(await request(session, flags, 'GET', `/project/${projectId}/github`, { orgId })));
|
|
3971
|
+
if (!githubDeploySettingsMatch(github, desired)) {
|
|
3972
|
+
throw new CliError('A configuracao solicitada nao foi confirmada pela API. Consulte "zenifra project github".');
|
|
3973
|
+
}
|
|
3974
|
+
|
|
3975
|
+
const publicConfig = publicGithubConfiguration(github);
|
|
3976
|
+
if (flags.json) return printJson(publicConfig);
|
|
3977
|
+
process.stdout.write(`Configuracao de deploy GitHub confirmada: ${desired.mode}${desired.mode === 'tag' || desired.mode === 'release' ? ` (${desired.body.version_deploy.tag_pattern})` : ''}.\n`);
|
|
3978
|
+
}
|
|
3979
|
+
|
|
3668
3980
|
async function handleProjectLifecycleMutation(session, flags, action) {
|
|
3669
3981
|
const commandKey = `project ${action}`;
|
|
3670
3982
|
const projectId = requireProjectId(flags, commandKey);
|
|
@@ -4671,6 +4983,12 @@ async function main() {
|
|
|
4671
4983
|
if (command === 'project' && subcommand === 'healthcheck' && positional[2] === 'set') return handleProjectHealthcheckSet(session, flags);
|
|
4672
4984
|
if (command === 'project' && subcommand === 'healthcheck' && positional[2] === 'disable') return handleProjectHealthcheckDisable(session, flags);
|
|
4673
4985
|
if (command === 'project' && subcommand === 'healthcheck' && positional[2] === 'failures') return handleProjectHealthcheckFailures(session, flags);
|
|
4986
|
+
if (command === 'project' && subcommand === 'github' && positional[2] === 'deploy-settings' && positional[3] === 'set') return handleProjectGithubDeploySettingsSet(session, flags);
|
|
4987
|
+
if (command === 'project' && subcommand === 'github' && positional[2] === 'deploy-settings') {
|
|
4988
|
+
process.stdout.write(commandHelp(['project', 'github', 'deploy-settings', 'set']));
|
|
4989
|
+
return;
|
|
4990
|
+
}
|
|
4991
|
+
if (command === 'project' && subcommand === 'github' && positional.length === 2) return handleProjectGithub(session, flags);
|
|
4674
4992
|
if (command === 'project' && subcommand === 'info') return handleProjectInfo(session, flags);
|
|
4675
4993
|
if (command === 'project' && subcommand === 'stop') return handleProjectLifecycleMutation(session, flags, 'stop');
|
|
4676
4994
|
if (command === 'project' && subcommand === 'resume') return handleProjectLifecycleMutation(session, flags, 'resume');
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type_project": "http",
|
|
3
|
+
"exposure": "public",
|
|
4
|
+
"github": {
|
|
5
|
+
"repository_owner": "example-team",
|
|
6
|
+
"repository_name": "sample-app",
|
|
7
|
+
"branch": "main",
|
|
8
|
+
"runtime": "nodejs",
|
|
9
|
+
"version": "24",
|
|
10
|
+
"auto_deploy": false,
|
|
11
|
+
"version_deploy": {
|
|
12
|
+
"enabled": true,
|
|
13
|
+
"event": "release",
|
|
14
|
+
"tag_pattern": "v*",
|
|
15
|
+
"include_prereleases": false
|
|
16
|
+
},
|
|
17
|
+
"start_command": "npm start",
|
|
18
|
+
"pre_build_command": null,
|
|
19
|
+
"build_command": "npm run build"
|
|
20
|
+
},
|
|
21
|
+
"port": 3000,
|
|
22
|
+
"instances": 1,
|
|
23
|
+
"storage": {
|
|
24
|
+
"persistent": false,
|
|
25
|
+
"capacity": 1
|
|
26
|
+
},
|
|
27
|
+
"envs": [
|
|
28
|
+
{
|
|
29
|
+
"name": "NODE_ENV",
|
|
30
|
+
"value": "production"
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
"network_access": {
|
|
34
|
+
"ingress_white_list": [
|
|
35
|
+
{
|
|
36
|
+
"cidr": "0.0.0.0/0",
|
|
37
|
+
"description": "Allow all"
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"ingress_black_list": []
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type_project": "http",
|
|
3
|
+
"exposure": "public",
|
|
4
|
+
"github": {
|
|
5
|
+
"repository_owner": "example-team",
|
|
6
|
+
"repository_name": "sample-app",
|
|
7
|
+
"branch": "main",
|
|
8
|
+
"runtime": "nodejs",
|
|
9
|
+
"version": "24",
|
|
10
|
+
"auto_deploy": false,
|
|
11
|
+
"version_deploy": {
|
|
12
|
+
"enabled": true,
|
|
13
|
+
"event": "tag",
|
|
14
|
+
"tag_pattern": "v1.2.3",
|
|
15
|
+
"include_prereleases": false
|
|
16
|
+
},
|
|
17
|
+
"start_command": "npm start",
|
|
18
|
+
"pre_build_command": null,
|
|
19
|
+
"build_command": "npm run build"
|
|
20
|
+
},
|
|
21
|
+
"port": 3000,
|
|
22
|
+
"instances": 1,
|
|
23
|
+
"storage": {
|
|
24
|
+
"persistent": false,
|
|
25
|
+
"capacity": 1
|
|
26
|
+
},
|
|
27
|
+
"envs": [
|
|
28
|
+
{
|
|
29
|
+
"name": "NODE_ENV",
|
|
30
|
+
"value": "production"
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
"network_access": {
|
|
34
|
+
"ingress_white_list": [
|
|
35
|
+
{
|
|
36
|
+
"cidr": "0.0.0.0/0",
|
|
37
|
+
"description": "Allow all"
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"ingress_black_list": []
|
|
41
|
+
}
|
|
42
|
+
}
|