@neetru/cli 2.32.0 → 2.32.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,14 @@ Convenção: [Keep a Changelog](https://keepachangelog.com/) + [SemVer](https://
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [2.32.1] — 2026-10-18 (comandos sem pergunta não carregam o `inquirer`)
8
+
9
+ > **Por que PATCH.** Só conserto de desempenho e testes, sem mudança de comportamento nem de saída documentada.
10
+
11
+ ### Corrigido
12
+
13
+ - **`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.
14
+
7
15
  ## [2.32.0] — 2026-10-18 (`neetru secrets`, erro tipado e conselho que só cita o que existe)
8
16
 
9
17
  > **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.
@@ -14,7 +14,7 @@
14
14
  */
15
15
  import chalk from 'chalk';
16
16
  import ora from 'ora';
17
- import inquirer from 'inquirer';
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 inquirer.prompt([
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 inquirer.prompt([
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 inquirer.prompt([
500
+ const ans = await (await carregarInquirer()).prompt([
501
501
  {
502
502
  type: 'editor',
503
503
  name: 'body',
@@ -37,16 +37,10 @@ import { exitWithCliError } from '../lib/cli-error.js';
37
37
  import { safeExit } from '../utils/safe-exit.js';
38
38
  import { runApplyPipeline, runExecSqlPipeline, buildPipelineDeps, isPipelineError, } from '../lib/db-pipeline/index.js';
39
39
  import { resolveStepUp } from '../lib/cli-write.js';
40
+ import { carregarInquirer } from '../lib/inquirer-sob-demanda.js';
40
41
  import { loadProductConfig } from '../lib/product-config.js';
41
- /**
42
- * `inquirer` sob demanda (bug_b1e9af59): ~0,5s de carga, e so os prompts
43
- * interativos usam. Era o unico import estatico dele no grafo de `neetru db`
44
- * — `db list`/`db status`/`db apply --json` (e os testes) pagavam a carga sem
45
- * nunca perguntar nada.
46
- */
47
- async function carregarInquirer() {
48
- return (await import('inquirer')).default;
49
- }
42
+ // `inquirer` sob demanda (bug_b1e9af59): só os prompts interativos o carregam,
43
+ // pelo `carregarInquirer()` único de `lib/inquirer-sob-demanda.ts` (P115).
50
44
  // ---------------------------------------------------------------------------
51
45
  // Constantes e tipos locais
52
46
  // ---------------------------------------------------------------------------
@@ -21,7 +21,7 @@ import * as fs from 'node:fs';
21
21
  import { promises as fsp } from 'node:fs';
22
22
  import { spawnSync } from 'node:child_process';
23
23
  import * as path from 'node:path';
24
- import inquirer from 'inquirer';
24
+ import { carregarInquirer } from '../lib/inquirer-sob-demanda.js';
25
25
  import chalk from 'chalk';
26
26
  import ora from 'ora';
27
27
  import { log } from '../utils/logger.js';
@@ -576,7 +576,7 @@ async function pickProduct(opts, nonInteractive, cwd) {
576
576
  if (catalog.products.length === 0) {
577
577
  deployFail(opts.json, 'no_products', 'Nenhum produto público encontrado. Crie um via `neetru publish` antes.');
578
578
  }
579
- const { slug } = await inquirer.prompt([
579
+ const { slug } = await (await carregarInquirer()).prompt([
580
580
  {
581
581
  type: 'list',
582
582
  name: 'slug',
@@ -601,7 +601,7 @@ export async function pickTarget(opts, nonInteractive, runtime) {
601
601
  // handler .ts, não um app Next.js. Recomendar "Workspace" aqui levava quem
602
602
  // aceita o padrão direto ao aviso do contrato (confirmação final do PR #74).
603
603
  const ehNext = runtime === 'nextjs';
604
- const { target } = await inquirer.prompt([
604
+ const { target } = await (await carregarInquirer()).prompt([
605
605
  {
606
606
  type: 'list',
607
607
  name: 'target',
@@ -674,7 +674,7 @@ async function pickServer(opts, nonInteractive) {
674
674
  if (nonInteractive) {
675
675
  deployFail(opts.json, 'server_required', '--server <id> é obrigatório em modo não interativo.');
676
676
  }
677
- const { serverId } = await inquirer.prompt([
677
+ const { serverId } = await (await carregarInquirer()).prompt([
678
678
  {
679
679
  type: 'list',
680
680
  name: 'serverId',
@@ -704,7 +704,7 @@ async function pickDomain(opts, nonInteractive, productSlug) {
704
704
  if (nonInteractive)
705
705
  return { domain };
706
706
  if (!domain) {
707
- const { d } = await inquirer.prompt([
707
+ const { d } = await (await carregarInquirer()).prompt([
708
708
  {
709
709
  type: 'input',
710
710
  name: 'd',
@@ -1264,7 +1264,7 @@ async function runWorkspaceDeploy(opts, productSlug, envName, cwd, nonInteractiv
1264
1264
  if (sourceSizeBytes > WORKSPACE_SOURCE_GCS_THRESHOLD_BYTES) {
1265
1265
  log.dim(` upload: via GCS (source grande — acima de ${Math.round(WORKSPACE_SOURCE_GCS_THRESHOLD_BYTES / 1024)} KB)`);
1266
1266
  }
1267
- const { ok } = await inquirer.prompt([
1267
+ const { ok } = await (await carregarInquirer()).prompt([
1268
1268
  { type: 'confirm', name: 'ok', message: 'Confirmar deploy?', default: true },
1269
1269
  ]);
1270
1270
  if (!ok) {
@@ -1730,7 +1730,7 @@ envFileTexto) {
1730
1730
  log.dim(` ambiente: ${envName}`);
1731
1731
  log.dim(' destino: cloud-run (serviço dedicado)');
1732
1732
  log.dim(` imagem: ${imageUrl}`);
1733
- const { ok } = await inquirer.prompt([
1733
+ const { ok } = await (await carregarInquirer()).prompt([
1734
1734
  { type: 'confirm', name: 'ok', message: 'Confirmar deploy?', default: true },
1735
1735
  ]);
1736
1736
  if (!ok) {
@@ -1886,7 +1886,7 @@ async function checkSchemaGate(cwd, opts) {
1886
1886
  log.error(msg);
1887
1887
  return false;
1888
1888
  }
1889
- const { continuar } = await inquirer.prompt([
1889
+ const { continuar } = await (await carregarInquirer()).prompt([
1890
1890
  {
1891
1891
  type: 'confirm',
1892
1892
  name: 'continuar',
@@ -2095,7 +2095,7 @@ export async function runDeploy(opts) {
2095
2095
  log.dim(` sha256: ${artifact.artifactSha256.slice(0, 16)}…`);
2096
2096
  // Em prod o default do confirm é FALSE — Enter não deploya produção sem
2097
2097
  // uma decisão explícita (auditoria UX 2026-09).
2098
- const { ok } = await inquirer.prompt([
2098
+ const { ok } = await (await carregarInquirer()).prompt([
2099
2099
  { type: 'confirm', name: 'ok', message: 'Confirmar deploy?', default: envName !== 'prod' },
2100
2100
  ]);
2101
2101
  if (!ok) {
@@ -7,6 +7,7 @@ import ora from 'ora';
7
7
  // todo `neetru init --yes` (CI e scripts) e pelo 1º teste do init.test.ts,
8
8
  // que estourava o timeout de 10 s com a máquina carregada.
9
9
  import { log } from '../utils/logger.js';
10
+ import { carregarInquirer } from '../lib/inquirer-sob-demanda.js';
10
11
  import { safeExit } from '../utils/safe-exit.js';
11
12
  /**
12
13
  * Inspeciona um diretório pra decidir se é projeto existente, vazio, ou
@@ -86,7 +87,7 @@ async function resolveTarget(name, cwd, opts) {
86
87
  }
87
88
  // ── Interactive ──────────────────────────────────────────────────────
88
89
  // Só aqui há pergunta: o inquirer é carregado agora (ver o topo do arquivo).
89
- const { default: inquirer } = await import('inquirer');
90
+ const inquirer = await carregarInquirer();
90
91
  console.log();
91
92
  log.info(`Onde você quer inicializar o projeto "${name}"?`);
92
93
  console.log();
@@ -9,7 +9,7 @@
9
9
  * Saída: tabela (default) ou `--json`.
10
10
  */
11
11
  import chalk from 'chalk';
12
- import inquirer from 'inquirer';
12
+ import { carregarInquirer } from '../lib/inquirer-sob-demanda.js';
13
13
  import ora from 'ora';
14
14
  import { apiRequest, CliApiError } from '../lib/api-client.js';
15
15
  import { config } from '../lib/config.js';
@@ -190,7 +190,7 @@ export async function runProductsCreate(opts) {
190
190
  const environmentsRequested = parseEnvCsv(opts.env, [...AMBIENTES_PEDIDOS_POR_PADRAO]);
191
191
  const interactive = !opts.yes && !opts.json && process.stdin.isTTY;
192
192
  if (interactive) {
193
- const answers = await inquirer.prompt([
193
+ const answers = await (await carregarInquirer()).prompt([
194
194
  {
195
195
  type: 'input',
196
196
  name: 'name',
@@ -12,6 +12,7 @@ import { apiRequest, CliApiError } from '../lib/api-client.js';
12
12
  import { requireToken, handleApiError } from '../lib/cli-read.js';
13
13
  import { confirmDestructive, printWriteResult, resolveStepUp, } from '../lib/cli-write.js';
14
14
  import { renderTable } from '../lib/render.js';
15
+ import { carregarInquirer } from '../lib/inquirer-sob-demanda.js';
15
16
  import { pickServer, promptText, promptChoice } from '../lib/pickers.js';
16
17
  import { AGENT_COMMAND_ALLOWLIST, AGENT_COMMAND_CHOICES, commandRequiresMfa, } from '../lib/agent-commands.js';
17
18
  import { log } from '../utils/logger.js';
@@ -369,7 +370,7 @@ export async function runServersForceCleanup(id, opts) {
369
370
  console.log(chalk.yellow.bold('\n⚠ FORCE-CLEANUP é IRREVERSÍVEL.'));
370
371
  console.log(chalk.dim(' Server stuck em provisioning/timeout que está bloqueando o pipeline.'));
371
372
  console.log(chalk.dim(' Audit pesado + notification all staff serão gerados.\n'));
372
- const inquirer = (await import('inquirer')).default;
373
+ const inquirer = await carregarInquirer();
373
374
  const ans = await inquirer.prompt([
374
375
  {
375
376
  type: 'input',
@@ -0,0 +1,14 @@
1
+ /**
2
+ * `inquirer` sob demanda (P115, 2026-10-18; o `db.ts` já fazia o mesmo desde
3
+ * o bug_b1e9af59, e o `init.ts` desde o P112).
4
+ *
5
+ * O `inquirer` é o pacote mais caro de carregar a frio no grafo da CLI: ~0,31 s
6
+ * sozinho e ~1 s com a máquina carregada (medido no P115). Quem o importa no
7
+ * topo faz TODO caminho do comando pagar a carga — inclusive `--json`,
8
+ * `--yes` e os não interativos, que nunca perguntam nada. Os comandos carregam
9
+ * o inquirer por aqui, só no ponto em que vão perguntar.
10
+ *
11
+ * A guarda `guarda-inquirer-sob-demanda.test.ts` impede import estático novo
12
+ * do inquirer em `src/`.
13
+ */
14
+ export declare function carregarInquirer(): Promise<typeof import('inquirer').default>;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * `inquirer` sob demanda (P115, 2026-10-18; o `db.ts` já fazia o mesmo desde
3
+ * o bug_b1e9af59, e o `init.ts` desde o P112).
4
+ *
5
+ * O `inquirer` é o pacote mais caro de carregar a frio no grafo da CLI: ~0,31 s
6
+ * sozinho e ~1 s com a máquina carregada (medido no P115). Quem o importa no
7
+ * topo faz TODO caminho do comando pagar a carga — inclusive `--json`,
8
+ * `--yes` e os não interativos, que nunca perguntam nada. Os comandos carregam
9
+ * o inquirer por aqui, só no ponto em que vão perguntar.
10
+ *
11
+ * A guarda `guarda-inquirer-sob-demanda.test.ts` impede import estático novo
12
+ * do inquirer em `src/`.
13
+ */
14
+ export async function carregarInquirer() {
15
+ const { default: inquirer } = await import('inquirer');
16
+ return inquirer;
17
+ }
18
+ //# sourceMappingURL=inquirer-sob-demanda.js.map
@@ -1,5 +1,5 @@
1
1
  import chalk from 'chalk';
2
- import inquirer from 'inquirer';
2
+ import { carregarInquirer } from './inquirer-sob-demanda.js';
3
3
  import { apiRequest } from './api-client.js';
4
4
  import { handleApiError } from './api-error.js';
5
5
  import { adviseCommand, declaredInputs } from '../cli-kit/command-context.js';
@@ -76,6 +76,8 @@ export async function resolveOrPick(opts) {
76
76
  : `Múltiplos candidatos encontrados, e este comando não recebe ${sujeito} por argumento: ` +
77
77
  `rode num terminal interativo para escolher.\nDisponiveis:\n${available}`, opts.json);
78
78
  }
79
+ // Só aqui o picker pergunta: o inquirer é carregado agora (P115).
80
+ const inquirer = await carregarInquirer();
79
81
  const choices = items.map((item) => ({
80
82
  name: opts.format(item),
81
83
  value: opts.getId(item),
@@ -107,7 +109,7 @@ export async function promptText(message, opts = {}) {
107
109
  return '';
108
110
  failInput(`Informe ${message}`, opts.json);
109
111
  }
110
- const answer = await inquirer.prompt([
112
+ const answer = await (await carregarInquirer()).prompt([
111
113
  {
112
114
  type: 'input',
113
115
  name: 'value',
@@ -131,7 +133,7 @@ export async function promptChoice(message, choices, opts = {}) {
131
133
  return opts.default;
132
134
  failInput(`Escolha ${message}`, opts.json);
133
135
  }
134
- const answer = await inquirer.prompt([
136
+ const answer = await (await carregarInquirer()).prompt([
135
137
  {
136
138
  type: 'list',
137
139
  name: 'picked',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neetru/cli",
3
- "version": "2.32.0",
3
+ "version": "2.32.1",
4
4
  "type": "module",
5
5
  "description": "Neetru Developer Kit — scaffold, AI assistant, publish, build e deploy de produtos SaaS Neetru via Core (VM-based agents)",
6
6
  "keywords": [