@zenifra/cli 0.3.4 → 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 +25 -0
- package/bin/zenifra.mjs +311 -7
- package/examples/http-github-release-project.json +42 -0
- package/examples/http-github-tag-project.json +42 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -80,6 +80,10 @@ zenifra project metrics --project <project-id> --instance <instance-id>
|
|
|
80
80
|
zenifra project metrics capabilities --project <project-id>
|
|
81
81
|
zenifra project network --project <project-id> --view summary
|
|
82
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
|
|
83
87
|
zenifra project envs --project <project-id>
|
|
84
88
|
zenifra project env add --project <project-id> --name NODE_ENV --value production
|
|
85
89
|
zenifra project env update --project <project-id> --name NODE_ENV --value staging
|
|
@@ -155,6 +159,22 @@ Fluxos:
|
|
|
155
159
|
|
|
156
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.
|
|
157
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
|
+
|
|
158
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.
|
|
159
179
|
|
|
160
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.
|
|
@@ -287,6 +307,8 @@ Use os arquivos em `examples/` como base para `zenifra create project`:
|
|
|
287
307
|
|
|
288
308
|
- `examples/http-project.json`: projeto HTTP com imagem OCI publica
|
|
289
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*`
|
|
290
312
|
- `examples/http-autoscaling-project.json`: projeto HTTP pago criado com auto-scaling
|
|
291
313
|
- `examples/postgresql-project.json`: projeto PostgreSQL
|
|
292
314
|
- `examples/mariadb-project.json`: projeto MariaDB
|
|
@@ -324,12 +346,15 @@ Valores aceitos:
|
|
|
324
346
|
- `config.version` em projetos Valkey: a versão retornada por `zenifra plans --type valkey`
|
|
325
347
|
- `config.storage` em projetos Valkey: obrigatório e persistente para Key Value/Queue; omitido para Cache
|
|
326
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`
|
|
327
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
|
|
328
352
|
|
|
329
353
|
Observacoes do wizard:
|
|
330
354
|
|
|
331
355
|
- conflitos entre documentacao e API sao validados pelo contrato real aceito pela API
|
|
332
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
|
|
333
358
|
- na criacao com auto-scaling, `config.instances` e o minimo inicial e `config.autoscaling.max_instances` e o maximo
|
|
334
359
|
- em projetos de banco, o wizard nao pergunta `username`, `password` nem `database name`
|
|
335
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',
|
|
@@ -114,6 +116,21 @@ const ALLOWED_PLAN_VALUES = new Set([
|
|
|
114
116
|
const ALLOWED_PAYMENT_MODE_VALUES = new Set(['hourly', 'monthly', 'yearly']);
|
|
115
117
|
const ALLOWED_TYPE_PROJECT_VALUES = new Set(['http', 'postgresql', 'mariadb', 'valkey', 'clickhouse']);
|
|
116
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'];
|
|
117
134
|
const ALLOWED_EXPOSURE_VALUES = new Set(['public', 'private']);
|
|
118
135
|
const GITHUB_RUNTIME_VERSIONS = {
|
|
119
136
|
nodejs: ['24', '22', '20'],
|
|
@@ -205,6 +222,8 @@ Usage:
|
|
|
205
222
|
zenifra project healthcheck failures --project <id> [--page <n>] [--limit <n>] [--json]
|
|
206
223
|
zenifra project network --project <id> [--view <summary|status-codes|routes|user-agents|request-events|source-ips>] [--json]
|
|
207
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]
|
|
208
227
|
zenifra project exposure set --project <id> --exposure <public|private> [--json]
|
|
209
228
|
zenifra project envs --project <id> [--json] [--show-values]
|
|
210
229
|
zenifra project env add --project <id> --name <name> --value <value> [--json]
|
|
@@ -455,7 +474,41 @@ const HELP_SPECS = [
|
|
|
455
474
|
description: 'Agrupa comandos operacionais e de introspecao sobre um projeto especifico.',
|
|
456
475
|
examples: ['zenifra project', 'zenifra project info --project proj_1', 'zenifra project env add --project proj_1 --name NODE_ENV --value production'],
|
|
457
476
|
output: 'Zenifra CLI - project',
|
|
458
|
-
notes: ['Use "zenifra help project <subcomando>" para detalhes de info, stop, resume, delete, url, logs, metrics, capabilities, network, image, exposure, autoscaling, billing, envs e instances.'],
|
|
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
|
+
],
|
|
459
512
|
},
|
|
460
513
|
{
|
|
461
514
|
command: 'project info',
|
|
@@ -861,7 +914,9 @@ function parseArgs(argv) {
|
|
|
861
914
|
continue;
|
|
862
915
|
}
|
|
863
916
|
|
|
864
|
-
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);
|
|
865
920
|
const key = rawKey.replace(/-([a-z])/g, (_, value) => value.toUpperCase());
|
|
866
921
|
|
|
867
922
|
if (inlineValue !== undefined) {
|
|
@@ -1218,7 +1273,9 @@ function estimateWizardTotal(state = {}) {
|
|
|
1218
1273
|
total += 1;
|
|
1219
1274
|
|
|
1220
1275
|
if (state.httpSource === 'github') {
|
|
1221
|
-
total +=
|
|
1276
|
+
total += state.httpGithubDeployMode === 'release'
|
|
1277
|
+
? 11
|
|
1278
|
+
: state.httpGithubDeployMode === 'tag' ? 10 : 9;
|
|
1222
1279
|
} else if (state.httpSource === 'oci') {
|
|
1223
1280
|
total += 2;
|
|
1224
1281
|
if (state.httpImagePublic === false) {
|
|
@@ -2943,6 +3000,57 @@ function validateValkeyConfig(config, plan, catalog) {
|
|
|
2943
3000
|
};
|
|
2944
3001
|
}
|
|
2945
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
|
+
|
|
2946
3054
|
function validateCreateInput({ plan, paymentMode, config, valkeyCatalog }) {
|
|
2947
3055
|
const nextTypeProject = normalizeTypeProject(config?.type_project);
|
|
2948
3056
|
const nextPlan = normalizePlan(plan);
|
|
@@ -2969,6 +3077,7 @@ function validateCreateInput({ plan, paymentMode, config, valkeyCatalog }) {
|
|
|
2969
3077
|
}
|
|
2970
3078
|
|
|
2971
3079
|
if (nextTypeProject === 'http' && nextConfig.github) {
|
|
3080
|
+
validateGithubVersionDeployConfig(nextConfig.github);
|
|
2972
3081
|
const nextRuntime = normalizeRuntime(nextConfig.github.runtime);
|
|
2973
3082
|
if (!nextRuntime || !ALLOWED_RUNTIME_VALUES.has(nextRuntime)) {
|
|
2974
3083
|
throw new CliError(`Runtime invalido: "${nextConfig.github.runtime || ''}". Valores aceitos: ${formatAllowedValues(ALLOWED_RUNTIME_VALUES)}. Docs: ${DOCS_RUNTIME_URL}`);
|
|
@@ -3080,11 +3189,45 @@ async function buildHttpGithubConfig() {
|
|
|
3080
3189
|
options: GITHUB_RUNTIME_VERSIONS[runtime].map((value) => ({ value })),
|
|
3081
3190
|
});
|
|
3082
3191
|
|
|
3083
|
-
const
|
|
3084
|
-
label: '
|
|
3192
|
+
const deployMode = await promptWizardSelect({
|
|
3193
|
+
label: 'Modo de deploy GitHub',
|
|
3085
3194
|
docs: DOCS_RUNTIME_URL,
|
|
3086
|
-
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
|
+
],
|
|
3087
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
|
+
}
|
|
3088
3231
|
|
|
3089
3232
|
const start_command = await promptWizardText({
|
|
3090
3233
|
label: 'Start command',
|
|
@@ -3114,7 +3257,8 @@ async function buildHttpGithubConfig() {
|
|
|
3114
3257
|
branch,
|
|
3115
3258
|
runtime,
|
|
3116
3259
|
version,
|
|
3117
|
-
auto_deploy,
|
|
3260
|
+
auto_deploy: deployMode === 'branch',
|
|
3261
|
+
...(versionDeploy ? { version_deploy: versionDeploy } : {}),
|
|
3118
3262
|
start_command,
|
|
3119
3263
|
pre_build_command: preBuildCommand,
|
|
3120
3264
|
build_command: buildCommandRaw,
|
|
@@ -3679,6 +3823,160 @@ async function handleProjectInfo(session, flags) {
|
|
|
3679
3823
|
printProject({ ...project, id: projectId });
|
|
3680
3824
|
}
|
|
3681
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
|
+
|
|
3682
3980
|
async function handleProjectLifecycleMutation(session, flags, action) {
|
|
3683
3981
|
const commandKey = `project ${action}`;
|
|
3684
3982
|
const projectId = requireProjectId(flags, commandKey);
|
|
@@ -4685,6 +4983,12 @@ async function main() {
|
|
|
4685
4983
|
if (command === 'project' && subcommand === 'healthcheck' && positional[2] === 'set') return handleProjectHealthcheckSet(session, flags);
|
|
4686
4984
|
if (command === 'project' && subcommand === 'healthcheck' && positional[2] === 'disable') return handleProjectHealthcheckDisable(session, flags);
|
|
4687
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);
|
|
4688
4992
|
if (command === 'project' && subcommand === 'info') return handleProjectInfo(session, flags);
|
|
4689
4993
|
if (command === 'project' && subcommand === 'stop') return handleProjectLifecycleMutation(session, flags, 'stop');
|
|
4690
4994
|
if (command === 'project' && subcommand === 'resume') return handleProjectLifecycleMutation(session, flags, 'resume');
|
|
@@ -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
|
+
}
|