@neetru/cli 2.29.0 → 2.32.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/CHANGELOG.md +141 -0
- package/README.md +63 -17
- 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 +114 -2
- 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 +17 -24
- package/dist/commands/deploy.d.ts +136 -2
- package/dist/commands/deploy.js +547 -69
- package/dist/commands/deployments.js +11 -6
- package/dist/commands/dev-context.js +15 -5
- package/dist/commands/dev-up.d.ts +7 -5
- package/dist/commands/dev-up.js +24 -23
- package/dist/commands/docs-produto.d.ts +90 -0
- package/dist/commands/docs-produto.js +384 -0
- 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.d.ts +2 -0
- package/dist/commands/init.js +14 -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 +31 -1
- package/dist/commands/new.js +94 -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 +84 -16
- 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 +6 -4
- 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 +142 -20
- package/dist/lib/api-client.d.ts +22 -1
- package/dist/lib/api-client.js +79 -8
- 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/compose-generator.d.ts +13 -9
- package/dist/lib/compose-generator.js +28 -60
- package/dist/lib/config-schema.d.ts +36 -2
- package/dist/lib/config-schema.js +11 -0
- 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/mobile-local/mobile-json.js +10 -4
- package/dist/lib/pickers.d.ts +44 -7
- package/dist/lib/pickers.js +60 -4
- package/dist/lib/product-docs/check.d.ts +62 -0
- package/dist/lib/product-docs/check.js +489 -0
- package/dist/lib/product-docs/config.d.ts +66 -0
- package/dist/lib/product-docs/config.js +234 -0
- package/dist/lib/product-docs/dates.d.ts +17 -0
- package/dist/lib/product-docs/dates.js +41 -0
- package/dist/lib/product-docs/frontmatter.d.ts +43 -0
- package/dist/lib/product-docs/frontmatter.js +302 -0
- package/dist/lib/product-docs/git.d.ts +16 -0
- package/dist/lib/product-docs/git.js +92 -0
- package/dist/lib/product-docs/markdown.d.ts +27 -0
- package/dist/lib/product-docs/markdown.js +98 -0
- package/dist/lib/product-docs/ops.d.ts +133 -0
- package/dist/lib/product-docs/ops.js +975 -0
- package/dist/lib/product-docs/structure.d.ts +61 -0
- package/dist/lib/product-docs/structure.js +239 -0
- package/dist/lib/product-docs/sync.d.ts +107 -0
- package/dist/lib/product-docs/sync.js +322 -0
- package/dist/lib/product-docs/taxonomy.d.ts +46 -0
- package/dist/lib/product-docs/taxonomy.js +163 -0
- package/dist/lib/product-docs/templates.d.ts +53 -0
- package/dist/lib/product-docs/templates.js +245 -0
- package/dist/lib/public-secret-scan.d.ts +117 -0
- package/dist/lib/public-secret-scan.js +771 -0
- 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.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/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/templates/dev/Dockerfile.agent +0 -39
- package/templates/dev/agent-entrypoint.sh +0 -41
- package/templates/dev/docker-compose.dev.yml +0 -63
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,147 @@ Convenção: [Keep a Changelog](https://keepachangelog.com/) + [SemVer](https://
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [2.32.0] — 2026-10-18 (`neetru secrets`, erro tipado e conselho que só cita o que existe)
|
|
8
|
+
|
|
9
|
+
> **Por que MINOR.** Entra comando novo (`neetru secrets set|list|archive`) e muda saída documentada: o `docs sync` com falha parcial passa a sair com o código do doc mais grave (antes era 1 fixo) e o `docs.sync.status` do JSON do `neetru deploy` de produção passa de `parcial` para `falha` quando nada deu certo. A 2.31.1 já tinha seção fechada aqui (11/10) e nunca foi publicada; publicar este código como 2.31.1 repetiria a colisão de número que a própria 2.31.1 corrigiu.
|
|
10
|
+
|
|
11
|
+
### Adicionado
|
|
12
|
+
|
|
13
|
+
- **`neetru secrets set|list|archive` — segredo do PRÓPRIO produto, com o valor indo direto pro Secret Manager** (bug_8b85353b, Parte 3 de 3; a Parte 1 é o core#632). Até aqui não havia comando nenhum para isso: `neetru env set` é só Cloud Run, e o Core recusa (com razão) segredo em `envVars` do deploy VM, porque o documento do deploy é lido por toda a equipe no Firestore. Agora:
|
|
14
|
+
- `neetru secrets set CHAVE --env <dev-local|staging|prod>` registra ou troca o valor. **O valor nunca vem da linha de comando** (fica no histórico do shell e aparece para outros processos): entra por `--stdin`, `--from-file <caminho>` (uma quebra de linha no fim é ignorada) ou por um pedido na tela **sem eco**, digitado duas vezes. Com `--generate [--bytes 16..128]` o **Core sorteia** o valor e ninguém o vê — o caminho certo para `NEETRU_WEBHOOK_SECRET`, já que quem assina o webhook é o próprio Core. Valor colado na linha de comando (posicional, `--value=…`, `CHAVE=VALOR`) é recusado **sem ser repetido na tela** — o erro padrão do commander ecoaria o segredo. O valor é validado antes de sair da máquina com a mesma regra do Core e do agente (sem quebra de linha, aspas, barra invertida ou espaço nas pontas; até 4096 bytes). `--env` é obrigatório: segredo no ambiente errado não dá erro, o app só não o encontra.
|
|
15
|
+
- `neetru secrets list [--env] [--include-archived]` mostra só nomes e dados de controle (origem, situação, revisão, quem e quando); a CLI ainda filtra a resposta para só os campos públicos saírem na tela ou no `--json`.
|
|
16
|
+
- `neetru secrets archive CHAVE --env <ambiente>` arquiva (soft delete, com confirmação; `--yes` em CI). O app deixa de recebê-lo no próximo deploy.
|
|
17
|
+
- Produção pede step-up MFA na escrita (`--mfa-token` ou `neetru auth mfa-session`), resolvido **antes** de o valor ser enviado; sessão de 1h recusada pelo Core leva a um pedido de TOTP fresco, uma vez. Um `--mfa-token` recusado não é repetido.
|
|
18
|
+
- Chave que o Core reserva (porque ele mesmo já a entrega ao app: `NEETRU_OIDC_CLIENT_SECRET`, `NEETRU_SDK_CLIENT_SECRET`, `NEETRU_OAUTH_CLIENT_SECRET`, a família `NEETRU_MODULE_*`, `PORT`, `LD_PRELOAD`…) é recusada **antes** de a CLI pedir o valor, com o motivo. O login também é conferido antes do pedido: sem sessão, o dev não digita o segredo à toa.
|
|
19
|
+
- `--from-file` e `--stdin` tiram **uma** marca BOM do começo (Bloco de Notas "UTF-8 com BOM", `Set-Content -Encoding utf8` do PowerShell 5.1) e o `--from-file` lê no máximo 64 KiB + 1 byte, nunca o arquivo inteiro. Arquivo não encontrado não repete o caminho, porque o engano provável é colar o valor no lugar dele. Opção ou argumento desconhecido em `secrets`, `list` e `archive` também é recusado sem ser repetido — e por isso sem a sugestão de nome parecido do commander (`neetru secrets lsit` não sugere mais `list`), porque o texto dela repete o que foi digitado.
|
|
20
|
+
- O valor é lido **só em UTF-8**. Arquivo ou entrada em ANSI (Windows-1252, o que o `Set-Content` do Windows PowerShell 5.1 grava sem `-Encoding`) ou em UTF-16 (o `>` e o `Out-File`) é recusado com o erro `invalid_encoding` e o jeito de converter, sem repetir o conteúdo. Antes, cada byte que não era UTF-8 virava um caractere de substituição em silêncio, e o Secret Manager guardava um segredo diferente do arquivo. O pedido na tela recusa do mesmo jeito quando o terminal não manda UTF-8. O jeito de converter vem com uma receita por versão do PowerShell, porque o `Get-Content` lê diferente nas duas: no Windows PowerShell 5.1, `Get-Content <arquivo> | Set-Content -Encoding utf8 <arquivo-novo>` (grava com a marca BOM, que a CLI tira); no PowerShell 7.4 ou mais novo, `Get-Content -Encoding ansi <arquivo> | Set-Content -Encoding utf8 <arquivo-novo>`. A receita do 5.1 rodada no 7 grava a letra já trocada por U+FFFD, em UTF-8 válido — e por isso o valor que traz U+FFFD também é recusado, venha de arquivo ou de `--stdin`. **Limites declarados:** um U+FFFD gravado de propósito também é recusado (a CLI fica mais estrita que o Core neste caractere); e o pipe do Windows PowerShell 5.1 troca letra acentuada por `?` antes de os bytes chegarem à CLI, e `?` é um caractere legítimo; a ajuda do `set` avisa e manda usar `--from-file`.
|
|
21
|
+
- **Limite declarado:** o lado do agente da VM (Parte 2 de bug_8b85353b) ainda não foi liberado em 2026-10-16. O registro funciona, mas o segredo só chega ao app no primeiro deploy depois da atualização do agente — e a CLI diz isso ao gravar, com a data.
|
|
22
|
+
- **Camada de correção no `src/cli-kit`: erro tipado, conselho validado contra o próprio comando e valores válidos de enum** (bug_89d2497e). Até aqui o cli-kit só tinha apresentação (glifos, paleta, formatadores) e cada comando montava a própria mensagem e escolhia como morrer. Três peças novas:
|
|
23
|
+
- `NeetruCliError` (`cli-kit/errors.ts`), no molde do `FirebaseError` do firebase-tools: código de máquina, **código de saída próprio**, causa original em `cause` e **filhos agregados**. Com filhos e sucessos, a falha se reporta como **parcial** ("Falha parcial: 2 de 4 itens falharam"; no `--json`, `partial`, `succeeded`, `failed`, `children`) e sai com o código do filho mais grave, salvo `exit` explícito. `exitWithCliError` (`lib/cli-error.ts`) imprime (JSON no stdout com `--json`, texto no stderr sem ele) e sai com o código do erro; o trap global do `index.ts` faz o mesmo com um `NeetruCliError` lançado e não tratado (antes, todo erro não tratado saía com 1).
|
|
24
|
+
- `installCommandContext(program)` guarda, num hook `preAction`, o comando que o Commander vai executar, e `adviseFlag` só cita uma flag se **esse** comando a declara — opção declarada só num ancestral não conta, porque o `.action()` do filho nunca a lê. Sem comando conhecido (chamada fora do Commander), a flag não é citada.
|
|
25
|
+
- `invalidChoiceError` lista os valores válidos a partir do **mesmo array que validou** e, no `--json`, acrescenta `flag`, `value` e `valid`.
|
|
26
|
+
- **Conselho validado em todo ponto, verbo de subcomando nunca vira valor e listas de ambiente de uma fonte só** (P105, o resto do bug_89d2497e). No `src/cli-kit`:
|
|
27
|
+
- `command-context.ts` passa a validar também o argumento posicional (`commandDeclaresInput`, `declaredInputs`), a escolher entre várias flags (`declaredFlags`) e o **comando** que uma dica manda rodar (`commandExists`, `adviseCommand`), contra a árvore de verdade do Commander. `installCommandContext` também guarda a raiz, então a validação vale no menu interativo, que não passa pelo `preAction`. Sem árvore conhecida, o comando não é citado.
|
|
28
|
+
- `suggest.ts` (novo) porta o `suggest_commands` do gcloud: `SYNONYMS` (verbo digitado → verbo que a CLI usa), a pontuação `_WordScore` (só pontua quando uma palavra contém a outra, em começo de palavra) e `MIN_RATIO = 0.7`. Sem sugestão que passe o piso, a CLI lista as escolhas em vez de chutar. `isSubcommandVerb` (a palavra é nome, apelido ou sinônimo de um subcomando DE QUEM PERGUNTA — não há lista global de verbos) e `reservedVerbError`.
|
|
29
|
+
- Guardas estáticas: `guarda-flag-citada-existe` passou a seguir classes de erro próprias (`NeetruCliError` e herdeiras, `DocsNewError`, `RehearseDockerUnavailableError`…), wrappers que recebem o texto como parâmetro (inclusive importados de outro arquivo e com o texto transformado numa `const` antes do sink) e, por lista explícita, os que recebem o texto num objeto (`invalidChoiceError.hint`, `adviseFlag`, `adviseCommand`, `reservedVerbError.escapeHint`); confere que o texto "sem" de um `adviseFlag`/`adviseCommand` não cita o que não existe (inclusive quando o texto chega por parâmetro de uma função local, como o `sessao()` dos dois sites de MFA, resolvido pelos argumentos de cada chamada); e confere as citações `neetru <comando> [<sub>…]` contra a árvore de comandos derivada dos `.command()`, com este alcance: anda enquanto as palavras forem subcomando e, na primeira que não é, (a) **entre crases/aspas**, reprova se é a 1ª palavra, se o nível é um grupo puro ou se ela parece erro de digitação de um subcomando do nível (distância de edição 1, ou 2 a partir de 8 letras: `neetru products creat`, `neetru deplo`, `neetru auth mfa-sesion`); (b) **fora de crases/aspas, só citações com hífen são conferidas**: uma palavra com hífen reprova se parece erro de digitação de um subcomando com hífen do nível (`neetru auth mfa-sesion`, `neetru servers pgbouncer sync-userlst`). **Limite declarado:** erro de digitação sem hífen fora de crases não é pego, de nenhum tamanho (`neetru cusotmers list`, `neetru products creat`, `neetru deplo` passam), nem comando que não existe; cite comandos entre crases para ter a conferência completa. A regra é fechada porque separar prosa PT-BR de erro de digitação por distância de edição não convergiu (as tentativas reprovavam "O neetru deve…", "Use neetru tenants lista…" e conjugações como `executa`, `confirma`, `provisiona`, `suspende`). Um corpus de prosa com essas frases e com prosa hifenizada passa com zero reprovação. A varredura do repositório roda uma vez num `beforeAll` com timeout próprio. Nova `guarda-lista-de-ambiente-fonte-unica`: fora das fontes únicas, nenhum array ou união de tipos com dois ou mais nomes de ambiente.
|
|
30
|
+
|
|
31
|
+
### Mudou
|
|
32
|
+
|
|
33
|
+
- **`neetru deploy` recusa localmente alvo × runtime incompatível, antes de qualquer build ou upload** (bug_a8375858, #74). Ex.: `--target workspace --artifact <tarball>` num produto `runtime: nextjs` subia ~35 MB e só então o Core devolvia 413 "Falha ao resolver a source do deploy" — mensagem que manda caçar tamanho quando o problema é o tipo do alvo. Uma tabela (`src/lib/deploy-target-contract.ts`) declara o que cada alvo recebe, recusa (erro) e descarta (aviso).
|
|
34
|
+
- **O pacote publicado deixa de levar source maps** (bug_2cb0b6c3, #84): 455 → 307 arquivos, 148 → 0 `.map`, 3,68 → 2,32 MB (−37%; medido no branch do #84 — o pacote da 2.32.0 tem 317 arquivos, nenhum `.map` e 2,52 MB descompactados). O `sourceMap` do `tsconfig` não mudou: quem roda do repo clonado continua com os mapas ao lado do `src/`.
|
|
35
|
+
|
|
36
|
+
### Corrigido
|
|
37
|
+
|
|
38
|
+
- **Três consertos que tinham parado no meio** (#71). O entrypoint de monorepo que o `neetru build` calcula passa a ir no deploy quando o standalone está aninhado (bug_678ddc98). `neetru admin database create` não oferece mais `dev` como primeira opção e padrão. O crash do WSL ao abrir o navegador (ENOENT assíncrono do `spawn`) foi consertado também em `neetru open`, `docs open`, `workspaces open` e `new --open`, não só no login (bug_d414f21c).
|
|
39
|
+
- **Três lugares onde a CLI apontava pro lado errado** (#72): o terceiro formato de erro do Core não se perde mais; o `doctor` não reprova com exit 1 o valor que o próprio Core escreveu; o `upgrade` não manda quem está à frente voltar atrás.
|
|
40
|
+
- **A 5ª e a 6ª cópia do `--env`, o destaque de produção e os avisos do Core** (#73): `workspaces list --env dev` não devolve mais zero em silêncio; `promote --from dev` não é mais aceito aqui para ser recusado com 400 no Core; `--env production` passa a mostrar o destaque "← PRODUÇÃO" (desde que nasceu, o destaque só comparava com `prod`); o `warnings[]` que o Core devolve passa a chegar ao usuário.
|
|
41
|
+
- **`neetru bug list` não corta mais o id** (bug_a4f84c1b, #75): o corte em 24 caracteres com `…` fazia o `bug show` do id copiado responder "não encontrado" para um bug que existe.
|
|
42
|
+
- **`chat identity show` lê em vez de gravar "show", e `chat send --as` não renomeia mais a credencial calado** (bug_09574d4b, #76); `send --as --json` não imprime mais duas linhas JSON no stdout.
|
|
43
|
+
- **"use --yes (ou --force)" não chega mais a comando que não declara `--force`** (P105). O `confirmDestructive` tinha o texto fixo, e 8 comandos declaram `--yes` sem `--force` (medido na árvore do Commander: `servers pgbouncer sync-userlist`, `products memory set`, `vm memory set`, `db move start|abort|decommission`, `promote execute`, `mobile revoke`); quem seguia com `--force` tomava "unknown option". Agora o texto cita só as flags de confirmação que o comando em execução declara; sem nenhuma (ou no menu), não cita flag. Código de saída (1) e formato do JSON (`confirmation_required`) inalterados; o `message` muda só nesses casos.
|
|
44
|
+
- **O picker não pede mais uma flag que o comando não tem** (P105). Em `--json` (e com vários candidatos sem terminal), `resolveOrPick` dizia "`--tenant` e obrigatorio" com a flag padrão do picker. Medido: ela não existia em 10 comandos — `tenants get|update|suspend|reactivate` recebem `[id]`, `servers deactivate|dispatch` recebem `[serverId]`, `products publish|unpublish` recebem `[slug]`, e `admin database create`/`products db create` usam `--product-id`/`--server-id`. Agora cada chamada diz como o comando recebe o valor e o texto cita só o que o comando declara (ex.: `<id> e obrigatorio em modo --json.`); sem nada declarado, diz que em `--json` não há como perguntar. Exit 1 e JSON `missing_input` inalterados.
|
|
45
|
+
- **A dica de MFA e a de "nenhum produto" só citam comando que existe** (P105). `neetru auth mfa-session` (no `handleApiError` e no `resolveStepUp`) e `neetru products create` (nos pickers de produto) passam por `adviseCommand`. Na CLI de hoje os dois existem, então nada muda na tela.
|
|
46
|
+
- **`docs sync` com falha parcial se reporta como parcial, com o código de saída certo** (P105). As falhas de envio viram filhos de um `NeetruCliError`: no texto, "Falha parcial: 1 de 3 itens falharam" e uma linha por doc; no `--json`, o resultado de antes mais `error` (`docs_sync_parcial`/`docs_sync_falha`), `partial`, `succeeded`, `failed` e `children` (com `slug` e `httpStatus`). Cada item de `falhas` ganhou `httpStatus` (0 = sem resposta). **Mudanças de saída, documentadas:** o código de saída deixa de ser 1 fixo e passa a ser o do doc mais grave, pela mesma tabela do `handleApiError` (401→2, 403→3, outro 4xx→1, 5xx e sem resposta→4); quando **nenhum** doc deu certo (nem enviado nem já igual no Core) o `status` passa de `parcial` para `falha`, porque não era parcial — e isso vale também para o `docs.sync.status` do JSON do `neetru deploy` de produção (o `deployDocsPostSync`), que lê o mesmo status; e uma falha de envio faz o comando falhar MESMO quando o lote termina num status informativo: se o doc 1 toma 500 e o doc 2 toma 404/405/501 ("Core sem suporte"), a falha do doc 1 aparece e o exit é o dela, na saída humana e no `--json` igualmente (`ok:false`). A regra "Core sem suporte sai 0" da 2.31.0 continua valendo quando nenhum doc falhou antes. A saída humana e o `--json` saem sempre com o MESMO código e o mesmo veredito (`desfechoDoSync`).
|
|
47
|
+
- **`neetru chat identity` não grava mais verbo como apelido, e `chat send`/`chat channels create` não postam nem criam o nome de um subcomando irmão** (P105; o bug_09574d4b só tinha fechado `show`/`set`). `identity describe` gravava "describe", `identity list` gravava "list", `identity show Fulano` gravava "show Fulano" e `identity set clear` gravava "clear"; `chat send history` postava "history" no canal global e `chat channels create close` criava o canal "close". Agora a CLI recusa (exit 1, `uso_invalido`) e sugere o comando certo (`neetru chat identity show`, `neetru chat history`) — ou, sem sugestão que passe o piso, lista os subcomandos reais. No JSON, `value`, `suggestions` e `valid`. A recusa vale só para o que é de fato verbo de subcomando de quem pergunta (nome, apelido ou sinônimo): em `chat send`, resposta curta comum em PT-BR passa (`show`, `ok`, `top`, `beleza`, `valeu`, `sim`, `blz`, `list`), e em `channels create` só `create`/`close` são recusados. Os caminhos citados na recusa saem do nó do Commander em execução, não de texto fixo. A frase que só começa com uma palavra de comando continua valendo (`identity set "Show de Bola"`), e a palavra sozinha vai pela flag explícita (`chat send --mensagem history`). Um apelido que É verbo de identity (`Set`, `Ver`) só se grava pelo `chat send --as`, e a recusa de `identity set Set` ensina esse caminho. `identity`, `identity show|get|clear|set "Nome"` e o posicional livre seguem iguais; não há recusa por parecença (`Seth` continua apelido).
|
|
48
|
+
- **As listas de ambiente saem de uma fonte só** (P105). `deploy.ts` tinha `new Set(['dev','staging','prod'])`, `build.ts` repetia `'dev' | 'workspace' | 'prod'` seis vezes, e `env.ts`, `doctor.ts`, `products.ts`, `db-json.ts`, `db-pipeline/types.ts` e `db.ts` tinham as suas. Agora tudo deriva de `TENANT_ENVIRONMENT_IDS`/`NEETRU_ENVS`/`SYNC_ENVS` ou da tabela `VOCABULARIOS_DE_AMBIENTE` (`lib/envs.ts`), que diz, para cada ambiente do Core, o nome no `--env` do deploy (`dev`, que o Core aceita como apelido de `dev-local`) e no `NEETRU_ENV` do SDK (`workspace` para `staging`). Os valores aceitos não mudaram. **Mudanças de saída, documentadas:** o `--env` inválido do `neetru deploy` (workspace/cloud-run) passa pelo erro tipado: o texto vira `Ambiente inválido: "x". Valores válidos: dev | staging | prod.` e o JSON `invalid_env` ganha `flag`, `value` e `valid` (exit 1 inalterado); e o detalhe do `neetru doctor` para `NEETRU_ENV` inválido lista os mesmos valores em outra ordem.
|
|
49
|
+
- **`neetru init --yes` não carrega mais o `inquirer`, e o teste do scaffold deixa de estourar o timeout com a máquina carregada** (P112). O `init.ts` importava o `inquirer` no topo, mas só o caminho interativo pergunta; agora ele é carregado ali, na hora. Medido: o import a frio custava ~0,35 s sozinho e ~1,1 s com a suíte rodando, em todo `init --yes` (o `neetru new` segue carregando-o pelo `new.ts`, que o importa no topo; não ficou mais rápido). No `init.test.ts`, o 1º teste carregava a frio, dentro do corpo, o `inquirer` e o grafo do `docs-produto.js` (~85% do tempo dele; o scaffold em si leva ~0,2 s) e estourou os 10 s duas vezes sob carga. O carregamento foi para um `beforeAll` com timeout próprio, como nos testes irmãos de scaffold que pagavam o mesmo custo (`onboarding-polish`, `mobile-commands`, `new-db-step`, `new-macro-env-customer-gate`). Na suíte completa, o 1º teste passou de ~2,0 s para 0,2–1,2 s; sozinho, de ~0,9 s para ~0,1 s. Sem mudança de comportamento.
|
|
50
|
+
|
|
51
|
+
- **O erro de MFA não manda mais passar `--mfa-token` a comando que não declara a flag** (bug_89d2497e; foi o bug_757b9746 no dia em que nasceu). O `handleApiError` (`401 mfa_required`) e o fail-closed do `resolveStepUp` em `--json`/não-interativo diziam "passe --mfa-token" a qualquer comando. Agora citam a flag só se o comando em execução a declara; senão, dizem para rodar `neetru auth mfa-session` e repetir o comando. Códigos de saída inalterados (2 no `handleApiError`, 1 no `resolveStepUp`) e mesmo formato de JSON; só o texto do `message` do `resolveStepUp` muda quando o comando não declara a flag.
|
|
52
|
+
- **`--env` inválido lista os valores da fonte que valida, não uma lista copiada à mão.** Nos comandos de banco (`db init`, `db apply`, `db exec-sql`, `admin database create`), a frase "Use dev-local, staging ou production." estava escrita à mão em quatro lugares e a do `normalizeEnv` numa quinta; agora todas saem de `NEETRU_ENVS` (`db-local/env.ts`), com os apelidos lidos do mesmo mapa que os aceita, e o prompt de ambiente do `admin database create` oferece a mesma lista. O `neetru dev sync --env` validava contra três literais e listava outros três escritos à parte; agora as duas coisas saem de `SYNC_ENVS`. O `--env` de tenant/workspace/deployment já usava `TENANT_ENVIRONMENT_IDS` e passou pelo mesmo erro tipado. **Mudança de saída, documentada:** o JSON de `invalid_env`/`invalid_environment` ganhou `flag`, `value` e `valid` (campos novos; os de antes ficam); o texto passou a ser `Ambiente inválido: "x". Valores válidos: a | b | c.`; e `db init --json`, `dev sync --json` e o filtro `workspaces list --env --json`, que escreviam só texto no stderr, passam a escrever o JSON do erro no stdout. Código de saída segue 1. Enum recusado pelo Core (400) continua chegando com o texto do Core inteiro, que já traz a lista dele.
|
|
53
|
+
- **`handleApiError` não imprime mais um segundo erro "[object Object]" depois de um `safeExit` dentro de `try`.** Em produção o `safeExit` lança um sinal para parar o comando; em `try { …; safeExit(n) } catch (e) { handleApiError(e) }` o sinal caía no `handleApiError` e virava erro "desconhecido" — e, com `--json`, uma segunda linha de JSON no stdout. Agora o sinal passa adiante e a saída agendada (com o código certo) acontece sozinha.
|
|
54
|
+
|
|
55
|
+
- **`neetru deploy --target vm --env-file` não manda mais o dev para um caminho que não existia.** Ao descartar uma chave com cara de segredo, o aviso dizia "Use o Secret Manager e referencie por nome na VM" — mecanismo que nunca existiu para segredo do produto (4ª cópia do padrão que o core#632 corrigiu nas outras três). Agora o aviso traz o comando pronto, com a chave e o ambiente do deploy: `neetru secrets set STRIPE_SECRET_KEY --env prod`, e diz uma vez, depois da lista, que o agente ainda não entrega esses segredos ao app. O comando só é sugerido quando o Core o aceitaria: para chave que o Core reserva (`NEETRU_OIDC_CLIENT_SECRET`, `NEETRU_MODULE_*`…) o aviso diz que o Core já a entrega ao app; para nome fora de CAIXA_ALTA, diz que é preciso renomear. O aviso do `--target cloud-run` não mudou.
|
|
56
|
+
- **O `--env-file` do `neetru deploy` e do `neetru build` não troca mais letra acentuada em silêncio.** Os dois liam o arquivo com `readFile(…, 'utf8')`, e um `.env` em ANSI (o `Set-Content` do Windows PowerShell 5.1 sem `-Encoding`) com `NOME_LOJA=Café` chegava ao deploy e ao build como `Caf\uFFFD`, sem aviso; um `.env` em UTF-16 (o `>`) no build parava com erro sem dizer o motivo. Agora os dois usam a mesma leitura estrita do `neetru secrets set` (`src/lib/texto-utf8.ts`): ANSI, UTF-16 e texto que traz U+FFFD são recusados com `invalid_encoding`, a receita de conversão de cada versão do PowerShell e sem repetir o conteúdo, antes de o POST do deploy sair e antes de o build rodar. UTF-8 com ou sem a marca BOM segue igual.
|
|
57
|
+
- No `neetru deploy`, o `--env-file` agora é lido logo no começo do comando: antes do catálogo e de qualquer pergunta (produto, schema divergente, alvo, servidor), de qualquer chamada de rede, do build e do upload. No `--target vm` ele era lido depois do artefato: sem manifest, quem recusava era o build interno (no stderr, e o `deploy --json` ficava sem a sua linha JSON); com o manifest em cache, o pacote subia ao GCS antes da recusa. No `--target cloud-run` era lido depois de a imagem ir ao Cloud Build. Agora a recusa sai como `{"ok":false,"error":"invalid_encoding",…}` sem nenhuma chamada ao Core. Com `--target workspace` o arquivo segue sem ser lido, porque o workspace o descarta (com aviso).
|
|
58
|
+
- **`neetru env switch` não estraga mais o `.env.local` que não está em UTF-8.** O comando lia o arquivo com `readFile(…, 'utf-8')` e regravava tudo: um `.env.local` em ANSI com `NOME_LOJA=Café` terminava gravado com o caractere de substituição (U+FFFD) no lugar do "é", e um em UTF-16 virava texto ilegível — com exit 0 e "✓" nos dois. Agora ANSI e UTF-16 são recusados com a receita de conversão, exit 1 e o arquivo intacto. UTF-8 com a marca BOM volta ao disco com a marca e com as letras. Um U+FFFD que já estava no arquivo segue (a regravação não o muda).
|
|
59
|
+
- **A varredura de segredo do `neetru deploy` passa a achar o valor como o bundler o grava: com letra fora do ASCII, com barra invertida, aspas, `$`, crase ou caractere de controle, lido do `.env` pela regra do dotenv e depois de expandir `$VAR`.** O pacote era lido em latin1 (1 byte = 1 caractere) e o valor da env pública era comparado como texto, então valor com acento nunca batia — nem com o `.env` em UTF-8 nem em ANSI. E o bundler nem sempre grava a letra em UTF-8. Num `next build` real (Next 15.5.27), o SWC grava `á` como `\xe1` e o emoji como o par `\uD83D\uDE00`, mas deixa `€` e `ł` crus, misturando as formas dentro do mesmo valor. O esbuild no padrão grava `\xE1`, `\u20AC` e `\u{1F600}`, e o Vite grava os bytes UTF-8. Agora a busca aceita, letra a letra, os bytes UTF-8, `\xNN` (de U+0080 a U+00FF), `\uNNNN`, o par de `\uNNNN` acima do plano básico e `\u{…}`, com o hex em qualquer caixa. Para valor só ASCII nada muda. No build Next medido, a varredura achava 1 de 5 valores (só o ASCII); agora acha os 5. O mesmo vale para caractere ASCII que o minificador escapa: a barra invertida sai como `\\` (os dois), as aspas como `\"` e `\'` (o SWC, quando o valor tem as duas), `</` como `<\/` (os dois), a quebra de linha como `\n` no SWC e crua dentro de crase no esbuild, o retorno como `\r`, e os demais caracteres de controle como `\b`, `\f`, `\v`, `\0` ou `\xNN`; a tabulação sai crua. O esbuild (0.21.5 do Vite 5.4, e 0.28) escolhe a crase quando o valor tem as duas aspas repetidas, e então grava `\${` e a crase escapada (medido num `vite build` real). A busca aceita essas formas, letra a letra, e o valor só com letras ASCII comuns continua no `includes`. Dentro de JS e JSON a barra invertida só é procurada escapada, porque ali uma barra crua sempre abre um escape: aceitar as duas formas fazia cada barra do valor casar uma ou duas barras do pacote, o tempo crescia 1,7 vez por barra (24 barras, 1,6 s) e um valor com duas barras casava o JS de uma. Agora 64 barras levam menos de 1 ms; em `.txt`, `.html` e afins, a barra crua continua achada. E o `.env*` do projeto passa a ser lido com a regra do dotenv que o `@next/env` 15.5 e o Vite 5.4 embutem: o parser de antes levava o comentário de `CHAVE=valor # nota`, não tirava as crases, não trocava `\n` e `\r` entre aspas duplas e cortava o valor de várias linhas. Num segundo `next build`, com 11 valores desses no `.env`, a varredura achava 8 de 16 (o de várias linhas nem era coletado); agora acha os 16. Por último, o Next e o Vite expandem `$VAR`, `${VAR}` e `${VAR:-padrão}` depois de ler o `.env` (também entre aspas simples, e com a variável do ambiente do build, como num CI), e `\$` vira `$`: é o valor expandido que vai ao navegador, e a varredura procurava o cru. Os dois embutem versões diferentes do dotenv-expand, com resultados diferentes (no Vite, `$A-fim` perde o `-fim`; no Next, `${A:-x}` com o `A` só no mesmo arquivo dá `x`), e a varredura agora procura os dois resultados, copiados de cada um, além do valor cru. Num `next build` real com 16 valores desses e num `vite build` real com 14, a varredura achava 1 de 16 e 2 de 14; agora acha todos. O `--env-file` do deploy entra como o ambiente do build, que é como o `neetru build` o injeta: lido com a regra da CLI, vence o `.env` e não é expandido. Os `.env*` do projeto continuam lidos como o Next e o Vite os leem (em UTF-8, trocando byte inválido por U+FFFD), porque é esse valor que vai ao JS do navegador. Um `.env*` fora de UTF-8 limpo não para a varredura, mas o deploy avisa o nome do arquivo, que o app vai receber outro valor e como converter (no stderr em `--json`).
|
|
60
|
+
- **A conferência opt-in da superfície MFA contra o Core (`NEETRU_CORE_DIR`) dava vermelho falso.** `rotasStepUpDoCore` fatiava o texto de cada handler até o próximo e não via o `requireCliStepUpMfa(request…)` chamado por função local do arquivo — o caso da rota de segredos (`requireStepUpMfaInProd`). Agora segue as funções locais (inclusive função que chama função) com o parser do TypeScript; o espelho bate exatamente com a origin/main do Core. A derivação (`metodosComStepUp`) também tem teste unitário com as fontes escritas no teste, que roda no gate sem `NEETRU_CORE_DIR`.
|
|
61
|
+
|
|
62
|
+
- **Arquivo temporário e diretório temporário passam a sumir de verdade quando o caminho tem acento (home `C:\Users\João`).** No Node 24 do Windows (página ANSI 1252), `fs.rmSync` com caminho não-ASCII volta **sem erro** e não apaga nada — arquivo (com ou sem `force`, NFC ou NFD, `日本`) e diretório recursivo, mesmo com o acento só num diretório acima — e, com um `ação.txt` ao lado, `rmSync('ação.txt')` apaga o arquivo **errado**. Na CLI isso deixava `.tmp-*` no `.neetru/` do projeto quando a escrita atômica do `db.json`/`mobile.json` falhava, e um `neetru-drizzle-*` no `%TEMP%` a cada `generateSql`. Agora tudo passa por `src/lib/fs-seguro.ts`: `removerArquivo` (unlinkSync) e `removerDiretorio` (fs.promises.rm) apagam e **conferem** que o caminho sumiu, e lançam se não sumiu. As 98 limpezas de teste e o setup `home-isolada` (que deixava um `neetru-cli-test-home-*` por arquivo de teste) também. Guarda nova (`regressions/fs-sync-acento.regression.test.ts`) proíbe `rmSync`, `rmdirSync` e `cpSync` no repo inteiro — chamada, import, apelido e acesso computado.
|
|
63
|
+
- **`neetru logs -f` não manda mais o token para host fora da lista de confiáveis** (C7). O stream SSE montava a URL com `config.get('apiBase')` — chave que nem existe no schema do config — e fazia `fetch` direto, sem a guarda `assertTrustedApiHost`. Com `"apiBase":"https://coletor.malicioso"` no `config.json`, o primeiro request (via `apiRequest`, guardado) ia para o Core e o segundo — o stream — ia para o coletor com `Authorization: Bearer nrt_…`: o mesmo ataque que o LABS #4 fechou só dentro de `api-client.ts`. De quebra, o `neetruApiUrl` de staging/local era ignorado no stream. Agora `api-client.ts` exporta `resolveTrustedApiUrl(path)` — a resolução de base e a guarda fail-closed que `apiRequest`/`apiStream` já usavam, num lugar só — e o stream monta a URL por ela. Varredura dos outros `fetch` diretos da CLI: os que levam o Bearer (`ai/orchestrator.ts`, `code-agent-gateway.ts`) já passavam pela guarda; os demais (upload para URL assinada do GCS, health-check do domínio do produto, telemetria, `version-check`, ferramentas web do `neetru code`) não anexam o token.
|
|
64
|
+
- **`neetru webhooks unregister` volta a funcionar — estava quebrado desde 2026-08-20** (A7). O Core passou a exigir step-up MFA no `DELETE /api/cli/v1/webhooks/{id}` (commit 7d37208d) e o comando seguiu mandando o request sem `X-Neetru-MFA-Token`/`X-Neetru-MFA-Session` — 401 `mfa_required` sempre, com ou sem `neetru auth mfa-session` — e o subcomando nem declarava `--mfa-token`. Agora passa pelo mesmo `resolveStepUp` dos outros destrutivos (`--mfa-token` → sessão de 1h → prompt TOTP, fail-closed em `--json`/não-TTY). Na mesma varredura: o alias depreciado `neetru products db set-status` não declarava `--mfa-token` (o `admin database set-status` declarava), então em `--json`/CI sem sessão não havia como mandar o código quando o banco está active/degraded.
|
|
65
|
+
- **O teste de contrato da superfície MFA deixou de depender de memória.** A lista à mão de `cli-mfa-destructive-surface.test.ts` deixou `webhooks unregister` de fora (e, antes, `admin database delete`, bug_757b9746). Entraram duas guardas estáticas (`src/__tests__/helpers/superficie-mfa.ts`): (1) toda chamada `apiRequest`/`apiStream` do código-fonte é cruzada com o espelho das rotas do Core que chamam `requireCliStepUpMfa` — chamada a rota dessas sem os cabeçalhos do step-up quebra o teste; (2) todo subcomando cuja `.action` chama função com step-up tem de declarar `--mfa-token`. O espelho também é vigiado: rota com comando sem nenhuma chamada, ou rota marcada "sem comando" que passou a ser chamada, falha; e `NEETRU_CORE_DIR=<clone do Core>` liga a conferência contra o `git grep` do próprio Core. Rotas com step-up no Core que não têm comando na CLI hoje: `functions move|redeploy|revert`, `modules install|uninstall|rotate-credentials`, `servers/fleet/migrate` e o `DELETE tenants/{id}` (arquivar). (`products/{slug}/secrets` estava nesta lista e ganhou o `neetru secrets` neste mesmo lote.)
|
|
66
|
+
- **`--mfa-token` explícito volta a valer mesmo com sessão MFA salva, e `neetru login` com outra conta descarta a sessão da conta anterior** (A8). `resolveStepUp` olhava o `mfa-session.json` antes do código e devolvia só `X-Neetru-MFA-Session`, jogando fora o `--mfa-token` em silêncio — enquanto `withStepUpRetry` fazia o contrário. E o `login` trocava o Bearer sem apagar a sessão MFA, que o Core só aceita do mesmo uid do Bearer. Depois de logar com outra conta, os ~40 comandos destrutivos davam 401 por até 1h **mesmo com `--mfa-token`**. Agora as duas funções decidem pela mesma regra (código explícito → sessão de 1h → prompt TOTP), e o `login` (device flow e `--token`) apaga a sessão salva quando o uid embutido nela (`<uid_base64url>.<issuedAt>.<hmac>`, formato do Core) não é o da conta que acabou de entrar — ou quando o dono não dá pra saber. Re-login na mesma conta mantém a sessão.
|
|
67
|
+
- **`neetru domains reconcile` não diz mais "Todos os domínios obrigatórios já existem." quando algum ambiente foi pulado** (bug_6e9fe576, PR companheiro do Core #677). Cenário exato do bug: dois ambientes com `target=workspace`, nada criado, nada existente, 2 pulados — e a CLI dizia que tudo estava ok, porque `runDomainsReconcile` só olhava `created === 0 && errors === 0` e ignorava `skipped`. Agora: pulado nunca é confundido com "tudo certo" — a tabela de detalhes ganhou a coluna "Motivo/erro" com o texto plain PT-BR de cada motivo (`stage_sem_subdominio`/`subdomain_cloud_run_unsupported` = esperado; `subdomain_deploy_config_missing`/`subdomain_hard_cap_reached` = exige ação), e o resumo final distingue "criou", "erro", "só pulou" e "tudo já existia". O tipo local `DomainsReconcileResponse.ok` deixou de ser o literal `true` (o Core SEMPRE mandou um boolean; só o runtime do Core antes do #677 é que cravava `true`) — agora `ok:false` é tratado como falha real de execução, com o mesmo `safeExit(1)` que `doctor`/`smoke` usam para um resumo em lote com falha, tanto em `--json` quanto na saída humana. **Compatibilidade:** o Core de hoje (sem o #677) nunca manda `reason` nos pulados — mesmo shape de resposta, campo ausente; nesse caso a coluna mostra "motivo não informado por este Core" em vez de célula vazia, mas a frase "tudo já existe" continua banida sempre que `skipped > 0`, com ou sem o #677 do Core. **Exit code (revisão do cli#80):** falha é decidida também pela contagem local `errors > 0`, e não só pelo `ok` — o Core anterior ao #677 manda `ok: true` fixo mesmo com erro, e decidir só pelo `ok` reabria a mentira ("Erros: 1" seguido de "tudo já existe", exit 0). E pulado que **exige ação** (`subdomain_deploy_config_missing`, `subdomain_hard_cap_reached`) agora avisa quais ambientes precisam de ação e sai com exit 1; pulado esperado continua exit 0.
|
|
68
|
+
|
|
69
|
+
## [2.31.1] — 2026-10-11 (consertos de campo + fim da colisão de versão)
|
|
70
|
+
|
|
71
|
+
> **Por que 2.31.1 e não outra 2.31.0.** A `2.31.0` foi publicada no npm em
|
|
72
|
+
> 2026-10-09T23:55Z. As quatro correções abaixo entraram na `main` **depois**
|
|
73
|
+
> disso (a primeira ~33 min depois), sem bump — então por dois dias a `main` e o
|
|
74
|
+
> npm carregaram o **mesmo número de versão com código diferente**. Quem
|
|
75
|
+
> instalava `@neetru/cli@2.31.0` para pegar o conserto do WSL recebia o pacote
|
|
76
|
+
> SEM ele, e não tinha como perceber: a versão batia. Esta release existe para
|
|
77
|
+
> que o número volte a identificar o código. Confira com
|
|
78
|
+
> `neetru --version` + `npm view @neetru/cli dist-tags`.
|
|
79
|
+
|
|
80
|
+
### Corrigido
|
|
81
|
+
|
|
82
|
+
- **`neetru build` passa a construir produto em monorepo (pnpm/yarn/bun workspaces).** O comando nasceu assumindo "um repo = um app na raiz = npm": construía sempre em `process.cwd()`, chamava `npm install`/`npm run build` literais, e, pro Next.js `output: 'standalone'`, fixava o entrypoint em `server.js` e injetava `.next/static`/`public` na RAIZ do tarball. Num monorepo o Next.js traceja a partir do `outputFileTracingRoot` e **aninha** o app dentro do standalone (`apps/web/server.js`), porque o `node_modules` tracejado fica na raiz do standalone e o `server.js` o resolve subindo a árvore — então o tarball saía sem `server.js` no topo e com os estáticos no lugar errado (o app subia sem CSS/JS). O PDV teve que escrever um empacotador próprio e publicar com `neetru deploy --artifact`. Agora:
|
|
83
|
+
- **`--app <dir>`** (alias `--cwd <dir>`) aponta pro diretório do app sem precisar de `cd`. O artefato continua saindo no diretório onde o comando foi invocado, pra que `neetru build --app apps/web && neetru deploy` a partir da raiz continue achando o `.neetru-build/manifest.json`. `--app` inexistente falha explicitamente em vez de construir o diretório errado.
|
|
84
|
+
- **Gerenciador de pacote detectado**, em vez de `npm` cravado: campo `packageManager` (corepack) vence lockfile; `pnpm-lock.yaml`/`yarn.lock`/`bun.lockb` têm precedência sobre um `package-lock.json` deixado por acidente. O `install` roda na **raiz do workspace** (onde mora o lockfile) e o `build` no diretório do app. Nenhum install é congelado (`--frozen-lockfile`/`--immutable`/`npm ci` quebrariam com `package.json` recém-editado — mesma regra do bug_93f984a). No caminho Windows de env-prefix Unix, o `npx` cravado virou `pnpm exec`/`yarn exec`/`bunx`/`npx` conforme o gerenciador — com pnpm o `npx` baixaria o pacote do registry em vez de usar o do workspace.
|
|
85
|
+
- **Standalone aninhado resolvido pela árvore pós-build**: o entrypoint do manifest vira `apps/web/server.js` e `.next/static`/`public` são injetados **dentro** do prefixo do app. `.next/standalone` presente sem nenhum `server.js` agora **aborta** em vez de empacotar um tarball que nunca inicia. Repo de app único mantém o layout plano de sempre (prefixo vazio). (bug_678ddc98)
|
|
86
|
+
- **A CLI parou de sugerir `--env dev`, que o Core recusa — e `neetru new` voltou a funcionar.** O prompt "Ambiente:" oferecia `dev` como primeira opção e o usava como padrão, mas as rotas `/api/cli/v1/{tenants,workspaces,deployments}` validam `environmentId` por igualdade **estrita** contra `['dev-local','staging','prod']` (`ENVIRONMENT_IDS` do Core) e devolvem 400 `validation_failed`. Ou seja: quem aceitava o padrão sugerido pela própria ferramenta não criava ambiente nenhum, e não tinha como adivinhar `dev-local`. O alfabeto agora vem de um lugar só (`src/lib/envs.ts`, espelho do enum do Core), o padrão é `dev-local` nos quatro comandos que tinham a cópia do defeito, e um `--env dev` digitado à mão é traduzido para o token canônico em vez de ir cru (vale também para `local`, `stage`, `production`). `--env` desconhecido agora falha na hora, com a lista do que é aceito, em vez de virar 400 do servidor. Usar um apelido imprime aviso no stderr (nunca no stdout, que é contrato de máquina em `--json`), na mesma convenção do `normalizeEnv` do `neetru db`.
|
|
87
|
+
- **`neetru new` exige `--customer` quando o produto não é Neetru-owned.** A macro assumia `customer: 'self'` em silêncio — sentinel que o Core só honra para produto da própria Neetru (`resolveDefaultCustomer`); para qualquer outro ele recusa a criação do workspace. Como o fallback de slugs Neetru tem só `gestovendas` e `pdv-agiliza`, **todo produto novo** caía nesse caminho: a macro criava o produto no passo [1/5] e morria no [2/5], deixando um produto órfão no Core. Agora o customer é resolvido **antes** do passo [1/5]: produto Neetru-owned segue sem flag, e qualquer outro recebe uma recusa que diz o que falta, por quê e como obter um `customerId` (`neetru customers list|create`) — sem criar nada.
|
|
88
|
+
- **`neetru admin database delete` usa a sessão MFA de 1h e aceita `--mfa-token`** (bug_757b9746). O comando chamava `apiRequest` **sem header nenhum** de step-up — nem `X-Neetru-MFA-Token`, nem `X-Neetru-MFA-Session`. A rota `/api/cli/v1/db/{id}/delete` exige `requireCliStepUpMfa` no Core, então devolvia 401 **mesmo com `neetru auth mfa-session` ativa**, e o hint do `handleApiError` mandava passar `--mfa-token` — flag que o subcomando não declarava. Como o Core tem anti-replay de TOTP, cada tentativa seguinte queimava um código novo: na prática, não dava para arquivar banco pela CLI. Ao contrário do que o título do bug sugeria, a sessão **sempre foi persistida e lida** corretamente (em `~/.config/neetru-cli/mfa-session.json`, não no store legado do `Conf`); o defeito era este comando nunca pedir por ela. Agora `runDbDelete` chama `resolveStepUp` como os irmãos destrutivos (mesmo hotfix que `runDbRotate` levou em 2026-08-16), e tanto `admin database delete` quanto o alias depreciado `products db delete` declaram `--mfa-token <codigo>`. O comando entrou na enumeração do teste de contrato `cli-mfa-destructive-surface` — estar fora dessa lista desde o hotfix de 2026-08-20 foi o motivo de a regressão sobreviver.
|
|
89
|
+
- **A suíte de testes deixou de depender da home real de quem a roda** (`src/__tests__/setup/home-isolada.ts`). `src/lib/auth.ts` resolve `~/.config/neetru-cli/` por `os.homedir()` e **não** honra `$XDG_CONFIG_HOME` — isso é proposital e continua valendo em produção. O efeito colateral era `loadMfaSession()` ler o `mfa-session.json` **de verdade** do dev durante os testes: como `resolveStepUp` prefere a sessão de 1h ao `--mfa-token` (precedência de produção, também intencional e preservada aqui), qualquer dev que tivesse rodado `neetru auth mfa-session` na última hora via 6 testes falharem com `expected undefined to be '123456'` em `db-commands`, `control-plane` e `mobile-commands` — e passarem num CI limpo. O resultado do teste dependia do relógio e do disco da máquina. O setup agora aponta `os.homedir()` para um tmpdir vazio por processo: conserta a classe inteira em vez dos 3 arquivos que pegaram fogo e fecha o risco inverso — um teste que exercitasse `saveMfaSession`/`clearMfaSession` escreveria ou **apagaria** o `auth.json`/`mfa-session.json` real do dev. Irmão do `setup/sem-env-de-repo-git.ts`, mesma classe de defeito; `home-isolada.test.ts` trava o isolamento.
|
|
90
|
+
- **`neetru login` não crasha mais no WSL sem `xdg-open`.** Ao abrir o navegador (`openBrowser`, usado pelo device-code flow), o CLI chamava `spawn('xdg-open', ...)` sem registrar um listener de `'error'` no `ChildProcess`. Quando o binário não existe (comum no WSL, que não tem `xdg-open` por padrão), o `spawn` emite `'error'` (ENOENT) de forma assíncrona; sem handler, o Node trata isso como exceção não capturada e derruba o processo — abortando o polling do device-code mesmo com o código e a URL já impressos na tela. Agora o erro é tratado (`child.on('error', ...)`) e o usuário recebe "Não consegui abrir o navegador; abra a URL acima manualmente", sem interromper o login. Vale para qualquer plataforma onde o binário de abrir navegador falhe (Windows/macOS/Linux).
|
|
91
|
+
- **`neetru mobile init --redirect-uri` aceita `http://localhost` e `http://127.0.0.1`.** A validação local recusava qualquer `redirectUri` que não começasse com `https://`, mas o Core (`validateRedirectUris`, usada também pelo registro de apps mobile) sempre aceitou `http://localhost`/`http://127.0.0.1` como loopback de dev (RFC 8252) — a CLI era mais restritiva que o próprio servidor que valida de novo. Agora a CLI espelha exatamente a regra do Core: `https://` ou `http://` com host `localhost`/`127.0.0.1`; qualquer outro `http://` e custom scheme (`meuapp://`) continuam rejeitados.
|
|
92
|
+
- **`neetru products create` parou de imprimir `--env` como se tivesse sido gravado** (bug `bug_db50b21f84e8400f9c3f6286b69c353b`). O POST `/api/cli/v1/products` do Core valida o corpo com `cliProductCreateSchema`, que **não declara** `environments` nem `planTemplate`: o zod (não-strict) descartava as duas chaves em silêncio, sem 400, e a resposta (`{ ok, productId, slug, createdAt }`) também não devolvia nenhuma delas. A CLI caía no fallback `res.environments ?? environments` e exibia na tabela exatamente o que o usuário pediu — quem rodava `--env dev,prod` saía convencido de ter escolhido ambientes que nunca existiram, e só descobria depois, procurando um `dev` que não estava lá. Agora só é reportado como verdade o que o servidor devolveu: sem confirmação, a tabela mostra `staging, prod (não confirmados)` — os que o Core auto-provisiona sozinho em `_products-core.ts` — e explica isso numa linha abaixo, com `neetru workspaces list --product <id>` pra conferir o que existe de fato; quem passou `--env` recebe um aviso de que o pedido foi ignorado. No `--json`, `environments` e `planTemplate` viram `null` quando não confirmados, e entram `environmentsRequested`, `environmentsConfirmed` e `planTemplateConfirmed`. A pergunta "Ambientes (csv)" saiu do modo interativo (a resposta era descartada) e a flag `--env` continua aceita — pra não quebrar script existente, e porque o corpo segue enviando o campo: no dia em que o Core passar a aceitá-lo e devolvê-lo, a CLI volta a reportar o valor real sem mudança nenhuma aqui. **Nada mudou no Core** — ensinar o servidor a aceitar ambientes na criação é outra decisão.
|
|
93
|
+
- **`neetru deploy --target vm --port` avisa também em `--json`.** O aviso de "porta é gerenciada pelo Core" só saía em modo interativo (`!opts.json` calava tudo, pra não quebrar o contrato de 1 linha JSON no stdout) — quem roda via script com `--json` nunca descobria que `--port` foi descartado. Agora o mesmo aviso sai no stderr quando `--json`, sem tocar a linha JSON do stdout. A mensagem também deixou de dizer "ignorado em deploy staging/prod": o guard dispara pra qualquer ambiente em `target=vm` (inclusive `dev`), já que a porta é sempre alocada pelo Core/agente por produto+ambiente+servidor.
|
|
94
|
+
|
|
95
|
+
## [2.31.0] — 2026-10-03 (docs do produto: `neetru docs init|new|check|sync`)
|
|
96
|
+
|
|
97
|
+
Implementa a parte de ferramenta do **protocolo de documentação dos produtos SaaS** (Core, `docs/sistema/contrib/PROTOCOLO_DOCS_PRODUTOS_SAAS.md` v1, com a taxonomia de `PROTOCOLO_WIKI_INTERNA.md` §5–§7). Decisões do dono de 2026-10-03: o deploy **só avisa** sobre docs (nunca bloqueia); publicar no Core é **opcional** — opt-in `docs.publishOnDeploy` no deploy de produção ou manual com `neetru docs sync`; `--migrar` não move nem renomeia nada.
|
|
98
|
+
|
|
99
|
+
### Adicionado
|
|
100
|
+
|
|
101
|
+
- **`neetru docs init [--com-api] [--migrar] [--dry-run] [--json]`** — cria o mínimo do primeiro dia (§7): pastas do §3 (com `.gitkeep` nas vazias), `docs/index.md`, `docs/produto/VISAO_DO_PRODUTO.md`, `docs/arquitetura/ARQUITETURA.md` (com a seção "Integração com o Core"), `docs/runbooks/RUNBOOK_DEPLOY.md`, `README.md` (3 seções) e `CHANGELOG.md` (Keep a Changelog PT-BR, versão do `package.json`), cada doc com frontmatter preenchido (`product`, datas de hoje, `status: draft`, `owner: produto`, `audience` da pasta) e um `<!-- o que escrever aqui -->` por seção. **Idempotente**: nunca sobrescreve; informa o que criou e o que pulou. `API_V1.md` só com `--com-api` (grava `docs.api: true`) ou quando o produto declara app móvel (`.neetru/mobile.json`) ou integração. Acrescenta a chave `docs` no `neetru.config.json` sem mexer no resto (preserva indentação, fim de linha e a quebra final).
|
|
102
|
+
- **`--migrar`** (repo que já tem docs): acrescenta frontmatter inferido **só** nos `.md` que não têm — `title` do H1, `description` da primeira frase útil, `type`/`category` pela pasta (inclusive as legadas: `architecture/`, `design/`, `guia-do-usuario/`, `_review/`, `deploy/`…) e pelo prefixo ou palavra-chave do nome, `status` pela família, `date`/`updated` pelo primeiro/último commit (ou `_revisado: AAAA-MM-DD_` do corpo), `tags: [migrado]`. **Não move, não renomeia e não altera o corpo** (o arquivo vira exatamente frontmatter + linha em branco + conteúdo original; BOM e CRLF preservados); quem já tem frontmatter fica intocado. Gera `docs/_nomes-legados.json` (só se não existir — a lista só diminui) e o relatório `docs/_audit/MIGRACAO_DOCS_<data>.md` com cada inferência, a origem e a confiança, as sugestões de destino pelo protocolo e o que falta do mínimo do primeiro dia (que **não** é criado no `--migrar`: vem depois de mover, com `docs init`).
|
|
103
|
+
- **`neetru docs new <tipo> "<título>" [--categoria] [--subcategoria] [--audience] [--descricao] [--data] [--dry-run] [--json]`** — doc a partir de modelo embutido, na pasta do §3 e com o nome do §6/§8 (sem acento, MAIÚSCULAS, prefixo do tipo — `AUDITORIA_`/`PROTOCOLO_` quando o título já começa assim —, data no fim nos registros, próximo `ADR-NNNN_` livre). Tipos: `guia`, `referencia`, `arquitetura`, `adr`, `runbook`, `norma`, `plano`, `relatorio`, `incidente`, `ata`, e os modelos-base do zelo: `rfc`/`spec` (viram `plano`), `politica` (vira `norma`), `ameacas` (modelo de ameaças, `referencia` em `seguranca/`). Nunca sobrescreve.
|
|
104
|
+
- **`neetru docs check [--changed] [--strict] [--offline] [--json]`** — validador **offline** com os mesmos códigos do Core (FM001–FM013, TI001–TI008, NM001–NM002, LK001–LK002, PB001, LC001–LC002) mais PR001 (`product` ausente ou diferente do slug), PR002 (mínimo do primeiro dia e seções do README; os itens "do primeiro mês" saem como **sugestão**, que nunca falha), PR003 (categoria só do Core) e PR004 (CHANGELOG sem a versão do `package.json`). `_archive/` e `_audit/` com as regras reduzidas do §9.4. `--changed` = catraca contra a branch principal (commits + staged + não commitados + não rastreados; sem git, avisa e checa tudo). `--strict` = aviso também falha. Sai 1 com erro.
|
|
105
|
+
- **Taxonomia embutida** (`src/lib/product-docs/taxonomy.ts`) idêntica ao bloco JSON do §5.2 do protocolo — teste confere contra a fixture copiada do protocolo e, quando há checkout do Core (`NEETRU_CORE_DIR`), contra o próprio markdown. Online, usa `GET /api/cli/v1/docs/taxonomy` do Core (outra frente cria a rota) e cai na embutida em 404, falha de rede ou formato inválido; avisa quando a do Core é mais nova.
|
|
106
|
+
- **`neetru docs sync [--dry-run] [--offline] [--json]`** — publica `docs/` no registro de docs por produto do Core, contra o contrato **proposto** em `PROJ_PRODUCT_DOCS` §4.2 (`GET`/`POST /api/cli/v1/products/{productId}/docs`, corpo `{slug, content, frontmatter, visibility}`). Só sobem docs **sem erro** no check (fora `_archive/` e `_audit/`); `visibility: public` só com `audience: publico` + `status: active`; doc igual ao do Core (sha256 do conteúdo + título + status + visibilidade) não é reenviado; **nada é apagado** — doc que só existe no Core é listado como órfão. Limite local de 200 docs / 10 MB (D-05). **Capacidade**: se o Core responde 404 (sem `product_not_found`), 405 ou 501, mostra "O Core ainda não aceita docs de produto" e sai **0**. `docs.publish: false` desliga.
|
|
107
|
+
- **`neetru init` e `neetru new` chamam `docs init`** depois do scaffold (`--sem-docs` pula; falha nos docs vira aviso, não derruba o init).
|
|
108
|
+
- **`neetru deploy`** roda `docs check` (taxonomia embutida, sem rede extra) e **só avisa** — em produção, lista o que falta do mínimo do primeiro dia. Com `docs.publishOnDeploy: true`, o deploy de **produção** bem-sucedido chama o sync no fim (VM, workspace e cloud-run); falha do sync vira aviso e o deploy continua com exit 0. Em `--json`, a saída ganha `docs: { check, sync }`. `--sem-docs` pula as duas coisas.
|
|
109
|
+
- `neetru.config.json`: bloco `docs` (`protocolVersion`, `root`, `publish`, `publishOnDeploy`, `api`) no schema.
|
|
110
|
+
|
|
111
|
+
### Corrigido (ensaio real nos 3 produtos e alinhamento com o servidor do Core)
|
|
112
|
+
|
|
113
|
+
- Datas no fuso de Brasília: depois das 21h BRT, docs, CHANGELOG, relatório e `gerado_em` saíam com o dia seguinte (UTC).
|
|
114
|
+
- `suggestedName` preserva a data ISO com hífens (`…_2026-06-30.md`); antes virava `_2026_06_30` e reprovava o NM002.
|
|
115
|
+
- CHANGELOG do `docs init` em produto maduro: entrada da versão atual, sem "Primeira versão do produto." inventada.
|
|
116
|
+
- `index.md` gerado lista tudo o que existe (inclusive `superpowers/`, `_review/`, pastas fora do padrão e docs da raiz), marcando o que está fora do padrão.
|
|
117
|
+
- Monorepo: o `neetru.config.json` é procurado subindo pastas até a raiz do git; `docs.root` pode apontar acima do config, mas nunca para fora do repositório.
|
|
118
|
+
- `--migrar`: inferências de tipo/destino melhores (`DESIGN*` não vira plano; doc solto na raiz recebe tipo coerente com o destino sugerido; `deploy/README.md` não vira índice; destinos do §10).
|
|
119
|
+
- `resolveTaxonomy` aceita o formato real do Core (`categories` como lista + `subcategories` como mapa); a cópia embutida passa a esconder `superpowers/`, como o protocolo.
|
|
120
|
+
- `docs sync`: espaça os envios (janela de 25 por minuto) e respeita `Retry-After` no 429; compara também os metadados com o Core; confere localmente o limite de 2 MB por doc e os caracteres aceitos no caminho (acento/espaço/`__` são recusados antes do envio); a dica de órfão não cita mais um comando que não existe.
|
|
121
|
+
|
|
122
|
+
### Dependência no Core
|
|
123
|
+
|
|
124
|
+
- `GET /api/cli/v1/docs/taxonomy` (Core PR #565) e `GET`/`POST /api/cli/v1/products/{productId}/docs` (Core PR #571). Sem eles no ar, a CLI funciona igual (taxonomia embutida; o sync informa e sai 0).
|
|
125
|
+
|
|
126
|
+
### Corrigido (entrou na publicação de 2026-10-09)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
- **`neetru dev up` voltou a subir o banco.** O compose gerado tinha um serviço `agent` com `build: dockerfile: Dockerfile.agent`, mas o comando só gravava o compose — o `Dockerfile.agent` nunca era escrito no projeto (a função que o copiaria não era chamada). O `docker compose up` falhava no build e nem o banco subia. Mesmo com o arquivo, o agente não rodaria: o entrypoint chamava `node` num executável `pkg` (não é JS), dependia de um `NEETRU_DEV_MODE` que o agente não implementa, e o container montava o `docker.sock` do host. Agora o `dev up` sobe **só o banco** do engine do projeto e imprime a conexão para o `.env.local` (`postgresql://postgres:neetru_dev@localhost:5432/app` etc.); o app roda com `npm run dev`. As mensagens deixaram de anunciar "App SaaS" e "Agente: conectado ao Core". O agente local volta quando o próprio agente tiver modo dev. Templates mortos removidos: `templates/dev/{Dockerfile.agent,agent-entrypoint.sh,docker-compose.dev.yml}`.
|
|
130
|
+
- README e `--help`: a seção de dev local descrevia flags do `dev up` que não existem (`--import`, `--export-on-exit`, `--skip-core`) e o tratava como "emulador completo, 100% offline". Agora descreve o que roda de fato na máquina do dev de produto. `dev exec` documentado como ferramenta do CI do Core (exige o `firebase.json` do Core).
|
|
131
|
+
|
|
132
|
+
## [2.30.0] — 2026-10-03 (varredura de segredo no pacote público antes do deploy)
|
|
133
|
+
|
|
134
|
+
### Adicionado
|
|
135
|
+
|
|
136
|
+
- **`neetru deploy` bloqueia segredo no pacote PÚBLICO** (decisão do dono 2026-10-03: a credencial `nrt_` do produto é **só de servidor**; produtos hoje carregam a chave no navegador via `NEXT_PUBLIC_NEETRU_API_KEY`/`NEXT_PUBLIC_NEETRU_PUBLIC_KEY`).
|
|
137
|
+
- Novo `src/lib/public-secret-scan.ts` (sem dependências): lê o `.tar.gz` de deploy em **streaming** (zlib + parser ustar/GNU `@LongLink`/pax `path=`) e varre só o que é servido ao navegador — `.next/static/**` (em qualquer profundidade), `public/**`, `out/**`, `dist/**`, `build/**`; na stack `static`, o pacote inteiro; `node_modules/**` nunca; binários (imagem/fonte/mídia/wasm) e arquivos > 25 MB pulados (o grande é avisado).
|
|
138
|
+
- Padrões: `nrt_[A-Za-z0-9_-]{20,}`, `npk_[A-Za-z0-9_-]{20,}`, `-----BEGIN … PRIVATE KEY-----` seguido do material base64 da chave (só o cabeçalho, comum em código de biblioteca, não conta) e o **valor** de envs públicas (`NEXT_PUBLIC_*`, `EXPO_PUBLIC_*`, `VITE_*`, `REACT_APP_*`, `PUBLIC_*`) com `KEY`/`SECRET`/`TOKEN` no nome — lidas de `.env`, `.env.production`, `.env.local`, `.env.production.local` (que **não** viajam no tarball), `--env-file` e `process.env`. Valores públicos por desenho (Stripe `pk_live_`/`pk_test_`, Google/Firebase `AIza…`, reCAPTCHA `6L…`) e valores com menos de 12 caracteres são ignorados.
|
|
139
|
+
- Roda **antes do upload** (fluxo build→upload e `--artifact <arquivo>`; também com `--local-artifact`). Em `target=cloud-run` sem `--image`, varre as pastas públicas do disco antes de buildar a imagem.
|
|
140
|
+
- Achou → **falha FECHADO**: exit 1, `public_secret_in_bundle` (em `--json`, a linha `{ok:false,error,message}`), mensagem PT-BR com arquivo + tipo + como corrigir. **Nunca imprime o segredo**: no máximo os 8 primeiros caracteres (e nunca mais que 1/4 do valor), mascarados. Pacote ilegível → `public_secret_scan_failed` (também fechado).
|
|
141
|
+
- **`--allow-public-secret "<motivo>"`** — override de emergência. Motivo obrigatório (≥ 10 caracteres, validado antes de build/upload; senão `invalid_override_reason`). Segue com aviso alto ("trate a chave como VAZADA") e **registra no deploy**: `publicSecretOverride: { reason, findings: [{ file, pattern }], at }` no corpo do `POST /api/cli/v1/deploy` (só caminho + tipo, nunca o valor) e `publicSecretScan` na saída `--json`.
|
|
142
|
+
- Não varridos (com aviso): `--artifact-url` (pacote remoto) e `--image` (imagem pronta).
|
|
143
|
+
|
|
144
|
+
### Dependência no Core
|
|
145
|
+
|
|
146
|
+
- O `deployRequestSchema` do Core faz *strip* de chaves desconhecidas: hoje o `publicSecretOverride` chega no corpo cru mas **não é persistido**. Para o registro valer de ponta a ponta, o Core precisa ler `publicSecretOverride` do corpo cru (mesmo padrão do `prodDirectOverride`) e gravar em `deployments/{id}` + auditoria. Até lá o registro fica na saída do CLI.
|
|
147
|
+
|
|
7
148
|
## [2.29.0] — 2026-10-01 (status live_unverified, db move decommission no contrato real, servers pgbouncer sync-userlist, TUI do code sob demanda)
|
|
8
149
|
|
|
9
150
|
### Adicionado
|
package/README.md
CHANGED
|
@@ -51,20 +51,26 @@ Cria produto + workspace + scaffold local Next.js 15 + instala deps + abre cockp
|
|
|
51
51
|
| `neetru new <slug>` | Criar produto completo (registry + workspace + scaffold) |
|
|
52
52
|
| `neetru init <name>` | Só scaffold local Next.js + Firebase |
|
|
53
53
|
| `neetru add <feature>` | Adicionar features ao scaffold (auth, billing, etc) |
|
|
54
|
-
| `neetru deploy` | Pipeline de deploy interativo (cloud-run / vm / workspace) |
|
|
54
|
+
| `neetru deploy` | Pipeline de deploy interativo (cloud-run / vm / workspace) — roda `docs check` e só avisa |
|
|
55
|
+
| `neetru docs init [--com-api] [--migrar]` | Mínimo do primeiro dia do `docs/` do produto (idempotente); `--migrar` só acrescenta frontmatter + relatório |
|
|
56
|
+
| `neetru docs new <tipo> "<título>"` | Doc novo a partir do modelo, na pasta e com o nome do protocolo |
|
|
57
|
+
| `neetru docs check [--changed] [--strict]` | Valida os docs do produto offline (códigos FM/TI/NM/LK/PB/LC + PR001–PR004) |
|
|
58
|
+
| `neetru docs sync [--dry-run]` | Publica os docs do produto no Core (só os sem erro; nada é apagado) |
|
|
55
59
|
| `neetru build [--stack ...] [--target-env dev\|workspace\|prod] [--env-file ...] [--no-cache] [--no-install]` | Empacotar produto em tarball pra deploy |
|
|
56
60
|
| `neetru smoke [--base-url ...] [--prod ...] [--health-path ...] [--routes csv] [--json]` | Percorrer as páginas do produto **local** antes do deploy (alcance + login + DB) + smoke de prod opcional |
|
|
57
61
|
| `neetru promote` | Promover dev → staging → prod |
|
|
58
62
|
| `neetru env switch <target>` | Trocar `NEETRU_ENV` no `.env.local` |
|
|
59
63
|
| `neetru env set --service=X --set KEY=VAL` | Setar env vars de serviço Cloud Run |
|
|
64
|
+
| `neetru secrets set CHAVE --env <ambiente> [--generate \| --stdin \| --from-file <caminho>]` | Segredo do próprio produto (app em VM): o valor vai direto pro Secret Manager e nunca entra pela linha de comando; `--generate` = o Core sorteia |
|
|
65
|
+
| `neetru secrets list` / `archive CHAVE --env <ambiente>` | Listar só os nomes / arquivar um segredo do produto |
|
|
60
66
|
| `neetru ar create <name>` | Criar repositório Artifact Registry |
|
|
61
67
|
| `neetru hosting create-mapping --service=X --domain=Y` | Domain mapping |
|
|
62
68
|
| `neetru admin database create --product-id=X --engine=cloud-sql-postgres` | Registrar banco isolado por produto (staff control plane) |
|
|
63
69
|
| `neetru admin database engines` | Listar engines suportados (`firestore-instance`, `cloud-sql-postgres`, `cloud-sql-mysql`, `vm-postgres-single`, `vm-mysql-single`, `nosql-vm`, …) |
|
|
64
70
|
| `neetru db init / apply / migrations` | Banco por produto no fluxo dev (registra + schema + migrations) |
|
|
65
71
|
| `neetru dev` | Banco Docker local + watch de schema (`salvar = aplicado`) |
|
|
66
|
-
| `neetru dev up` |
|
|
67
|
-
| `neetru dev exec <script>` | Roda `<script>` com emuladores
|
|
72
|
+
| `neetru dev up` | Banco do produto em container (Docker Compose) + URL de conexão pro `.env.local` |
|
|
73
|
+
| `neetru dev exec <script>` | Roda `<script>` com os emuladores Firebase do **Core** (exige o repo do Core; CI do Core) |
|
|
68
74
|
| `neetru cloud-run pause/resume/delete <service>` | Controle Cloud Run |
|
|
69
75
|
| `neetru products list/create/update` | Registry de produtos |
|
|
70
76
|
| `neetru workspaces create/list/get/advance` | Instâncias por cliente |
|
|
@@ -91,24 +97,44 @@ Cria produto + workspace + scaffold local Next.js 15 + instala deps + abre cockp
|
|
|
91
97
|
|
|
92
98
|
40+ subcomandos. Quase todos suportam `--json` pra automação.
|
|
93
99
|
|
|
94
|
-
##
|
|
100
|
+
## Docs do produto (`neetru docs init|new|check|sync`) — 2.31.0
|
|
95
101
|
|
|
96
|
-
|
|
102
|
+
Padrão: `PROTOCOLO_DOCS_PRODUTOS_SAAS.md` do Core (versão 1). O `docs/` de cada produto segue a mesma taxonomia da wiki do Core (categoria + subcategoria por assunto, `type` à parte) e nomes `MAIÚSCULO_COM_UNDERLINE`.
|
|
97
103
|
|
|
98
104
|
```bash
|
|
99
|
-
#
|
|
100
|
-
neetru
|
|
101
|
-
#
|
|
102
|
-
|
|
103
|
-
|
|
105
|
+
neetru docs init # README, CHANGELOG, docs/index.md, visão, arquitetura, runbook de deploy
|
|
106
|
+
neetru docs init --com-api # + docs/api/API_V1.md (o produto expõe /api/v1)
|
|
107
|
+
neetru docs init --migrar # repo que já tem docs: frontmatter inferido + relatório em docs/_audit/
|
|
108
|
+
neetru docs new adr "Usar Postgres para vendas"
|
|
109
|
+
neetru docs new relatorio "Auditoria de UX da tela de caixa"
|
|
110
|
+
neetru docs check --changed # catraca: só o que mudou contra a branch principal
|
|
111
|
+
neetru docs sync --dry-run # o que subiria para o Core
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
- `neetru init` e `neetru new` já rodam `docs init` (`--sem-docs` pula).
|
|
115
|
+
- `neetru deploy` roda `docs check` e **só avisa** — nunca bloqueia. Com `"docs": { "publishOnDeploy": true }` no `neetru.config.json`, o deploy de **produção** publica os docs no fim; falha na publicação não derruba o deploy. `--sem-docs` pula as duas coisas.
|
|
116
|
+
- A taxonomia vem embutida na CLI (funciona offline); quando o Core expõe `GET /api/cli/v1/docs/taxonomy`, o `check` usa a do Core e avisa se a embutida estiver velha.
|
|
104
117
|
|
|
105
|
-
|
|
106
|
-
neetru dev up --import .neetru-dev/seed --export-on-exit .neetru-dev/seed
|
|
118
|
+
## Dev local (`neetru dev`, `neetru dev up`)
|
|
107
119
|
|
|
108
|
-
|
|
109
|
-
|
|
120
|
+
O que roda na máquina do dev de produto hoje:
|
|
121
|
+
|
|
122
|
+
| Peça | Como |
|
|
123
|
+
|---|---|
|
|
124
|
+
| SDK sem conta Neetru | `NEETRU_ENV=dev` → mocks em memória (login fixture, uso zerado, banco em memória). `neetru mocks reset` limpa as fixtures. |
|
|
125
|
+
| Banco real do produto | `neetru dev` (container + reaplica `db/schema.ts` a cada save) **ou** `neetru dev up` (só sobe o container e imprime a URL de conexão). Engine vem do projeto. |
|
|
126
|
+
| App | `npm run dev`, fora de container. |
|
|
127
|
+
| Agente / Core locais | **Ainda não existem** para produto. Deploy e migração pelo agente só acontecem na VM. |
|
|
110
128
|
|
|
111
|
-
|
|
129
|
+
```bash
|
|
130
|
+
# Na raiz do produto (com database.engine no neetru.config.json):
|
|
131
|
+
neetru dev up
|
|
132
|
+
# → gera neetru-compose.dev.yml e sobe o banco (docker compose up -d --wait)
|
|
133
|
+
# → imprime a conexão, ex. postgresql://postgres:neetru_dev@localhost:5432/app
|
|
134
|
+
docker compose -f neetru-compose.dev.yml down # para
|
|
135
|
+
|
|
136
|
+
# CI do Core (na raiz do Core, onde tem firebase.json): roda o script com os
|
|
137
|
+
# emuladores Firebase e propaga o exit code.
|
|
112
138
|
neetru dev exec "npm run test:integration"
|
|
113
139
|
```
|
|
114
140
|
|
|
@@ -121,7 +147,7 @@ neetru dev use --product meu-produto --sync
|
|
|
121
147
|
# Protótipo local ainda não cadastrado no Core:
|
|
122
148
|
neetru dev use --prototype meu-rascunho
|
|
123
149
|
|
|
124
|
-
# Ver o contexto
|
|
150
|
+
# Ver o contexto dev-local efetivo:
|
|
125
151
|
neetru dev status
|
|
126
152
|
|
|
127
153
|
# Sincronizar bancos dev-local do Core para .neetru/db.json:
|
|
@@ -129,7 +155,7 @@ neetru dev sync --write-db
|
|
|
129
155
|
```
|
|
130
156
|
|
|
131
157
|
> `neetru dev` (sem subcomando) mantém o modo legado: banco Docker local + watch de schema.
|
|
132
|
-
> Flags:
|
|
158
|
+
> Flags: `dev up --core-port <n>` (porta do app nas instruções, default 3000); `dev exec --only firestore,auth,functions --core-path <dir>`.
|
|
133
159
|
> O arquivo `.neetru/dev.json` guarda o contexto local: produto Core ou protótipo, Core path e emuladores padrão.
|
|
134
160
|
|
|
135
161
|
## Build — env do bundle (`NEXT_PUBLIC_NEETRU_ENV`)
|
|
@@ -145,6 +171,26 @@ Isso existe porque o Next.js carrega `.env.local` (arquivo do disco do dev, nunc
|
|
|
145
171
|
|
|
146
172
|
Se o produto envolver `next.config.mjs` com `withNeetruBuildGuard` (`@neetru/sdk/build-guard`, `@neetru/sdk >= 3.1.15`), `neetru build` confere pós-build que o env realmente compilado bate com o esperado — e **recusa empacotar o tarball** se divergir, em vez de deployar um bundle quebrado silenciosamente.
|
|
147
173
|
|
|
174
|
+
## Segredo no pacote público bloqueia o deploy (2.30.0)
|
|
175
|
+
|
|
176
|
+
A credencial `nrt_` do produto é **só de servidor**. Antes de enviar o pacote, `neetru deploy` varre o que é servido ao navegador — `.next/static/**`, `public/**`, `out/**`, `dist/**`, `build/**` (na stack `static`, o pacote inteiro; `node_modules` nunca) — procurando:
|
|
177
|
+
|
|
178
|
+
- `nrt_…` e `npk_…` (credenciais Neetru);
|
|
179
|
+
- `-----BEGIN … PRIVATE KEY-----` com o material da chave (só o cabeçalho não conta);
|
|
180
|
+
- o **valor** de envs públicas (`NEXT_PUBLIC_*`, `EXPO_PUBLIC_*`, `VITE_*`, `REACT_APP_*`, `PUBLIC_*`) cujo nome contém `KEY`/`SECRET`/`TOKEN` (lidas de `.env`, `.env.production`, `.env.local`, `.env.production.local`, `--env-file` e do ambiente). Valores públicos por desenho do provedor (Stripe `pk_live_`/`pk_test_`, chave web Google/Firebase `AIza…`, reCAPTCHA `6L…`) são ignorados.
|
|
181
|
+
|
|
182
|
+
Achou → **o deploy falha** (exit 1, `public_secret_in_bundle`) mostrando arquivo e tipo — **nunca o segredo** (no máximo 8 caracteres iniciais, mascarados). Pacote ilegível também falha (`public_secret_scan_failed`).
|
|
183
|
+
|
|
184
|
+
Correção: tire a chave do código do navegador/app; o navegador/app chama a API do seu produto com o token do usuário e o backend usa `@neetru/sdk` com `NEETRU_API_KEY` (ver `verifyAccessToken` no SDK ≥ 3.8.0).
|
|
185
|
+
|
|
186
|
+
Emergência (fica registrado no deploy — só caminho e tipo, nunca o valor):
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
neetru deploy --allow-public-secret "chave antiga já revogada, ticket #123"
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Limites: `--artifact-url` (pacote remoto) e `--image` (imagem pronta, cloud-run) não passam pelo CLI e **não são varridos** — o deploy avisa. Em `target=cloud-run` sem `--image`, a varredura é nas pastas públicas do disco antes do build da imagem.
|
|
193
|
+
|
|
148
194
|
## Smoke local antes do deploy (`neetru smoke`)
|
|
149
195
|
|
|
150
196
|
Roda contra o servidor **dev local** (`next dev` / `neetru dev up`) e percorre as páginas do produto **antes** de `neetru build` + `neetru deploy` — pega página quebrada, login que não renderiza ou banco fora do ar sem subir nada em produção.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* "Valores válidos" (bug_89d2497e, etapa 2).
|
|
3
|
+
*
|
|
4
|
+
* O gcloud (`calliope/parser_extensions.py`) separa erro de COMANDO de erro de
|
|
5
|
+
* ARGUMENTO: quando o valor de uma flag não está no enum, ele LISTA as escolhas
|
|
6
|
+
* válidas em vez de adivinhar. Aqui é o mesmo, com uma regra a mais: a lista
|
|
7
|
+
* impressa é a MESMA lista que validou. Quem chama passa o array que usou na
|
|
8
|
+
* checagem (ex.: `TENANT_ENVIRONMENT_IDS`) — nunca uma string copiada à mão,
|
|
9
|
+
* que é como o `--env dev` ficou anos sugerido por uma CLI cujo servidor o
|
|
10
|
+
* recusava (bug_7dcb68e2).
|
|
11
|
+
*/
|
|
12
|
+
import { NeetruCliError } from './errors.js';
|
|
13
|
+
export interface InvalidChoiceInput {
|
|
14
|
+
/** A flag que recebeu o valor (ex.: `'--env'`). Vai para o JSON. */
|
|
15
|
+
flag: string;
|
|
16
|
+
/** O valor recebido, como o usuário digitou. */
|
|
17
|
+
value: unknown;
|
|
18
|
+
/** A MESMA lista que validou o valor. */
|
|
19
|
+
valid: readonly string[];
|
|
20
|
+
/** Sujeito da frase humana (ex.: `'Ambiente'`). Padrão: `Valor de <flag>`. */
|
|
21
|
+
subject?: string;
|
|
22
|
+
/** Código de máquina do JSON. Padrão: `'invalid_choice'`. */
|
|
23
|
+
code?: string;
|
|
24
|
+
/** Linha extra para humanos (ex.: os apelidos aceitos). */
|
|
25
|
+
hint?: string;
|
|
26
|
+
}
|
|
27
|
+
/** `dev-local | staging | prod` — o formato único de lista de escolhas. */
|
|
28
|
+
export declare function formatChoices(valid: readonly string[]): string;
|
|
29
|
+
/**
|
|
30
|
+
* Erro de valor fora do enum, com os valores válidos da fonte real. Sai como
|
|
31
|
+
* erro do cliente (exit 1) e, em `--json`, leva `flag`, `value` e `valid`.
|
|
32
|
+
*/
|
|
33
|
+
export declare function invalidChoiceError(input: InvalidChoiceInput): NeetruCliError;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* "Valores válidos" (bug_89d2497e, etapa 2).
|
|
3
|
+
*
|
|
4
|
+
* O gcloud (`calliope/parser_extensions.py`) separa erro de COMANDO de erro de
|
|
5
|
+
* ARGUMENTO: quando o valor de uma flag não está no enum, ele LISTA as escolhas
|
|
6
|
+
* válidas em vez de adivinhar. Aqui é o mesmo, com uma regra a mais: a lista
|
|
7
|
+
* impressa é a MESMA lista que validou. Quem chama passa o array que usou na
|
|
8
|
+
* checagem (ex.: `TENANT_ENVIRONMENT_IDS`) — nunca uma string copiada à mão,
|
|
9
|
+
* que é como o `--env dev` ficou anos sugerido por uma CLI cujo servidor o
|
|
10
|
+
* recusava (bug_7dcb68e2).
|
|
11
|
+
*/
|
|
12
|
+
import { EXIT_CODES, NeetruCliError } from './errors.js';
|
|
13
|
+
/** `dev-local | staging | prod` — o formato único de lista de escolhas. */
|
|
14
|
+
export function formatChoices(valid) {
|
|
15
|
+
return valid.join(' | ');
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Erro de valor fora do enum, com os valores válidos da fonte real. Sai como
|
|
19
|
+
* erro do cliente (exit 1) e, em `--json`, leva `flag`, `value` e `valid`.
|
|
20
|
+
*/
|
|
21
|
+
export function invalidChoiceError(input) {
|
|
22
|
+
const valor = String(input.value ?? '');
|
|
23
|
+
const message = `${input.subject ?? `Valor de ${input.flag}`} inválido: "${valor}". ` +
|
|
24
|
+
`Valores válidos: ${formatChoices(input.valid)}.` +
|
|
25
|
+
(input.hint ? `\n ${input.hint}` : '');
|
|
26
|
+
return new NeetruCliError(message, {
|
|
27
|
+
code: input.code ?? 'invalid_choice',
|
|
28
|
+
exit: EXIT_CODES.client,
|
|
29
|
+
details: { flag: input.flag, value: valor, valid: [...input.valid] },
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=choices.js.map
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conselho validado contra o comando em execução (bug_89d2497e, etapa 1).
|
|
3
|
+
*
|
|
4
|
+
* A doença: a CLI dava conselho sem validar contra si mesma. O caso que nasceu
|
|
5
|
+
* dela foi o bug_757b9746 — o `handleApiError` mandava "Passe --mfa-token" para
|
|
6
|
+
* `admin database delete`, que não declarava a flag. A guarda do #83 valida as
|
|
7
|
+
* flags citadas contra a árvore INTEIRA e não pegaria esse caso: `--mfa-token`
|
|
8
|
+
* existia em outros comandos. O que faltava era saber QUAL comando está rodando.
|
|
9
|
+
*
|
|
10
|
+
* Aqui a CLI passa a saber: `installCommandContext(program)` registra um hook
|
|
11
|
+
* `preAction` que guarda o comando que o Commander vai executar, e
|
|
12
|
+
* `adviseFlag` só cita a flag se ESSE comando a declara. Senão, oferece a
|
|
13
|
+
* alternativa que existe.
|
|
14
|
+
*
|
|
15
|
+
* Regras de desenho:
|
|
16
|
+
* - Só valem as opções do PRÓPRIO comando. Uma opção declarada num ancestral
|
|
17
|
+
* é aceita pelo parser, mas cai nos `opts()` do ancestral — o `.action()` do
|
|
18
|
+
* subcomando nunca a lê. Citá-la seria o mesmo conselho falso.
|
|
19
|
+
* - Sem comando conhecido (chamada fora do Commander, menu interativo, teste
|
|
20
|
+
* que chama a função direto), a flag NÃO é citada. Conselho que não dá para
|
|
21
|
+
* validar não é dado.
|
|
22
|
+
*
|
|
23
|
+
* P105 (o resto do bug_89d2497e): a mesma regra vale para o argumento
|
|
24
|
+
* posicional que um conselho cita (`<id>` é obrigatório…) e para o COMANDO
|
|
25
|
+
* alternativo que uma dica manda rodar (`neetru auth mfa-session`). O comando
|
|
26
|
+
* é validado contra a árvore de verdade do Commander: a raiz é guardada por
|
|
27
|
+
* `installCommandContext` (então vale também no menu interativo, que não passa
|
|
28
|
+
* pelo `preAction`) ou deduzida subindo do comando em execução. Sem árvore
|
|
29
|
+
* conhecida, o comando não é citado — pela mesma razão da flag.
|
|
30
|
+
*/
|
|
31
|
+
import type { Command } from 'commander';
|
|
32
|
+
/** Registra o comando que o Commander vai executar. */
|
|
33
|
+
export declare function setCurrentCommand(cmd: Command | null): void;
|
|
34
|
+
/** O comando em execução, ou `null` fora do Commander. */
|
|
35
|
+
export declare function getCurrentCommand(): Command | null;
|
|
36
|
+
/** Registra a raiz da árvore de comandos (o `program`). */
|
|
37
|
+
export declare function setRootCommand(cmd: Command | null): void;
|
|
38
|
+
/**
|
|
39
|
+
* A raiz da árvore de comandos: a do comando em execução, se houver; senão a
|
|
40
|
+
* registrada por `installCommandContext`; senão `null`.
|
|
41
|
+
*/
|
|
42
|
+
export declare function getRootCommand(): Command | null;
|
|
43
|
+
/**
|
|
44
|
+
* Liga o contexto no programa raiz: guarda a raiz e, antes de cada `.action()`,
|
|
45
|
+
* o comando que vai rodar. Chamar uma vez, no entry-point.
|
|
46
|
+
*/
|
|
47
|
+
export declare function installCommandContext(program: Command): void;
|
|
48
|
+
/**
|
|
49
|
+
* O comando declara a flag (forma longa ou curta, inclusive alias oculto)?
|
|
50
|
+
* `false` sem comando — ver o cabeçalho.
|
|
51
|
+
*/
|
|
52
|
+
export declare function commandDeclaresFlag(cmd: Command | null, flag: string): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* O comando aceita esta entrada? Flag (`--server`, `-y`) ou argumento
|
|
55
|
+
* posicional escrito como no `.command()` (`<id>`, `[serverId]`, `<args...>`).
|
|
56
|
+
* `false` sem comando.
|
|
57
|
+
*/
|
|
58
|
+
export declare function commandDeclaresInput(cmd: Command | null, input: string): boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Das flags pedidas, as que o comando declara — na ordem pedida. Base dos
|
|
61
|
+
* conselhos que oferecem mais de uma saída (`--yes (ou --force)`).
|
|
62
|
+
*/
|
|
63
|
+
export declare function declaredFlags(flags: readonly string[], cmd?: Command | null): string[];
|
|
64
|
+
/**
|
|
65
|
+
* Das entradas pedidas (flags ou `<posicional>`), as que o comando aceita —
|
|
66
|
+
* na ordem pedida.
|
|
67
|
+
*/
|
|
68
|
+
export declare function declaredInputs(inputs: readonly string[], cmd?: Command | null): string[];
|
|
69
|
+
/**
|
|
70
|
+
* O comando existe na árvore? `path` sem o `neetru` (ex.: `'auth mfa-session'`).
|
|
71
|
+
* Cada palavra casa com o nome ou um apelido de um subcomando. `false` sem
|
|
72
|
+
* árvore conhecida ou com caminho vazio.
|
|
73
|
+
*/
|
|
74
|
+
export declare function commandExists(path: string, root?: Command | null): boolean;
|
|
75
|
+
export interface FlagAdvice {
|
|
76
|
+
/** A flag que o conselho quer citar (ex.: `'--mfa-token'`). */
|
|
77
|
+
flag: string;
|
|
78
|
+
/** Texto quando o comando declara a flag — pode (e deve) citá-la. */
|
|
79
|
+
withFlag: string;
|
|
80
|
+
/** Texto quando NÃO declara — a alternativa que existe; nunca cita a flag. */
|
|
81
|
+
withoutFlag: string;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Escolhe o conselho conforme o comando em execução declare ou não a flag.
|
|
85
|
+
* `withoutFlag` citar a flag seria bug de quem chamou — o teste de cada call
|
|
86
|
+
* site trava os dois textos, e a guarda estática confere todos os call sites.
|
|
87
|
+
*/
|
|
88
|
+
export declare function adviseFlag(advice: FlagAdvice, cmd?: Command | null): string;
|
|
89
|
+
export interface CommandAdvice {
|
|
90
|
+
/** O comando que a dica quer mandar rodar, sem o `neetru` (ex.: `'auth mfa-session'`). */
|
|
91
|
+
command: string;
|
|
92
|
+
/** Texto quando o comando existe — pode citá-lo. */
|
|
93
|
+
withCommand: string;
|
|
94
|
+
/** Texto quando NÃO existe (ou a árvore é desconhecida) — nunca o cita. */
|
|
95
|
+
withoutCommand: string;
|
|
96
|
+
}
|
|
97
|
+
/** Escolhe a dica conforme o comando citado exista ou não na árvore. */
|
|
98
|
+
export declare function adviseCommand(advice: CommandAdvice, root?: Command | null): string;
|