@neetru/cli 2.32.0 → 2.33.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 +26 -0
- package/dist/commands/bug.js +4 -4
- package/dist/commands/db-frontdoor.d.ts +116 -0
- package/dist/commands/db-frontdoor.js +567 -0
- package/dist/commands/db-move.d.ts +5 -0
- package/dist/commands/db-move.js +27 -0
- package/dist/commands/db.js +3 -9
- package/dist/commands/deploy.js +9 -9
- package/dist/commands/init.js +2 -1
- package/dist/commands/products.js +2 -2
- package/dist/commands/servers.d.ts +29 -0
- package/dist/commands/servers.js +110 -2
- package/dist/index.js +56 -0
- package/dist/lib/api-client.d.ts +8 -1
- package/dist/lib/api-client.js +18 -2
- package/dist/lib/inquirer-sob-demanda.d.ts +14 -0
- package/dist/lib/inquirer-sob-demanda.js +18 -0
- package/dist/lib/pickers.js +5 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,32 @@ Convenção: [Keep a Changelog](https://keepachangelog.com/) + [SemVer](https://
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [2.33.0] — 2026-10-19 (`neetru db frontdoor` e confirmação da VM na limpeza forçada)
|
|
8
|
+
|
|
9
|
+
> **Por que MINOR.** Entram um comando novo (`neetru db frontdoor enable|status|disable`) e uma flag nova (`neetru servers force-cleanup --confirm-instance-id`), sem quebrar comportamento existente. Os dois só funcionam contra um Core com o core#727 e o core#732 no ar; antes disso a rota da porta de entrada responde 404, e a limpeza forçada ignora a confirmação.
|
|
10
|
+
|
|
11
|
+
### Adicionado
|
|
12
|
+
|
|
13
|
+
- **`neetru db frontdoor enable|status|disable` — porta de entrada privada de um banco Postgres em VM para um app em OUTRA VM da rede interna, sem mover o banco** (Core #727, bug_5c0e7738). Até aqui o único caminho era o `neetru db move`, que recusa destino igual à origem — um banco que já estava na VM certa não tinha como atender um app noutra VM (caso do Zelo: banco na snee-003-db, app indo para a snee-002-multi).
|
|
14
|
+
- `neetru db frontdoor enable <dbId> --app-server <serverId> --reason "<motivo>"` pede ao Core que prepare o PgBouncer na VM do banco, teste a conexão a partir da VM do app, teste o login pelo IP interno na própria VM do banco e só então grave o novo endereço. Falha em qualquer passo não muda nada para o app. Enquanto o Core trabalha, a CLI mostra o passo em que ele está (1 de 3, 2 de 3, 3 de 3). A operação só anda enquanto o comando roda: se o Core devolver a operação ainda em curso, ou a conexão cair, a CLI chama de novo e o Core retoma a mesma operação; repetir o comando depois também retoma. `--dry-run` só confere os requisitos, sem alterar nada e sem MFA (inclusive o novo "VM do app é da frota da Neetru"). A confirmação avisa que o PgBouncer da VM do banco reinicia uma vez. Só o endereço gravado no cadastro do banco faz o comando dizer "ligada": o vencimento do certificado pode aparecer antes, ou depois de uma falha, e não conta.
|
|
15
|
+
- `neetru db frontdoor status <dbId>` mostra se a porta está ligada (e em que IP), quando o certificado vence e a última operação, com o erro e o que fazer; uma operação parada no meio vem com o comando para retomar ou cancelar.
|
|
16
|
+
- `neetru db frontdoor disable <dbId> --reason "<motivo>"` volta o app ao endereço local da VM do banco e cancela uma ligação em andamento. **Desligar uma porta ligada (o desfazer normal) exige `--force`**, e também quando não se sabe em que VM o app roda: o app volta ao endereço local da VM do banco e, se ainda roda em outra VM, fica sem banco. A CLI explica isso antes de pedir MFA e, com `--force`, pede confirmação forte (digitar o id do banco; em script, `--yes` junto). Cancelar uma ligação em andamento que ainda não gravou nada não precisa de `--force`. Um endereço gravado por outra origem (por exemplo uma mudança de VM do banco) nunca é tirado por aqui: a CLI avisa que ele ficou (`host_kept`) ou que nem `--force` o desfaz (`FRONT_DOOR_HOST_NOT_OWNED`).
|
|
17
|
+
- MFA como no `db move`: `--mfa-token` ou a sessão de 1h de `neetru auth mfa-session`; sessão recusada pelo Core leva a um pedido de TOTP fresco, uma vez. O TOTP é de uso único, então nas retomadas o código já usado não é reenviado (vale a sessão, ou um TOTP novo).
|
|
18
|
+
- Cada código `FRONT_DOOR_*` da rota tem mensagem própria em português (a do Core primeiro, depois o que fazer); no `--json`, `{ ok:false, error, message, guidance }`.
|
|
19
|
+
- `neetru db move` passa a explicar a recusa `RELOCATE_FRONT_DOOR_OP_IN_PROGRESS` (409): há uma ligação da porta de entrada do banco em andamento — esperar ou cancelá-la com `neetru db frontdoor disable` e rodar a mudança de novo.
|
|
20
|
+
- Conferência opt-in contra o Core de verdade (`NEETRU_CORE_DIR`, a mesma do manifesto de MFA): todo código que a CLI trata existe no código das rotas do Core, todo `FRONT_DOOR_*` do Core tem mensagem na CLI e todo requisito do preflight tem nome legível.
|
|
21
|
+
- **Depende do Core #727** (rotas `POST|GET /api/cli/v1/db/{id}/frontdoor` e `POST /api/cli/v1/db/{id}/frontdoor/disable`); antes do deploy dele o comando responde 404.
|
|
22
|
+
|
|
23
|
+
- **`neetru servers force-cleanup --confirm-instance-id <id>` — confirmar a VM que a limpeza forçada vai apagar** (core#732, bug_90ec21cd). O Core passou a só apagar a VM de um servidor quando ela é provadamente a dele: nome de VM é reaproveitado (uma VM recriada com o mesmo nome já foi apagada no lugar da velha). Quando o servidor não tem o id da VM registrado e existe uma VM com o mesmo nome, o Core recusa (`vm_sem_externalid`) e diz o id que viu e o comando `gcloud` para conferir; o operador confere e repete o comando com `--confirm-instance-id <id que conferiu>`. A CLI mostra, em português, a mensagem de cada recusa da guarda (`vm_sem_externalid`, `vm_confirmacao_nao_confere`, `vm_id_divergente`, `vm_leitura_falhou` — esta quando o Google Cloud não pôde ser lido — e `vm_nome_em_uso_por_outro_servidor`, quando outro servidor registrado e ativo usa uma VM com o mesmo nome e zona: nesse caso **não adianta confirmar o id**, o Core recusa mesmo com `--confirm-instance-id`, e a CLI lista os outros servidores) com o que fazer. Quando o Core manda os detalhes da recusa, a CLI mostra o id visto, a criação da VM ao lado da criação do servidor (a VM do próprio servidor nasce segundos depois dele; diferença grande indica VM recriada) e o comando de conferência. No `--json` devolve `{ ok:false, error, message, guidance, details? }`. `--confirm-instance-id` vazio ou só com espaços é recusado (`invalid_param`), sem ir ao Core. O `CliApiError` passa a carregar em `details` os campos extras do erro canônico `{ error: { code, message, … } }`. **A CLI nunca preenche o id a partir da resposta nem tenta de novo sozinha**: quem confere digita. Id fora do formato numérico é recusado antes de ir ao Core. No sucesso, a CLI diz como a VM foi identificada (pelo id registrado, pelo id confirmado, ou que a VM com aquele nome era de outro servidor e não foi tocada). **Depende do core#732**: antes do deploy dele o Core ignora a flag e ainda apaga a VM pelo nome, sem conferir a identidade (o comportamento antigo).
|
|
24
|
+
|
|
25
|
+
## [2.32.1] — 2026-10-18 (comandos sem pergunta não carregam o `inquirer`)
|
|
26
|
+
|
|
27
|
+
> **Por que PATCH.** Só conserto de desempenho e testes, sem mudança de comportamento nem de saída documentada.
|
|
28
|
+
|
|
29
|
+
### Corrigido
|
|
30
|
+
|
|
31
|
+
- **`neetru deploy`, `neetru bug`, `neetru products` e os pickers (`tenants`, `servers`, `deployments`, `products db`…) não carregam mais o `inquirer` quando não vão perguntar nada** (P115). Os quatro módulos o importavam no topo, então todo `--json`, `--yes` e caminho não interativo pagava o import a frio dele: ~0,31 s sozinho e ~1 s com a máquina carregada, o mais caro do grafo da CLI. Agora o inquirer é carregado só no ponto da pergunta, por um carregador único (`src/lib/inquirer-sob-demanda.ts`), que `db`, `init` e `servers` também passam a usar no lugar das cópias próprias. Uma guarda nova reprova o módulo de `src/` que carregar o inquirer ou um `@inquirer/*` no topo, direto (import estático, `import()` ou `carregarInquirer()` no corpo do módulo, inclusive dentro de IIFE) ou por outro módulo que o carregue (transitivo), reprova `import('inquirer')` e `import('@inquirer/…')` fora do carregador e confere em runtime que importar os consumidores do carregador e do picker não o carrega. `ui`, `chat-menu`, `archive`, `auth`, `vm`, `new`, `ai` (por `auth`) e o `index.ts` (o menu do `neetru` sem argumentos, por `ui`) ainda o carregam: dívida declarada na guarda, com teto exato (entrada nova reprova). Nos testes: 27 arquivos cujo 1º teste carregava o comando a frio, dentro do corpo, ganharam aquecimento num `beforeAll` com `TIMEOUT_AQUECIMENTO_MS` (na suíte completa, o 1º teste passou de 0,6–2,1 s para 3–236 ms em 25 deles; em `build-standalone-packaging` e `build-env-local-exclusion` fica em 0,37–0,64 s, o nível dos demais testes de cada um, que rodam o `tar` de verdade), e outra guarda impede teste novo de importar comando no corpo sem aquecê-lo (dívida de 43 arquivos, também com teto exato). Sem mudança de comportamento.
|
|
32
|
+
|
|
7
33
|
## [2.32.0] — 2026-10-18 (`neetru secrets`, erro tipado e conselho que só cita o que existe)
|
|
8
34
|
|
|
9
35
|
> **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.
|
package/dist/commands/bug.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import chalk from 'chalk';
|
|
16
16
|
import ora from 'ora';
|
|
17
|
-
import
|
|
17
|
+
import { carregarInquirer } from '../lib/inquirer-sob-demanda.js';
|
|
18
18
|
import { apiRequest } from '../lib/api-client.js';
|
|
19
19
|
import { requireToken, handleApiError } from '../lib/cli-read.js';
|
|
20
20
|
import { printWriteResult } from '../lib/cli-write.js';
|
|
@@ -169,7 +169,7 @@ export async function runBugReport(opts) {
|
|
|
169
169
|
let severity = opts.severity?.trim() ?? '';
|
|
170
170
|
const interactive = !opts.json && (!title || !description);
|
|
171
171
|
if (interactive) {
|
|
172
|
-
const answers = await
|
|
172
|
+
const answers = await (await carregarInquirer()).prompt([
|
|
173
173
|
{
|
|
174
174
|
type: 'input',
|
|
175
175
|
name: 'title',
|
|
@@ -423,7 +423,7 @@ export async function runBugResolve(id, opts) {
|
|
|
423
423
|
let status = (opts.status ?? 'resolved').trim();
|
|
424
424
|
let duplicateOf = opts.duplicateOf?.trim() ?? '';
|
|
425
425
|
if (!opts.json && !summary) {
|
|
426
|
-
const ans = await
|
|
426
|
+
const ans = await (await carregarInquirer()).prompt([
|
|
427
427
|
{
|
|
428
428
|
type: 'editor',
|
|
429
429
|
name: 'summary',
|
|
@@ -497,7 +497,7 @@ export async function runBugComment(id, opts) {
|
|
|
497
497
|
}
|
|
498
498
|
let body = opts.body?.trim() ?? '';
|
|
499
499
|
if (!opts.json && !body) {
|
|
500
|
-
const ans = await
|
|
500
|
+
const ans = await (await carregarInquirer()).prompt([
|
|
501
501
|
{
|
|
502
502
|
type: 'editor',
|
|
503
503
|
name: 'body',
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
export type FrontDoorState = 'planning' | 'preparing' | 'verifying_app' | 'verifying_local' | 'enabled' | 'failed' | 'cancelled' | 'disabled';
|
|
2
|
+
export interface FrontDoorLedger {
|
|
3
|
+
serverId: string | null;
|
|
4
|
+
frontDoorHost: string | null;
|
|
5
|
+
frontDoorCertNotAfter: string | null;
|
|
6
|
+
}
|
|
7
|
+
export interface FrontDoorOp {
|
|
8
|
+
opId: string;
|
|
9
|
+
state: FrontDoorState;
|
|
10
|
+
dbServerId: string;
|
|
11
|
+
appServerId: string;
|
|
12
|
+
host: string;
|
|
13
|
+
port: number;
|
|
14
|
+
allowedClientCidr: string | null;
|
|
15
|
+
frontDoorCertNotAfter: string | null;
|
|
16
|
+
preparedAt: string | null;
|
|
17
|
+
appVerifiedAt: string | null;
|
|
18
|
+
localVerifiedAt: string | null;
|
|
19
|
+
enabledAt: string | null;
|
|
20
|
+
failedAt: string | null;
|
|
21
|
+
cancelledAt: string | null;
|
|
22
|
+
disabledAt: string | null;
|
|
23
|
+
lastError: {
|
|
24
|
+
code: string;
|
|
25
|
+
message: string;
|
|
26
|
+
at: string;
|
|
27
|
+
} | null;
|
|
28
|
+
reason: string;
|
|
29
|
+
updatedAt: string;
|
|
30
|
+
}
|
|
31
|
+
export interface FrontDoorCheck {
|
|
32
|
+
name: string;
|
|
33
|
+
ok: boolean;
|
|
34
|
+
detail?: string;
|
|
35
|
+
}
|
|
36
|
+
export interface FrontDoorEnableResponse {
|
|
37
|
+
ok: true;
|
|
38
|
+
dbId: string;
|
|
39
|
+
dryRun: boolean;
|
|
40
|
+
done: boolean;
|
|
41
|
+
alreadyEnabled: boolean;
|
|
42
|
+
state: FrontDoorState;
|
|
43
|
+
preflight: {
|
|
44
|
+
ok: boolean;
|
|
45
|
+
checks: FrontDoorCheck[];
|
|
46
|
+
};
|
|
47
|
+
op: FrontDoorOp | null;
|
|
48
|
+
ledger: FrontDoorLedger | null;
|
|
49
|
+
}
|
|
50
|
+
export interface FrontDoorStatusResponse {
|
|
51
|
+
ok: true;
|
|
52
|
+
dbId: string;
|
|
53
|
+
ledger: FrontDoorLedger;
|
|
54
|
+
op: FrontDoorOp | null;
|
|
55
|
+
}
|
|
56
|
+
export interface FrontDoorDisableResponse {
|
|
57
|
+
ok: true;
|
|
58
|
+
dbId: string;
|
|
59
|
+
/**
|
|
60
|
+
* `host_kept` (Core #727, rodada 2): a ligação em curso foi cancelada, mas o
|
|
61
|
+
* endereço gravado no cadastro não é desta porta de entrada e FICOU.
|
|
62
|
+
*/
|
|
63
|
+
outcome: 'disabled' | 'already_disabled' | 'host_kept';
|
|
64
|
+
previousFrontDoorHost: string | null;
|
|
65
|
+
opTransition: 'cancelled' | 'disabled' | null;
|
|
66
|
+
opId: string | null;
|
|
67
|
+
ledger: FrontDoorLedger | null;
|
|
68
|
+
}
|
|
69
|
+
/** O que cada estado quer dizer, em português simples. */
|
|
70
|
+
export declare const STATE_LABELS: Record<FrontDoorState, string>;
|
|
71
|
+
export declare function isInFlight(state: FrontDoorState | undefined): boolean;
|
|
72
|
+
/** Nome legível de cada requisito do `--dry-run` (o Core manda o nome técnico). */
|
|
73
|
+
export declare const CHECK_LABELS: Record<string, string>;
|
|
74
|
+
/**
|
|
75
|
+
* O que fazer para cada código `FRONT_DOOR_*` que a rota devolve. A mensagem
|
|
76
|
+
* do Core (com VMs, IPs e o motivo do agente) vem antes; isto é o "e agora?".
|
|
77
|
+
*/
|
|
78
|
+
export declare const FRONT_DOOR_GUIDANCE: Record<string, string>;
|
|
79
|
+
/** Mensagem do Core primeiro (dados reais), depois o que fazer. Pura. */
|
|
80
|
+
export declare function describeFrontDoorError(code: string, serverMessage: string): string;
|
|
81
|
+
export interface DbFrontdoorEnableOptions {
|
|
82
|
+
appServer?: string;
|
|
83
|
+
reason?: string;
|
|
84
|
+
dryRun?: boolean;
|
|
85
|
+
yes?: boolean;
|
|
86
|
+
mfaToken?: string;
|
|
87
|
+
json?: boolean;
|
|
88
|
+
/** Somente testes: intervalo da consulta de progresso (ms). Default 3000. */
|
|
89
|
+
pollIntervalMs?: number;
|
|
90
|
+
/** Somente testes: prazo total somando as voltas (ms). Default 20 min. */
|
|
91
|
+
timeoutMs?: number;
|
|
92
|
+
/** Somente testes: espera de cada POST no Core (s). Default 240 (o máximo da rota). */
|
|
93
|
+
waitSeconds?: number;
|
|
94
|
+
}
|
|
95
|
+
export declare function runDbFrontdoorEnable(dbId: string, opts?: DbFrontdoorEnableOptions): Promise<void>;
|
|
96
|
+
export declare function runDbFrontdoorStatus(dbId: string, opts?: {
|
|
97
|
+
json?: boolean;
|
|
98
|
+
}): Promise<void>;
|
|
99
|
+
export interface DbFrontdoorDisableOptions {
|
|
100
|
+
reason?: string;
|
|
101
|
+
force?: boolean;
|
|
102
|
+
yes?: boolean;
|
|
103
|
+
mfaToken?: string;
|
|
104
|
+
json?: boolean;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Desligar (desfazer) — regras do Core #727 (rodada 2), decididas na transação:
|
|
108
|
+
* - porta LIGADA por este comando pra um app noutra VM (o desfazer normal) ⇒
|
|
109
|
+
* exige `--force`; idem quando não se sabe em que VM o app roda;
|
|
110
|
+
* - ligação em curso sem endereço gravado ⇒ só cancela, sem `--force`;
|
|
111
|
+
* - endereço gravado por OUTRA origem (ex.: mudança de VM do banco) nunca é
|
|
112
|
+
* tirado por aqui: `host_kept` (cancelou a ligação em curso) ou
|
|
113
|
+
* `FRONT_DOOR_HOST_NOT_OWNED` (409, nem com `--force`).
|
|
114
|
+
* Com `--force` a confirmação é forte: digitar o id do banco (ou `--yes`).
|
|
115
|
+
*/
|
|
116
|
+
export declare function runDbFrontdoorDisable(dbId: string, opts?: DbFrontdoorDisableOptions): Promise<void>;
|