@neetru/cli 2.31.0 → 2.32.1
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/CHANGELOG.md +99 -2
- package/README.md +2 -0
- package/dist/cli-kit/choices.d.ts +33 -0
- package/dist/cli-kit/choices.js +32 -0
- package/dist/cli-kit/command-context.d.ts +98 -0
- package/dist/cli-kit/command-context.js +108 -0
- package/dist/cli-kit/errors.d.ts +79 -0
- package/dist/cli-kit/errors.js +94 -0
- package/dist/cli-kit/index.d.ts +8 -0
- package/dist/cli-kit/index.js +8 -0
- package/dist/cli-kit/suggest.d.ts +74 -0
- package/dist/cli-kit/suggest.js +207 -0
- package/dist/commands/admin/oidc-login.js +1 -1
- package/dist/commands/admin/sdk-credential.js +1 -1
- package/dist/commands/autocomplete.d.ts +1 -1
- package/dist/commands/autocomplete.js +1 -0
- package/dist/commands/bug.d.ts +59 -0
- package/dist/commands/bug.js +118 -6
- package/dist/commands/build.d.ts +24 -1
- package/dist/commands/build.js +195 -73
- package/dist/commands/chat.d.ts +94 -1
- package/dist/commands/chat.js +258 -26
- package/dist/commands/customers.js +1 -1
- package/dist/commands/db-move.js +3 -3
- package/dist/commands/db.js +20 -33
- package/dist/commands/deploy.d.ts +91 -2
- package/dist/commands/deploy.js +423 -78
- package/dist/commands/deployments.js +11 -6
- package/dist/commands/dev-context.js +15 -5
- package/dist/commands/docs-produto.d.ts +25 -0
- package/dist/commands/docs-produto.js +70 -8
- package/dist/commands/doctor.js +36 -1
- package/dist/commands/domains.js +85 -4
- package/dist/commands/env.d.ts +9 -3
- package/dist/commands/env.js +23 -3
- package/dist/commands/infra-read.js +2 -2
- package/dist/commands/init.js +8 -1
- package/dist/commands/login.js +35 -8
- package/dist/commands/logs.js +12 -4
- package/dist/commands/mobile.js +33 -9
- package/dist/commands/new.d.ts +29 -1
- package/dist/commands/new.js +92 -4
- package/dist/commands/open.js +16 -6
- package/dist/commands/ops.js +1 -1
- package/dist/commands/products-db.d.ts +1 -0
- package/dist/commands/products-db.js +41 -8
- package/dist/commands/products.d.ts +17 -3
- package/dist/commands/products.js +86 -18
- package/dist/commands/promote.js +30 -5
- package/dist/commands/secrets.d.ts +91 -0
- package/dist/commands/secrets.js +471 -0
- package/dist/commands/servers.js +8 -5
- package/dist/commands/status.js +2 -2
- package/dist/commands/support.js +5 -3
- package/dist/commands/tenants.js +28 -16
- package/dist/commands/upgrade.d.ts +9 -0
- package/dist/commands/upgrade.js +50 -1
- package/dist/commands/vm-memory.js +1 -0
- package/dist/commands/webhooks.d.ts +2 -0
- package/dist/commands/webhooks.js +9 -3
- package/dist/commands/workspaces.js +42 -11
- package/dist/index.js +70 -14
- package/dist/lib/api-client.d.ts +14 -0
- package/dist/lib/api-client.js +59 -6
- package/dist/lib/api-error.d.ts +14 -0
- package/dist/lib/api-error.js +107 -46
- package/dist/lib/build-targets.d.ts +127 -0
- package/dist/lib/build-targets.js +348 -0
- package/dist/lib/cli-error.d.ts +13 -0
- package/dist/lib/cli-error.js +24 -0
- package/dist/lib/cli-write.d.ts +43 -9
- package/dist/lib/cli-write.js +154 -45
- package/dist/lib/config-schema.d.ts +8 -8
- package/dist/lib/db-local/db-json.d.ts +1 -1
- package/dist/lib/db-local/db-json.js +13 -5
- package/dist/lib/db-local/env.d.ts +15 -3
- package/dist/lib/db-local/env.js +37 -10
- package/dist/lib/db-pipeline/build-deps.d.ts +5 -1
- package/dist/lib/db-pipeline/build-deps.js +23 -3
- package/dist/lib/db-pipeline/types.d.ts +6 -2
- package/dist/lib/db-pipeline/types.js +0 -18
- package/dist/lib/deploy-target-contract.d.ts +152 -0
- package/dist/lib/deploy-target-contract.js +411 -0
- package/dist/lib/env-file-do-build.d.ts +19 -0
- package/dist/lib/env-file-do-build.js +44 -0
- package/dist/lib/envs.d.ts +108 -0
- package/dist/lib/envs.js +127 -0
- package/dist/lib/fs-seguro.d.ts +63 -0
- package/dist/lib/fs-seguro.js +108 -0
- package/dist/lib/inquirer-sob-demanda.d.ts +14 -0
- package/dist/lib/inquirer-sob-demanda.js +18 -0
- package/dist/lib/mobile-local/mobile-json.js +10 -4
- package/dist/lib/pickers.d.ts +44 -7
- package/dist/lib/pickers.js +65 -7
- package/dist/lib/product-docs/sync.d.ts +6 -0
- package/dist/lib/product-docs/sync.js +10 -1
- package/dist/lib/public-secret-scan.d.ts +44 -3
- package/dist/lib/public-secret-scan.js +361 -36
- package/dist/lib/render.d.ts +16 -0
- package/dist/lib/render.js +34 -4
- package/dist/lib/segredo-produto.d.ts +124 -0
- package/dist/lib/segredo-produto.js +336 -0
- package/dist/lib/texto-utf8.d.ts +96 -0
- package/dist/lib/texto-utf8.js +196 -0
- package/package.json +2 -1
- package/dist/cli-kit/format.js.map +0 -1
- package/dist/cli-kit/glyphs.js.map +0 -1
- package/dist/cli-kit/index.js.map +0 -1
- package/dist/cli-kit/palette.js.map +0 -1
- package/dist/commands/add.js.map +0 -1
- package/dist/commands/admin/iam-check.js.map +0 -1
- package/dist/commands/admin/oidc-login.js.map +0 -1
- package/dist/commands/admin/sdk-credential.js.map +0 -1
- package/dist/commands/agent-release.js.map +0 -1
- package/dist/commands/agent-write.js.map +0 -1
- package/dist/commands/ai-models.js.map +0 -1
- package/dist/commands/ai.js.map +0 -1
- package/dist/commands/api-catalog.js.map +0 -1
- package/dist/commands/archive.js.map +0 -1
- package/dist/commands/artifact-registry.js.map +0 -1
- package/dist/commands/audit.js.map +0 -1
- package/dist/commands/auth.js.map +0 -1
- package/dist/commands/autocomplete.js.map +0 -1
- package/dist/commands/billing.js.map +0 -1
- package/dist/commands/bootstrap.js.map +0 -1
- package/dist/commands/bug.js.map +0 -1
- package/dist/commands/build.js.map +0 -1
- package/dist/commands/chat-menu.js.map +0 -1
- package/dist/commands/chat.js.map +0 -1
- package/dist/commands/cloud-run.js.map +0 -1
- package/dist/commands/code.js.map +0 -1
- package/dist/commands/config.js.map +0 -1
- package/dist/commands/customers.js.map +0 -1
- package/dist/commands/db-move.js.map +0 -1
- package/dist/commands/db.js.map +0 -1
- package/dist/commands/deploy.js.map +0 -1
- package/dist/commands/deployments.js.map +0 -1
- package/dist/commands/dev-context.js.map +0 -1
- package/dist/commands/dev-up.js.map +0 -1
- package/dist/commands/dev.js.map +0 -1
- package/dist/commands/docs-produto.js.map +0 -1
- package/dist/commands/docs.js.map +0 -1
- package/dist/commands/doctor.js.map +0 -1
- package/dist/commands/domains.js.map +0 -1
- package/dist/commands/dr.js.map +0 -1
- package/dist/commands/env.js.map +0 -1
- package/dist/commands/hosting-write.js.map +0 -1
- package/dist/commands/infra-read.js.map +0 -1
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/login.js.map +0 -1
- package/dist/commands/logout.js.map +0 -1
- package/dist/commands/logs.js.map +0 -1
- package/dist/commands/mobile.js.map +0 -1
- package/dist/commands/mocks.js.map +0 -1
- package/dist/commands/new.js.map +0 -1
- package/dist/commands/open.js.map +0 -1
- package/dist/commands/ops.js.map +0 -1
- package/dist/commands/products-db.js.map +0 -1
- package/dist/commands/products.js.map +0 -1
- package/dist/commands/promote.js.map +0 -1
- package/dist/commands/publish.js.map +0 -1
- package/dist/commands/schema.js.map +0 -1
- package/dist/commands/servers.js.map +0 -1
- package/dist/commands/smoke.js.map +0 -1
- package/dist/commands/status.js.map +0 -1
- package/dist/commands/support.js.map +0 -1
- package/dist/commands/surface-status.js.map +0 -1
- package/dist/commands/tenants.js.map +0 -1
- package/dist/commands/ui.js.map +0 -1
- package/dist/commands/upgrade.js.map +0 -1
- package/dist/commands/validate.js.map +0 -1
- package/dist/commands/vm-memory.js.map +0 -1
- package/dist/commands/vm.js.map +0 -1
- package/dist/commands/webhooks.js.map +0 -1
- package/dist/commands/whoami.js.map +0 -1
- package/dist/commands/workspaces.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/lib/agent-artifact-url.js.map +0 -1
- package/dist/lib/agent-commands.js.map +0 -1
- package/dist/lib/ai/auth-discovery.js.map +0 -1
- package/dist/lib/ai/context.js.map +0 -1
- package/dist/lib/ai/orchestrator.js.map +0 -1
- package/dist/lib/api-client.js.map +0 -1
- package/dist/lib/api-error.js.map +0 -1
- package/dist/lib/auth.js.map +0 -1
- package/dist/lib/cli-read.js.map +0 -1
- package/dist/lib/cli-write.js.map +0 -1
- package/dist/lib/code-agent-budget.js.map +0 -1
- package/dist/lib/code-agent-files.js.map +0 -1
- package/dist/lib/code-agent-gateway.js.map +0 -1
- package/dist/lib/code-agent-git-tools.js.map +0 -1
- package/dist/lib/code-agent-protocol.js.map +0 -1
- package/dist/lib/code-agent-session.js.map +0 -1
- package/dist/lib/code-agent-settings.js.map +0 -1
- package/dist/lib/code-agent-skills.js.map +0 -1
- package/dist/lib/code-agent-subagents.js.map +0 -1
- package/dist/lib/code-agent-tools.js.map +0 -1
- package/dist/lib/code-agent-tui.js.map +0 -1
- package/dist/lib/code-agent-types.js.map +0 -1
- package/dist/lib/code-agent-ui.js.map +0 -1
- package/dist/lib/code-agent-web-tools.js.map +0 -1
- package/dist/lib/code-agent-write-tools.js.map +0 -1
- package/dist/lib/compose-generator.js.map +0 -1
- package/dist/lib/config-schema.js.map +0 -1
- package/dist/lib/config.js.map +0 -1
- package/dist/lib/db-local/db-json.js.map +0 -1
- package/dist/lib/db-local/env.js.map +0 -1
- package/dist/lib/db-local/fingerprint.js.map +0 -1
- package/dist/lib/db-local/index.js.map +0 -1
- package/dist/lib/db-pipeline/build-deps.js.map +0 -1
- package/dist/lib/db-pipeline/errors.js.map +0 -1
- package/dist/lib/db-pipeline/index.js.map +0 -1
- package/dist/lib/db-pipeline/pipeline.js.map +0 -1
- package/dist/lib/db-pipeline/rehearse.js.map +0 -1
- package/dist/lib/db-pipeline/types.js.map +0 -1
- package/dist/lib/dev-local-context.js.map +0 -1
- package/dist/lib/iam-preflight.js.map +0 -1
- package/dist/lib/mobile-local/index.js.map +0 -1
- package/dist/lib/mobile-local/mobile-json.js.map +0 -1
- package/dist/lib/pickers.js.map +0 -1
- package/dist/lib/product-config.js.map +0 -1
- package/dist/lib/product-docs/check.js.map +0 -1
- package/dist/lib/product-docs/config.js.map +0 -1
- package/dist/lib/product-docs/dates.js.map +0 -1
- package/dist/lib/product-docs/frontmatter.js.map +0 -1
- package/dist/lib/product-docs/git.js.map +0 -1
- package/dist/lib/product-docs/markdown.js.map +0 -1
- package/dist/lib/product-docs/ops.js.map +0 -1
- package/dist/lib/product-docs/structure.js.map +0 -1
- package/dist/lib/product-docs/sync.js.map +0 -1
- package/dist/lib/product-docs/taxonomy.js.map +0 -1
- package/dist/lib/product-docs/templates.js.map +0 -1
- package/dist/lib/public-secret-scan.js.map +0 -1
- package/dist/lib/render.js.map +0 -1
- package/dist/lib/schema-cache.js.map +0 -1
- package/dist/lib/sse.js.map +0 -1
- package/dist/lib/telemetry.js.map +0 -1
- package/dist/lib/token-resolver.js.map +0 -1
- package/dist/lib/url-allowlist.js.map +0 -1
- package/dist/lib/version-check.js.map +0 -1
- package/dist/utils/logger.js.map +0 -1
- package/dist/utils/logo.js.map +0 -1
- package/dist/utils/safe-exit.js.map +0 -1
- package/dist/utils/spinner-registry.js.map +0 -1
- package/dist/version.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -5,6 +5,9 @@ import { CLI_VERSION } from './version.js';
|
|
|
5
5
|
import { installTelemetryFlushHook, markCommandStart, markCommandEnd, } from './lib/telemetry.js';
|
|
6
6
|
import { safeExit, isSafeExitHalt } from './utils/safe-exit.js';
|
|
7
7
|
import { CliApiError } from './lib/api-client.js';
|
|
8
|
+
import { NeetruCliError } from './cli-kit/errors.js';
|
|
9
|
+
import { installCommandContext, getCurrentCommand } from './cli-kit/command-context.js';
|
|
10
|
+
import { emitCliError } from './lib/cli-error.js';
|
|
8
11
|
// UV_HANDLE_CLOSING no Windows (bug_818cf6d): o double-destroy de stdin (handler
|
|
9
12
|
// 'exit' redundante) foi removido em 2026-07-15 (ddd479f), MAS isso não bastou
|
|
10
13
|
// — a causa dominante é o `uv_async_t` do undici (fetch) ainda vivo quando o
|
|
@@ -29,6 +32,20 @@ installTelemetryFlushHook();
|
|
|
29
32
|
function handleFatalSignal(err) {
|
|
30
33
|
if (isSafeExitHalt(err))
|
|
31
34
|
return; // saída já agendada dentro do safeExit
|
|
35
|
+
// bug_89d2497e: erro tipado lançado e não tratado sai com o código DELE (e,
|
|
36
|
+
// se o comando em execução recebeu --json, como JSON) — não com o 1 genérico.
|
|
37
|
+
if (err instanceof NeetruCliError) {
|
|
38
|
+
let code = err.exit;
|
|
39
|
+
try {
|
|
40
|
+
code = emitCliError(err, Boolean(getCurrentCommand()?.opts().json));
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
/* logger indisponível — segue direto pro exit deferido */
|
|
44
|
+
}
|
|
45
|
+
process.exitCode = code;
|
|
46
|
+
setTimeout(() => process.exit(code), 300);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
32
49
|
try {
|
|
33
50
|
log.error(err instanceof Error ? (err.stack ?? err.message) : String(err));
|
|
34
51
|
// LABS #6 (2026-09-28): se o erro carrega o x-correlation-id do Core, mostra
|
|
@@ -89,6 +106,10 @@ program.hook('preAction', (_thisCmd, actionCmd) => {
|
|
|
89
106
|
process.once('exit', (code) => {
|
|
90
107
|
markCommandEnd(typeof code === 'number' ? code : 0);
|
|
91
108
|
});
|
|
109
|
+
// bug_89d2497e: guarda o comando que o Commander vai executar, para que um
|
|
110
|
+
// conselho de erro (ex.: "passe --mfa-token") só cite flag que ESSE comando
|
|
111
|
+
// declara. Ver `src/cli-kit/command-context.ts`.
|
|
112
|
+
installCommandContext(program);
|
|
92
113
|
// ── neetru ai ─────────────────────────────────────────────────────────
|
|
93
114
|
const aiCommand = program
|
|
94
115
|
.command('ai')
|
|
@@ -176,9 +197,9 @@ program
|
|
|
176
197
|
.command('new <name>')
|
|
177
198
|
.description('Criar produto, workspace, scaffold e abrir painel')
|
|
178
199
|
.option('--product-slug <slug>', 'slug do produto (default: derivado de <name>)')
|
|
179
|
-
.option('--env <env>', 'dev (
|
|
200
|
+
.option('--env <env>', 'dev-local (padrão) | staging | prod')
|
|
180
201
|
.option('--tier <tier>', 'standard (default) | dev | enterprise', 'standard')
|
|
181
|
-
.option('--customer <customerId>', 'customerId dono do workspace (
|
|
202
|
+
.option('--customer <customerId>', 'customerId dono do workspace (OBRIGATÓRIO se o produto não for Neetru-owned)')
|
|
182
203
|
.option('--skip-init', 'pula o scaffold local')
|
|
183
204
|
.option('--open', 'abre o browser após criar o workspace (opt-in)')
|
|
184
205
|
.option('--skip-open', '(obsoleto — browser não abre por default; use --open para abrir)')
|
|
@@ -403,11 +424,15 @@ program
|
|
|
403
424
|
.command('build')
|
|
404
425
|
.description('Construir tarball do produto pronto para deploy')
|
|
405
426
|
.option('--product <slug>', 'override do slug (default: lê neetru.config.json)')
|
|
427
|
+
// bug_678ddc98: monorepo — aponta pro diretório do app sem precisar de `cd`.
|
|
428
|
+
// `--cwd` é alias porque é o nome que a maioria das ferramentas usa.
|
|
429
|
+
.option('--app <dir>', 'diretório do app a construir (monorepo: ex. apps/web). O artefato continua saindo no diretório atual')
|
|
430
|
+
.option('--cwd <dir>', 'alias de --app')
|
|
406
431
|
.option('--stack <stack>', 'node | docker | php-apache | static (default: detectado)')
|
|
407
432
|
.option('--output <dir>', 'diretório de saída', '.neetru-build')
|
|
408
433
|
.option('--version <version>', 'versão (default: detectada de package.json ou timestamp)')
|
|
409
434
|
.option('--no-cache', 'invalida caches do build (recria lockfile, --no-cache no docker)')
|
|
410
|
-
.option('--no-install', 'pula
|
|
435
|
+
.option('--no-install', 'pula o install (assume node_modules pronto — útil quando `next dev` ativo no Windows)')
|
|
411
436
|
.option('--env-file <path>', 'arquivo .env com vars de ambiente a injetar no build (ex: NEXT_PUBLIC_API_URL)')
|
|
412
437
|
.option('--target-env <env>', 'dev | workspace | prod — força NEXT_PUBLIC_NEETRU_ENV no bundle (default: prod; ' +
|
|
413
438
|
'neutraliza .env.local vencendo .env.production — incidente 2026-07-22)', 'prod')
|
|
@@ -416,6 +441,9 @@ program
|
|
|
416
441
|
const { runBuild } = await import('./commands/build.js');
|
|
417
442
|
await runBuild({
|
|
418
443
|
product: opts.product,
|
|
444
|
+
// bug_678ddc98: `--app` vence `--cwd` quando os dois vêm juntos (o
|
|
445
|
+
// canônico ganha do alias, em vez de escolher em silêncio).
|
|
446
|
+
app: opts.app ?? opts.cwd,
|
|
419
447
|
stack: opts.stack,
|
|
420
448
|
output: opts.output,
|
|
421
449
|
version: opts.version,
|
|
@@ -602,7 +630,7 @@ tenantsCmd
|
|
|
602
630
|
.option('--slug <slug>', 'slug do tenant')
|
|
603
631
|
.option('--customer <customerId>', 'id do customer')
|
|
604
632
|
.option('--product <productId>', 'id do produto (interativo se ausente)')
|
|
605
|
-
.option('--env <env>', 'dev | staging | prod (default: dev)')
|
|
633
|
+
.option('--env <env>', 'dev-local | staging | prod (default: dev-local)')
|
|
606
634
|
.option('--domain <domain>', 'domínio primário')
|
|
607
635
|
.option('--json', 'saída em JSON')
|
|
608
636
|
.action(async (opts) => {
|
|
@@ -624,7 +652,7 @@ tenantsCmd
|
|
|
624
652
|
.option('--slug <slug>', 'slug do tenant')
|
|
625
653
|
.option('--customer <customerId>', 'id do customer')
|
|
626
654
|
.option('--product <productId>', 'id do produto (interativo se ausente)')
|
|
627
|
-
.option('--env <env>', 'dev | staging | prod (default: dev)')
|
|
655
|
+
.option('--env <env>', 'dev-local | staging | prod (default: dev-local)')
|
|
628
656
|
.option('--domain <domain>', 'domínio primário')
|
|
629
657
|
.option('--json', 'saída em JSON')
|
|
630
658
|
.action(async (id, opts) => {
|
|
@@ -901,8 +929,8 @@ workspacesCmd
|
|
|
901
929
|
.command('create')
|
|
902
930
|
.description('Criar workspace (retorna OAuth secret one-time)')
|
|
903
931
|
.option('--product <productId>', 'id do produto (interativo se ausente)')
|
|
904
|
-
.option('--customer <customerId>', 'id do customer (
|
|
905
|
-
.option('--env <env>', 'dev | staging | prod (default: dev)')
|
|
932
|
+
.option('--customer <customerId>', 'id do customer (omitir só em produto Neetru-owned)')
|
|
933
|
+
.option('--env <env>', 'dev-local | staging | prod (default: dev-local)')
|
|
906
934
|
.option('--tier <tier>', 'dev | standard | enterprise (default: standard)')
|
|
907
935
|
.option('--name <name>', 'label do workspace')
|
|
908
936
|
.option('--bind-config', 'grava tenantId retornado no neetru.config.json do cwd')
|
|
@@ -939,7 +967,7 @@ workspacesCmd
|
|
|
939
967
|
.command('list')
|
|
940
968
|
.description('Listar workspaces')
|
|
941
969
|
.option('--product <productId>', 'filtra por productId')
|
|
942
|
-
.option('--env <env>', 'dev | staging | prod')
|
|
970
|
+
.option('--env <env>', 'dev-local | staging | prod')
|
|
943
971
|
.option('--status <status>', 'active | paused | provisioning | …')
|
|
944
972
|
.option('--json', 'saída em JSON')
|
|
945
973
|
.action(async (opts) => {
|
|
@@ -976,7 +1004,7 @@ deploymentsCmd
|
|
|
976
1004
|
.option('--product <productId>', 'id do produto (interativo se ausente)')
|
|
977
1005
|
.option('--tenant <tenantId>', 'id do tenant alvo (interativo se ausente)')
|
|
978
1006
|
.option('--version <version>', 'versao a deployar')
|
|
979
|
-
.option('--env <env>', 'dev | staging | prod (default: dev)')
|
|
1007
|
+
.option('--env <env>', 'dev-local | staging | prod (default: dev-local)')
|
|
980
1008
|
.option('--server <serverId>', 'id do server alvo (interativo se ausente)')
|
|
981
1009
|
.option('--json', 'saída em JSON')
|
|
982
1010
|
.action(async (opts) => {
|
|
@@ -1209,7 +1237,10 @@ productsCmd
|
|
|
1209
1237
|
.option('--description <text>', 'descrição (opcional)')
|
|
1210
1238
|
.option('--owner-team <team>', 'time dono (default: neetru-eng)')
|
|
1211
1239
|
.option('--plan-template <tier>', 'free | starter | pro (default: free)')
|
|
1212
|
-
|
|
1240
|
+
// bug bug_db50b21f84e8400f9c3f6286b69c353b: o Core descarta `environments` no
|
|
1241
|
+
// POST. A flag segue aceita pra não quebrar script existente, mas o texto
|
|
1242
|
+
// precisa dizer a verdade — antes prometia uma escolha que não acontecia.
|
|
1243
|
+
.option('--env <csv>', 'IGNORADO hoje: o Core provisiona staging e prod sozinho')
|
|
1213
1244
|
.option('--yes', 'pula prompts (exige --name; resto usa defaults)')
|
|
1214
1245
|
.option('--json', 'saída em JSON (machine-readable)')
|
|
1215
1246
|
.action(async (opts) => {
|
|
@@ -1389,10 +1420,15 @@ adminDatabaseCmd
|
|
|
1389
1420
|
.command('delete [id]')
|
|
1390
1421
|
.description('[Staff] Arquivar banco por soft-delete')
|
|
1391
1422
|
.option('--force', 'Requerido para DBs em status=active (passa force=true ao Core)')
|
|
1423
|
+
// bug_757b9746: a rota exige step-up MFA e o hint de erro já mandava usar
|
|
1424
|
+
// `--mfa-token`, mas o subcomando não declarava a flag. Sem sessão de 1h
|
|
1425
|
+
// ativa (`neetru auth mfa-session`), em --json/não-interativo este era o
|
|
1426
|
+
// único caminho possível — e ele não existia.
|
|
1427
|
+
.option('--mfa-token <codigo>', 'Código TOTP para o step-up MFA (dispensado com `neetru auth mfa-session`)')
|
|
1392
1428
|
.option('--json')
|
|
1393
1429
|
.action(async (id, opts) => {
|
|
1394
1430
|
const { runDbDelete } = await import('./commands/products-db.js');
|
|
1395
|
-
await runDbDelete(id, { json: !!opts.json, force: !!opts.force });
|
|
1431
|
+
await runDbDelete(id, { json: !!opts.json, force: !!opts.force, mfaToken: opts.mfaToken });
|
|
1396
1432
|
});
|
|
1397
1433
|
// ── neetru admin sdk-credential ────────────────────────────────────────────
|
|
1398
1434
|
// Gerencia credenciais OIDC de MÁQUINA (M2M / client_credentials) por produto —
|
|
@@ -1613,10 +1649,14 @@ productsDbCmd
|
|
|
1613
1649
|
.command('set-status [id] [status]')
|
|
1614
1650
|
.description('[RENOMEADO — use: neetru admin database set-status]')
|
|
1615
1651
|
.option('--reason <text>')
|
|
1652
|
+
// A7 (2026-10-13): a rota exige step-up MFA com o banco active/degraded; o
|
|
1653
|
+
// alias depreciado não declarava a flag e, em --json/CI sem sessão de 1h,
|
|
1654
|
+
// não havia como mandar o código (o `admin database set-status` já tinha).
|
|
1655
|
+
.option('--mfa-token <code>', 'código TOTP para step-up MFA (exigido se o banco está active/degraded)')
|
|
1616
1656
|
.option('--json')
|
|
1617
1657
|
.action(async (id, status, opts) => {
|
|
1618
1658
|
const { runDbSetStatus } = await import('./commands/products-db.js');
|
|
1619
|
-
await runDbSetStatus(id, status, { reason: opts.reason, json: !!opts.json });
|
|
1659
|
+
await runDbSetStatus(id, status, { reason: opts.reason, mfaToken: opts.mfaToken, json: !!opts.json });
|
|
1620
1660
|
});
|
|
1621
1661
|
productsDbCmd
|
|
1622
1662
|
.command('retry [id]')
|
|
@@ -1638,10 +1678,14 @@ productsDbCmd
|
|
|
1638
1678
|
productsDbCmd
|
|
1639
1679
|
.command('delete [id]')
|
|
1640
1680
|
.description('[RENOMEADO — use: neetru admin database delete]')
|
|
1681
|
+
// bug_757b9746: o alias roteia pro MESMO runDbDelete, que agora faz step-up
|
|
1682
|
+
// MFA. Sem a flag aqui o alias reproduziria o bug inteiro (401 com instrução
|
|
1683
|
+
// de usar uma flag inexistente).
|
|
1684
|
+
.option('--mfa-token <codigo>', 'Código TOTP para o step-up MFA (dispensado com `neetru auth mfa-session`)')
|
|
1641
1685
|
.option('--json')
|
|
1642
1686
|
.action(async (id, opts) => {
|
|
1643
1687
|
const { runDbDelete } = await import('./commands/products-db.js');
|
|
1644
|
-
await runDbDelete(id, { json: !!opts.json });
|
|
1688
|
+
await runDbDelete(id, { json: !!opts.json, mfaToken: opts.mfaToken });
|
|
1645
1689
|
});
|
|
1646
1690
|
// ── neetru logs ───────────────────────────────────────────────────────
|
|
1647
1691
|
program
|
|
@@ -2052,7 +2096,7 @@ dbMigrationsCmd
|
|
|
2052
2096
|
.description('Confirmar migração destrutiva (POST /api/cli/v1/db/migrations/[id]/confirm — exige MFA)')
|
|
2053
2097
|
// Era `requiredOption('--mfa')` — o commander rejeitava ANTES de rodar, matando
|
|
2054
2098
|
// o caminho de sessão MFA de 1h (`neetru auth mfa-session`) que o resolveStepUp
|
|
2055
|
-
// promete honrar. Agora `option`: resolveStepUp faz o fail-closed (
|
|
2099
|
+
// promete honrar. Agora `option`: resolveStepUp faz o fail-closed (--mfa-token → sessão
|
|
2056
2100
|
// → erro acionável). Flag canônica `--mfa-token`; `--mfa` fica como alias oculto. (auditoria UX 2026-09)
|
|
2057
2101
|
.option('--mfa-token <token>', 'código TOTP step-up MFA (ou use `neetru auth mfa-session`)')
|
|
2058
2102
|
.addOption(new Option('--mfa <token>').hideHelp())
|
|
@@ -2941,6 +2985,7 @@ webhooksCmd
|
|
|
2941
2985
|
.requiredOption('--product-id <id>', 'ID do produto')
|
|
2942
2986
|
.option('--yes', 'pula a confirmação interativa (necessário em --json/CI)')
|
|
2943
2987
|
.option('--force', 'alias de --yes')
|
|
2988
|
+
.option('--mfa-token <code>', 'código TOTP para step-up MFA (ou use `neetru auth mfa-session`)')
|
|
2944
2989
|
.option('--json', 'saída em JSON')
|
|
2945
2990
|
.action(async (id, opts) => {
|
|
2946
2991
|
const { runWebhooksUnregister } = await import('./commands/webhooks.js');
|
|
@@ -2949,6 +2994,7 @@ webhooksCmd
|
|
|
2949
2994
|
productId: opts.productId,
|
|
2950
2995
|
yes: !!opts.yes,
|
|
2951
2996
|
force: !!opts.force,
|
|
2997
|
+
mfaToken: opts.mfaToken,
|
|
2952
2998
|
json: !!opts.json,
|
|
2953
2999
|
});
|
|
2954
3000
|
});
|
|
@@ -3017,6 +3063,16 @@ domainsCmd
|
|
|
3017
3063
|
const { runDomainsRetry } = await import('./commands/domains.js');
|
|
3018
3064
|
await runDomainsRetry({ product: opts.product, sub: opts.sub, json: !!opts.json });
|
|
3019
3065
|
});
|
|
3066
|
+
// ── neetru secrets ──────────────────────────────────────────────────
|
|
3067
|
+
// Segredos PRÓPRIOS do produto (bug_8b85353b, Parte 3): registrar/trocar
|
|
3068
|
+
// (`set`, inclusive `--generate`), listar só nomes (`list`) e arquivar
|
|
3069
|
+
// (`archive`). O valor vai direto pro Secret Manager e nunca entra pelo argv.
|
|
3070
|
+
// Contrato: core#632 (GET/POST/DELETE /api/cli/v1/products/{slug}/secrets).
|
|
3071
|
+
// @top-level: secrets
|
|
3072
|
+
{
|
|
3073
|
+
const { registerSecretsCommand } = await import('./commands/secrets.js');
|
|
3074
|
+
registerSecretsCommand(program);
|
|
3075
|
+
}
|
|
3020
3076
|
// ── neetru mobile ───────────────────────────────────────────────────
|
|
3021
3077
|
// Devs/IAs registram/listam/revogam Apps Mobile (iOS/Android) do produto
|
|
3022
3078
|
// atual (lido de neetru.config.json no cwd). Dev-facing, self-service —
|
package/dist/lib/api-client.d.ts
CHANGED
|
@@ -50,6 +50,20 @@ interface StreamOptions {
|
|
|
50
50
|
export declare function assertTrustedApiHost(rawUrl: string): void;
|
|
51
51
|
/** @deprecated Nome legado — agora é fail-closed (throw), não só warning. Use `assertTrustedApiHost`. */
|
|
52
52
|
export declare const maybeWarnUntrustedHost: typeof assertTrustedApiHost;
|
|
53
|
+
/**
|
|
54
|
+
* URL absoluta de um path da API do Core: MESMA base (`neetruApiUrl`) e MESMA
|
|
55
|
+
* guarda fail-closed de `apiRequest`/`apiStream`. Lança `CliNetworkError` se a
|
|
56
|
+
* base estiver fora da allowlist — antes de qualquer Bearer sair da máquina.
|
|
57
|
+
*
|
|
58
|
+
* Todo `fetch` que leve o token da CLI e não passe por `apiRequest`/`apiStream`
|
|
59
|
+
* (ex.: o SSE de `neetru logs -f`, que precisa ler o corpo aos poucos) monta a
|
|
60
|
+
* URL por aqui. C7 (2026-10-13): o stream do `logs -f` lia uma chave
|
|
61
|
+
* `apiBase` (que nem existe no schema do config) e fazia `fetch` direto, sem a
|
|
62
|
+
* guarda — com `"apiBase":"https://coletor.malicioso"` no `config.json`, o
|
|
63
|
+
* segundo request ia pra lá com `Bearer nrt_…`, reabrindo o ataque que o
|
|
64
|
+
* LABS #4 fechou aqui; e o `neetruApiUrl` de staging/local era ignorado.
|
|
65
|
+
*/
|
|
66
|
+
export declare function resolveTrustedApiUrl(path: string): string;
|
|
53
67
|
/** @deprecated Sem estado agora (fail-closed não tem flag one-shot). No-op mantido p/ compat de teste. */
|
|
54
68
|
export declare function _resetUntrustedHostWarningForTests(): void;
|
|
55
69
|
export declare function apiRequest<T>(path: string, opts?: RequestOptions): Promise<T>;
|
package/dist/lib/api-client.js
CHANGED
|
@@ -69,16 +69,32 @@ export function assertTrustedApiHost(rawUrl) {
|
|
|
69
69
|
}
|
|
70
70
|
/** @deprecated Nome legado — agora é fail-closed (throw), não só warning. Use `assertTrustedApiHost`. */
|
|
71
71
|
export const maybeWarnUntrustedHost = assertTrustedApiHost;
|
|
72
|
+
/**
|
|
73
|
+
* URL absoluta de um path da API do Core: MESMA base (`neetruApiUrl`) e MESMA
|
|
74
|
+
* guarda fail-closed de `apiRequest`/`apiStream`. Lança `CliNetworkError` se a
|
|
75
|
+
* base estiver fora da allowlist — antes de qualquer Bearer sair da máquina.
|
|
76
|
+
*
|
|
77
|
+
* Todo `fetch` que leve o token da CLI e não passe por `apiRequest`/`apiStream`
|
|
78
|
+
* (ex.: o SSE de `neetru logs -f`, que precisa ler o corpo aos poucos) monta a
|
|
79
|
+
* URL por aqui. C7 (2026-10-13): o stream do `logs -f` lia uma chave
|
|
80
|
+
* `apiBase` (que nem existe no schema do config) e fazia `fetch` direto, sem a
|
|
81
|
+
* guarda — com `"apiBase":"https://coletor.malicioso"` no `config.json`, o
|
|
82
|
+
* segundo request ia pra lá com `Bearer nrt_…`, reabrindo o ataque que o
|
|
83
|
+
* LABS #4 fechou aqui; e o `neetruApiUrl` de staging/local era ignorado.
|
|
84
|
+
*/
|
|
85
|
+
export function resolveTrustedApiUrl(path) {
|
|
86
|
+
const baseUrl = buildBaseUrl();
|
|
87
|
+
assertTrustedApiHost(baseUrl);
|
|
88
|
+
return `${baseUrl}${path.startsWith('/') ? path : `/${path}`}`;
|
|
89
|
+
}
|
|
72
90
|
/** @deprecated Sem estado agora (fail-closed não tem flag one-shot). No-op mantido p/ compat de teste. */
|
|
73
91
|
export function _resetUntrustedHostWarningForTests() {
|
|
74
92
|
/* no-op */
|
|
75
93
|
}
|
|
76
94
|
export async function apiRequest(path, opts = {}) {
|
|
77
|
-
const baseUrl = buildBaseUrl();
|
|
78
|
-
const url = `${baseUrl}${path.startsWith('/') ? path : `/${path}`}`;
|
|
79
95
|
// LABS #4 (2026-09-28): fail-closed — bloqueia host fora da allowlist ANTES
|
|
80
96
|
// de anexar o Bearer (era só warning que mandava o token assim mesmo).
|
|
81
|
-
|
|
97
|
+
const url = resolveTrustedApiUrl(path);
|
|
82
98
|
// MASTER-HIGH-048 (2.11.0): prefere auth.json (canonico) sobre Conf (legacy).
|
|
83
99
|
const token = opts.token ?? getActiveTokenSync();
|
|
84
100
|
const headers = {
|
|
@@ -185,6 +201,45 @@ export async function apiRequest(path, opts = {}) {
|
|
|
185
201
|
else if (typeof parsed === 'string' && parsed.trim() !== '') {
|
|
186
202
|
message = parsed;
|
|
187
203
|
}
|
|
204
|
+
else if (errBody && typeof errBody === 'object') {
|
|
205
|
+
// bug_a3c4d7a8 / achado 2026-10-12: o Core tem TRÊS formatos de erro e
|
|
206
|
+
// este ramo cobre o terceiro, que antes caía no chão inteiro.
|
|
207
|
+
//
|
|
208
|
+
// 1. `{ error: { code, message } }` — canônico (`cliJsonError`)
|
|
209
|
+
// 2. `{ error: "x", error_description }` — RFC 6749 (OAuth)
|
|
210
|
+
// 3. `{ ok: false, code, reason, ... }` — rotas que respondem direto
|
|
211
|
+
//
|
|
212
|
+
// O formato 3 NÃO tem a chave `error`, então o `if (errBody?.error)` acima
|
|
213
|
+
// era pulado e tudo virava `Erro HTTP <status>`. Medido no campo: o
|
|
214
|
+
// `GET /servers/{id}/logs?source=startup` devolve 410 com `code`, um
|
|
215
|
+
// `reason` de dez linhas e quatro `alternatives` acionáveis — e o operador
|
|
216
|
+
// lia apenas "Requisição rejeitada (410): Erro HTTP 410". Pior: o
|
|
217
|
+
// `servers get` RECOMENDA esse comando justamente quando o agente morreu,
|
|
218
|
+
// então a mensagem inútil chega no pior momento possível.
|
|
219
|
+
const cru = errBody;
|
|
220
|
+
if (typeof cru.code === 'string' && cru.code)
|
|
221
|
+
code = cru.code;
|
|
222
|
+
// `reason` primeiro: é o campo que as rotas do formato 3 usam pro texto
|
|
223
|
+
// longo. `message`/`detail`/`hint` cobrem as variações que já existem.
|
|
224
|
+
for (const chave of ['reason', 'message', 'detail', 'hint']) {
|
|
225
|
+
const v = cru[chave];
|
|
226
|
+
if (typeof v === 'string' && v.trim() !== '') {
|
|
227
|
+
message = v;
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// O que fazer EM VEZ disso é a parte que salva o operador; sem anexar
|
|
232
|
+
// aqui, ela morre no servidor. Só strings — nada de despejar objeto na
|
|
233
|
+
// cara de quem lê.
|
|
234
|
+
const alt = cru.alternatives;
|
|
235
|
+
if (alt && typeof alt === 'object' && !Array.isArray(alt)) {
|
|
236
|
+
const linhas = Object.entries(alt)
|
|
237
|
+
.filter(([, v]) => typeof v === 'string' && v.trim() !== '')
|
|
238
|
+
.map(([k, v]) => ` ${k}: ${v}`);
|
|
239
|
+
if (linhas.length > 0)
|
|
240
|
+
message = `${message}\n\nAlternativas:\n${linhas.join('\n')}`;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
188
243
|
// LABS #6 (2026-09-28): captura o x-correlation-id do Core p/ o rodapé do erro.
|
|
189
244
|
const correlationId = response.headers.get('x-correlation-id') ?? undefined;
|
|
190
245
|
throw new CliApiError(response.status, code, message, correlationId, parseRetryAfter(response.headers.get('retry-after')));
|
|
@@ -200,10 +255,8 @@ export async function apiRequest(path, opts = {}) {
|
|
|
200
255
|
* `opts.signal` (Ctrl-C). Erros HTTP antes do stream abrir viram `CliApiError`.
|
|
201
256
|
*/
|
|
202
257
|
export async function* apiStream(path, opts = {}) {
|
|
203
|
-
const baseUrl = buildBaseUrl();
|
|
204
|
-
const url = `${baseUrl}${path.startsWith('/') ? path : `/${path}`}`;
|
|
205
258
|
// LABS #4 (2026-09-28): fail-closed também no stream.
|
|
206
|
-
|
|
259
|
+
const url = resolveTrustedApiUrl(path);
|
|
207
260
|
// MASTER-HIGH-048 (2.11.0): mesmo helper aplica ao stream NDJSON.
|
|
208
261
|
const token = opts.token ?? getActiveTokenSync();
|
|
209
262
|
const headers = {
|
package/dist/lib/api-error.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { NeetruCliError } from '../cli-kit/errors.js';
|
|
1
2
|
/** Exit codes canônicos, exportados pra teste/documentação. */
|
|
2
3
|
export declare const API_ERROR_EXIT: {
|
|
3
4
|
readonly client: 1;
|
|
@@ -5,4 +6,17 @@ export declare const API_ERROR_EXIT: {
|
|
|
5
6
|
readonly forbidden: 3;
|
|
6
7
|
readonly server: 4;
|
|
7
8
|
};
|
|
9
|
+
/**
|
|
10
|
+
* O que fazer diante de `401 mfa_required`. bug_757b9746 nasceu aqui: o texto
|
|
11
|
+
* mandava passar `--mfa-token` a qualquer comando, inclusive aos que não
|
|
12
|
+
* declaravam a flag. Agora a flag só é citada se o comando em execução a
|
|
13
|
+
* declara; senão, a alternativa que existe sempre — a sessão de 1h.
|
|
14
|
+
*/
|
|
15
|
+
export declare function mfaRequiredAdvice(): string;
|
|
16
|
+
/**
|
|
17
|
+
* Traduz qualquer erro para o `NeetruCliError` canônico — sem imprimir nem
|
|
18
|
+
* encerrar. Exportado para teste e para quem precise agregar erros de API
|
|
19
|
+
* como filhos de uma falha parcial.
|
|
20
|
+
*/
|
|
21
|
+
export declare function cliErrorFromApiError(error: unknown): NeetruCliError;
|
|
8
22
|
export declare function handleApiError(error: unknown, json?: boolean): never;
|
package/dist/lib/api-error.js
CHANGED
|
@@ -16,23 +16,29 @@
|
|
|
16
16
|
*
|
|
17
17
|
* Imprime a mensagem adequada (texto humano ou JSON com `--json`) e encerra o
|
|
18
18
|
* processo — nunca retorna (`never`).
|
|
19
|
+
*
|
|
20
|
+
* bug_89d2497e (2026-10-17): o erro de API vira um `NeetruCliError` (cli-kit)
|
|
21
|
+
* com o exit code da tabela acima e o erro original em `cause`; quem imprime e
|
|
22
|
+
* encerra é `exitWithCliError`. O formato do JSON e os códigos de saída são os
|
|
23
|
+
* mesmos de antes. Um `NeetruCliError` que chegue aqui (ex.: lançado dentro do
|
|
24
|
+
* `try` de um comando) sai com o código DELE, sem ser reclassificado.
|
|
19
25
|
*/
|
|
20
26
|
import { CliApiError, CliNetworkError } from './api-client.js';
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
else
|
|
34
|
-
log.error(text);
|
|
27
|
+
import { EXIT_CODES, NeetruCliError } from '../cli-kit/errors.js';
|
|
28
|
+
import { adviseCommand, adviseFlag } from '../cli-kit/command-context.js';
|
|
29
|
+
import { exitWithCliError } from './cli-error.js';
|
|
30
|
+
/**
|
|
31
|
+
* Reconhece o `SafeExitHalt` de `utils/safe-exit.ts` pela marca, sem importar
|
|
32
|
+
* `isSafeExitHalt`: dezenas de testes mockam `safe-exit.js` só com `safeExit`,
|
|
33
|
+
* e ler outra exportação de um mock incompleto lança no vitest.
|
|
34
|
+
*/
|
|
35
|
+
function isSafeExitHalt(x) {
|
|
36
|
+
return (typeof x === 'object' &&
|
|
37
|
+
x !== null &&
|
|
38
|
+
x.__neetruSafeExit === true);
|
|
35
39
|
}
|
|
40
|
+
/** Exit codes canônicos, exportados pra teste/documentação. */
|
|
41
|
+
export const API_ERROR_EXIT = EXIT_CODES;
|
|
36
42
|
/**
|
|
37
43
|
* A mensagem do 401 vale ser exibida? O Core legado mandava só
|
|
38
44
|
* `{ error: 'Unauthorized' }` (o parser vira message='Unauthorized') — aí o
|
|
@@ -45,7 +51,30 @@ function isInformative401Message(message) {
|
|
|
45
51
|
return false;
|
|
46
52
|
return !/^(unauthori[sz]ed|erro http 401)$/i.test(m);
|
|
47
53
|
}
|
|
48
|
-
|
|
54
|
+
/**
|
|
55
|
+
* O que fazer diante de `401 mfa_required`. bug_757b9746 nasceu aqui: o texto
|
|
56
|
+
* mandava passar `--mfa-token` a qualquer comando, inclusive aos que não
|
|
57
|
+
* declaravam a flag. Agora a flag só é citada se o comando em execução a
|
|
58
|
+
* declara; senão, a alternativa que existe sempre — a sessão de 1h.
|
|
59
|
+
*/
|
|
60
|
+
export function mfaRequiredAdvice() {
|
|
61
|
+
// P105: o comando alternativo também é validado — contra a árvore do
|
|
62
|
+
// Commander. Sem ele (ou sem árvore conhecida), a dica não o cita.
|
|
63
|
+
const sessao = (withCommand, withoutCommand) => adviseCommand({ command: 'auth mfa-session', withCommand, withoutCommand });
|
|
64
|
+
return adviseFlag({
|
|
65
|
+
flag: '--mfa-token',
|
|
66
|
+
withFlag: sessao(' Passe --mfa-token <código TOTP> ou rode `neetru auth mfa-session` (sessão de 1h).', ' Passe --mfa-token <código TOTP>.'),
|
|
67
|
+
withoutFlag: sessao(' Rode `neetru auth mfa-session` (sessão de 1h) e repita o comando.', ' Este comando exige step-up MFA e esta CLI não tem, nele, como enviá-lo.'),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Traduz qualquer erro para o `NeetruCliError` canônico — sem imprimir nem
|
|
72
|
+
* encerrar. Exportado para teste e para quem precise agregar erros de API
|
|
73
|
+
* como filhos de uma falha parcial.
|
|
74
|
+
*/
|
|
75
|
+
export function cliErrorFromApiError(error) {
|
|
76
|
+
if (error instanceof NeetruCliError)
|
|
77
|
+
return error;
|
|
49
78
|
if (error instanceof CliApiError) {
|
|
50
79
|
const s = error.status;
|
|
51
80
|
if (s === 401) {
|
|
@@ -54,48 +83,80 @@ export function handleApiError(error, json) {
|
|
|
54
83
|
// Antes TODO 401 virava "Token inválido ou revogado" — inclusive o de MFA
|
|
55
84
|
// faltando, mandando o operador fazer `neetru login` à toa.
|
|
56
85
|
if (error.code === 'mfa_required') {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
86
|
+
return new NeetruCliError(error.message, {
|
|
87
|
+
code: 'mfa_required',
|
|
88
|
+
exit: EXIT_CODES.unauthenticated,
|
|
89
|
+
cause: error,
|
|
90
|
+
details: { code: error.code, status: s },
|
|
91
|
+
human: `Step-up MFA exigido: ${error.message}\n${mfaRequiredAdvice()}`,
|
|
92
|
+
});
|
|
61
93
|
}
|
|
62
94
|
const serverMsg = isInformative401Message(error.message) ? error.message : '';
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
:
|
|
72
|
-
|
|
95
|
+
return new NeetruCliError(error.message, {
|
|
96
|
+
code: 'unauthorized',
|
|
97
|
+
exit: EXIT_CODES.unauthenticated,
|
|
98
|
+
cause: error,
|
|
99
|
+
details: {
|
|
100
|
+
...(error.code && error.code !== 'http_401' ? { code: error.code } : {}),
|
|
101
|
+
status: s,
|
|
102
|
+
},
|
|
103
|
+
human: serverMsg
|
|
104
|
+
? `Não autenticado: ${serverMsg}${/neetru login/.test(serverMsg) ? '' : ' — execute: neetru login'}`
|
|
105
|
+
: 'Token inválido ou revogado. Execute: neetru login',
|
|
106
|
+
});
|
|
73
107
|
}
|
|
74
108
|
if (s === 403) {
|
|
75
|
-
|
|
76
|
-
|
|
109
|
+
return new NeetruCliError(error.message, {
|
|
110
|
+
code: 'forbidden',
|
|
111
|
+
exit: EXIT_CODES.forbidden,
|
|
112
|
+
cause: error,
|
|
113
|
+
details: { status: s },
|
|
114
|
+
human: `Acesso negado: ${error.message}`,
|
|
115
|
+
});
|
|
77
116
|
}
|
|
78
117
|
if (s >= 400 && s < 500) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
:
|
|
87
|
-
|
|
118
|
+
// Valores válidos de um enum recusado pelo Core (ex.: `environmentId
|
|
119
|
+
// inválido — use: dev-local | staging | prod.`) vêm no `message` do
|
|
120
|
+
// próprio Core, que é a fonte que validou: o texto passa inteiro, sem
|
|
121
|
+
// corte e sem lista copiada do lado de cá.
|
|
122
|
+
return new NeetruCliError(error.message, {
|
|
123
|
+
code: s === 404 ? 'not_found' : (error.code ?? 'client_error'),
|
|
124
|
+
exit: EXIT_CODES.client,
|
|
125
|
+
cause: error,
|
|
126
|
+
details: { status: s },
|
|
127
|
+
human: s === 404
|
|
128
|
+
? error.message || 'Recurso não encontrado.'
|
|
129
|
+
: `Requisição rejeitada (${s}): ${error.message}`,
|
|
130
|
+
});
|
|
88
131
|
}
|
|
89
132
|
// 5xx (incl. 501 — não implementado).
|
|
90
|
-
|
|
91
|
-
|
|
133
|
+
return new NeetruCliError(error.message, {
|
|
134
|
+
code: error.code ?? 'server_error',
|
|
135
|
+
exit: EXIT_CODES.server,
|
|
136
|
+
cause: error,
|
|
137
|
+
details: { status: s },
|
|
138
|
+
human: `Erro do servidor (${s}): ${error.message}`,
|
|
139
|
+
});
|
|
92
140
|
}
|
|
93
141
|
if (error instanceof CliNetworkError) {
|
|
94
|
-
|
|
95
|
-
|
|
142
|
+
return new NeetruCliError(error.message, {
|
|
143
|
+
code: 'network',
|
|
144
|
+
exit: EXIT_CODES.server,
|
|
145
|
+
cause: error,
|
|
146
|
+
human: `Falha de rede: ${error.message}`,
|
|
147
|
+
});
|
|
96
148
|
}
|
|
97
149
|
const message = error instanceof Error ? error.message : String(error);
|
|
98
|
-
|
|
99
|
-
|
|
150
|
+
return new NeetruCliError(message, { code: 'unknown', exit: EXIT_CODES.server, cause: error });
|
|
151
|
+
}
|
|
152
|
+
export function handleApiError(error, json) {
|
|
153
|
+
// `safeExit` lança `SafeExitHalt` em produção para parar o caller. Dentro de
|
|
154
|
+
// `try { …; safeExit(n) } catch (e) { handleApiError(e) }` o sinal caía aqui
|
|
155
|
+
// e virava um segundo erro "[object Object]" — e, em `--json`, uma SEGUNDA
|
|
156
|
+
// linha de JSON no stdout. A saída já está agendada com o código certo:
|
|
157
|
+
// o sinal só segue adiante.
|
|
158
|
+
if (isSafeExitHalt(error))
|
|
159
|
+
throw error;
|
|
160
|
+
return exitWithCliError(cliErrorFromApiError(error), json);
|
|
100
161
|
}
|
|
101
162
|
//# sourceMappingURL=api-error.js.map
|