@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.
Files changed (259) hide show
  1. package/CHANGELOG.md +141 -0
  2. package/README.md +63 -17
  3. package/dist/cli-kit/choices.d.ts +33 -0
  4. package/dist/cli-kit/choices.js +32 -0
  5. package/dist/cli-kit/command-context.d.ts +98 -0
  6. package/dist/cli-kit/command-context.js +108 -0
  7. package/dist/cli-kit/errors.d.ts +79 -0
  8. package/dist/cli-kit/errors.js +94 -0
  9. package/dist/cli-kit/index.d.ts +8 -0
  10. package/dist/cli-kit/index.js +8 -0
  11. package/dist/cli-kit/suggest.d.ts +74 -0
  12. package/dist/cli-kit/suggest.js +207 -0
  13. package/dist/commands/admin/oidc-login.js +1 -1
  14. package/dist/commands/admin/sdk-credential.js +1 -1
  15. package/dist/commands/autocomplete.d.ts +1 -1
  16. package/dist/commands/autocomplete.js +1 -0
  17. package/dist/commands/bug.d.ts +59 -0
  18. package/dist/commands/bug.js +114 -2
  19. package/dist/commands/build.d.ts +24 -1
  20. package/dist/commands/build.js +195 -73
  21. package/dist/commands/chat.d.ts +94 -1
  22. package/dist/commands/chat.js +258 -26
  23. package/dist/commands/customers.js +1 -1
  24. package/dist/commands/db-move.js +3 -3
  25. package/dist/commands/db.js +17 -24
  26. package/dist/commands/deploy.d.ts +136 -2
  27. package/dist/commands/deploy.js +547 -69
  28. package/dist/commands/deployments.js +11 -6
  29. package/dist/commands/dev-context.js +15 -5
  30. package/dist/commands/dev-up.d.ts +7 -5
  31. package/dist/commands/dev-up.js +24 -23
  32. package/dist/commands/docs-produto.d.ts +90 -0
  33. package/dist/commands/docs-produto.js +384 -0
  34. package/dist/commands/doctor.js +36 -1
  35. package/dist/commands/domains.js +85 -4
  36. package/dist/commands/env.d.ts +9 -3
  37. package/dist/commands/env.js +23 -3
  38. package/dist/commands/infra-read.js +2 -2
  39. package/dist/commands/init.d.ts +2 -0
  40. package/dist/commands/init.js +14 -1
  41. package/dist/commands/login.js +35 -8
  42. package/dist/commands/logs.js +12 -4
  43. package/dist/commands/mobile.js +33 -9
  44. package/dist/commands/new.d.ts +31 -1
  45. package/dist/commands/new.js +94 -4
  46. package/dist/commands/open.js +16 -6
  47. package/dist/commands/ops.js +1 -1
  48. package/dist/commands/products-db.d.ts +1 -0
  49. package/dist/commands/products-db.js +41 -8
  50. package/dist/commands/products.d.ts +17 -3
  51. package/dist/commands/products.js +84 -16
  52. package/dist/commands/promote.js +30 -5
  53. package/dist/commands/secrets.d.ts +91 -0
  54. package/dist/commands/secrets.js +471 -0
  55. package/dist/commands/servers.js +6 -4
  56. package/dist/commands/status.js +2 -2
  57. package/dist/commands/support.js +5 -3
  58. package/dist/commands/tenants.js +28 -16
  59. package/dist/commands/upgrade.d.ts +9 -0
  60. package/dist/commands/upgrade.js +50 -1
  61. package/dist/commands/vm-memory.js +1 -0
  62. package/dist/commands/webhooks.d.ts +2 -0
  63. package/dist/commands/webhooks.js +9 -3
  64. package/dist/commands/workspaces.js +42 -11
  65. package/dist/index.js +142 -20
  66. package/dist/lib/api-client.d.ts +22 -1
  67. package/dist/lib/api-client.js +79 -8
  68. package/dist/lib/api-error.d.ts +14 -0
  69. package/dist/lib/api-error.js +107 -46
  70. package/dist/lib/build-targets.d.ts +127 -0
  71. package/dist/lib/build-targets.js +348 -0
  72. package/dist/lib/cli-error.d.ts +13 -0
  73. package/dist/lib/cli-error.js +24 -0
  74. package/dist/lib/cli-write.d.ts +43 -9
  75. package/dist/lib/cli-write.js +154 -45
  76. package/dist/lib/compose-generator.d.ts +13 -9
  77. package/dist/lib/compose-generator.js +28 -60
  78. package/dist/lib/config-schema.d.ts +36 -2
  79. package/dist/lib/config-schema.js +11 -0
  80. package/dist/lib/db-local/db-json.d.ts +1 -1
  81. package/dist/lib/db-local/db-json.js +13 -5
  82. package/dist/lib/db-local/env.d.ts +15 -3
  83. package/dist/lib/db-local/env.js +37 -10
  84. package/dist/lib/db-pipeline/build-deps.d.ts +5 -1
  85. package/dist/lib/db-pipeline/build-deps.js +23 -3
  86. package/dist/lib/db-pipeline/types.d.ts +6 -2
  87. package/dist/lib/db-pipeline/types.js +0 -18
  88. package/dist/lib/deploy-target-contract.d.ts +152 -0
  89. package/dist/lib/deploy-target-contract.js +411 -0
  90. package/dist/lib/env-file-do-build.d.ts +19 -0
  91. package/dist/lib/env-file-do-build.js +44 -0
  92. package/dist/lib/envs.d.ts +108 -0
  93. package/dist/lib/envs.js +127 -0
  94. package/dist/lib/fs-seguro.d.ts +63 -0
  95. package/dist/lib/fs-seguro.js +108 -0
  96. package/dist/lib/mobile-local/mobile-json.js +10 -4
  97. package/dist/lib/pickers.d.ts +44 -7
  98. package/dist/lib/pickers.js +60 -4
  99. package/dist/lib/product-docs/check.d.ts +62 -0
  100. package/dist/lib/product-docs/check.js +489 -0
  101. package/dist/lib/product-docs/config.d.ts +66 -0
  102. package/dist/lib/product-docs/config.js +234 -0
  103. package/dist/lib/product-docs/dates.d.ts +17 -0
  104. package/dist/lib/product-docs/dates.js +41 -0
  105. package/dist/lib/product-docs/frontmatter.d.ts +43 -0
  106. package/dist/lib/product-docs/frontmatter.js +302 -0
  107. package/dist/lib/product-docs/git.d.ts +16 -0
  108. package/dist/lib/product-docs/git.js +92 -0
  109. package/dist/lib/product-docs/markdown.d.ts +27 -0
  110. package/dist/lib/product-docs/markdown.js +98 -0
  111. package/dist/lib/product-docs/ops.d.ts +133 -0
  112. package/dist/lib/product-docs/ops.js +975 -0
  113. package/dist/lib/product-docs/structure.d.ts +61 -0
  114. package/dist/lib/product-docs/structure.js +239 -0
  115. package/dist/lib/product-docs/sync.d.ts +107 -0
  116. package/dist/lib/product-docs/sync.js +322 -0
  117. package/dist/lib/product-docs/taxonomy.d.ts +46 -0
  118. package/dist/lib/product-docs/taxonomy.js +163 -0
  119. package/dist/lib/product-docs/templates.d.ts +53 -0
  120. package/dist/lib/product-docs/templates.js +245 -0
  121. package/dist/lib/public-secret-scan.d.ts +117 -0
  122. package/dist/lib/public-secret-scan.js +771 -0
  123. package/dist/lib/render.d.ts +16 -0
  124. package/dist/lib/render.js +34 -4
  125. package/dist/lib/segredo-produto.d.ts +124 -0
  126. package/dist/lib/segredo-produto.js +336 -0
  127. package/dist/lib/texto-utf8.d.ts +96 -0
  128. package/dist/lib/texto-utf8.js +196 -0
  129. package/package.json +2 -1
  130. package/dist/cli-kit/format.js.map +0 -1
  131. package/dist/cli-kit/glyphs.js.map +0 -1
  132. package/dist/cli-kit/index.js.map +0 -1
  133. package/dist/cli-kit/palette.js.map +0 -1
  134. package/dist/commands/add.js.map +0 -1
  135. package/dist/commands/admin/iam-check.js.map +0 -1
  136. package/dist/commands/admin/oidc-login.js.map +0 -1
  137. package/dist/commands/admin/sdk-credential.js.map +0 -1
  138. package/dist/commands/agent-release.js.map +0 -1
  139. package/dist/commands/agent-write.js.map +0 -1
  140. package/dist/commands/ai-models.js.map +0 -1
  141. package/dist/commands/ai.js.map +0 -1
  142. package/dist/commands/api-catalog.js.map +0 -1
  143. package/dist/commands/archive.js.map +0 -1
  144. package/dist/commands/artifact-registry.js.map +0 -1
  145. package/dist/commands/audit.js.map +0 -1
  146. package/dist/commands/auth.js.map +0 -1
  147. package/dist/commands/autocomplete.js.map +0 -1
  148. package/dist/commands/billing.js.map +0 -1
  149. package/dist/commands/bootstrap.js.map +0 -1
  150. package/dist/commands/bug.js.map +0 -1
  151. package/dist/commands/build.js.map +0 -1
  152. package/dist/commands/chat-menu.js.map +0 -1
  153. package/dist/commands/chat.js.map +0 -1
  154. package/dist/commands/cloud-run.js.map +0 -1
  155. package/dist/commands/code.js.map +0 -1
  156. package/dist/commands/config.js.map +0 -1
  157. package/dist/commands/customers.js.map +0 -1
  158. package/dist/commands/db-move.js.map +0 -1
  159. package/dist/commands/db.js.map +0 -1
  160. package/dist/commands/deploy.js.map +0 -1
  161. package/dist/commands/deployments.js.map +0 -1
  162. package/dist/commands/dev-context.js.map +0 -1
  163. package/dist/commands/dev-up.js.map +0 -1
  164. package/dist/commands/dev.js.map +0 -1
  165. package/dist/commands/docs.js.map +0 -1
  166. package/dist/commands/doctor.js.map +0 -1
  167. package/dist/commands/domains.js.map +0 -1
  168. package/dist/commands/dr.js.map +0 -1
  169. package/dist/commands/env.js.map +0 -1
  170. package/dist/commands/hosting-write.js.map +0 -1
  171. package/dist/commands/infra-read.js.map +0 -1
  172. package/dist/commands/init.js.map +0 -1
  173. package/dist/commands/login.js.map +0 -1
  174. package/dist/commands/logout.js.map +0 -1
  175. package/dist/commands/logs.js.map +0 -1
  176. package/dist/commands/mobile.js.map +0 -1
  177. package/dist/commands/mocks.js.map +0 -1
  178. package/dist/commands/new.js.map +0 -1
  179. package/dist/commands/open.js.map +0 -1
  180. package/dist/commands/ops.js.map +0 -1
  181. package/dist/commands/products-db.js.map +0 -1
  182. package/dist/commands/products.js.map +0 -1
  183. package/dist/commands/promote.js.map +0 -1
  184. package/dist/commands/publish.js.map +0 -1
  185. package/dist/commands/schema.js.map +0 -1
  186. package/dist/commands/servers.js.map +0 -1
  187. package/dist/commands/smoke.js.map +0 -1
  188. package/dist/commands/status.js.map +0 -1
  189. package/dist/commands/support.js.map +0 -1
  190. package/dist/commands/surface-status.js.map +0 -1
  191. package/dist/commands/tenants.js.map +0 -1
  192. package/dist/commands/ui.js.map +0 -1
  193. package/dist/commands/upgrade.js.map +0 -1
  194. package/dist/commands/validate.js.map +0 -1
  195. package/dist/commands/vm-memory.js.map +0 -1
  196. package/dist/commands/vm.js.map +0 -1
  197. package/dist/commands/webhooks.js.map +0 -1
  198. package/dist/commands/whoami.js.map +0 -1
  199. package/dist/commands/workspaces.js.map +0 -1
  200. package/dist/index.js.map +0 -1
  201. package/dist/lib/agent-artifact-url.js.map +0 -1
  202. package/dist/lib/agent-commands.js.map +0 -1
  203. package/dist/lib/ai/auth-discovery.js.map +0 -1
  204. package/dist/lib/ai/context.js.map +0 -1
  205. package/dist/lib/ai/orchestrator.js.map +0 -1
  206. package/dist/lib/api-client.js.map +0 -1
  207. package/dist/lib/api-error.js.map +0 -1
  208. package/dist/lib/auth.js.map +0 -1
  209. package/dist/lib/cli-read.js.map +0 -1
  210. package/dist/lib/cli-write.js.map +0 -1
  211. package/dist/lib/code-agent-budget.js.map +0 -1
  212. package/dist/lib/code-agent-files.js.map +0 -1
  213. package/dist/lib/code-agent-gateway.js.map +0 -1
  214. package/dist/lib/code-agent-git-tools.js.map +0 -1
  215. package/dist/lib/code-agent-protocol.js.map +0 -1
  216. package/dist/lib/code-agent-session.js.map +0 -1
  217. package/dist/lib/code-agent-settings.js.map +0 -1
  218. package/dist/lib/code-agent-skills.js.map +0 -1
  219. package/dist/lib/code-agent-subagents.js.map +0 -1
  220. package/dist/lib/code-agent-tools.js.map +0 -1
  221. package/dist/lib/code-agent-tui.js.map +0 -1
  222. package/dist/lib/code-agent-types.js.map +0 -1
  223. package/dist/lib/code-agent-ui.js.map +0 -1
  224. package/dist/lib/code-agent-web-tools.js.map +0 -1
  225. package/dist/lib/code-agent-write-tools.js.map +0 -1
  226. package/dist/lib/compose-generator.js.map +0 -1
  227. package/dist/lib/config-schema.js.map +0 -1
  228. package/dist/lib/config.js.map +0 -1
  229. package/dist/lib/db-local/db-json.js.map +0 -1
  230. package/dist/lib/db-local/env.js.map +0 -1
  231. package/dist/lib/db-local/fingerprint.js.map +0 -1
  232. package/dist/lib/db-local/index.js.map +0 -1
  233. package/dist/lib/db-pipeline/build-deps.js.map +0 -1
  234. package/dist/lib/db-pipeline/errors.js.map +0 -1
  235. package/dist/lib/db-pipeline/index.js.map +0 -1
  236. package/dist/lib/db-pipeline/pipeline.js.map +0 -1
  237. package/dist/lib/db-pipeline/rehearse.js.map +0 -1
  238. package/dist/lib/db-pipeline/types.js.map +0 -1
  239. package/dist/lib/dev-local-context.js.map +0 -1
  240. package/dist/lib/iam-preflight.js.map +0 -1
  241. package/dist/lib/mobile-local/index.js.map +0 -1
  242. package/dist/lib/mobile-local/mobile-json.js.map +0 -1
  243. package/dist/lib/pickers.js.map +0 -1
  244. package/dist/lib/product-config.js.map +0 -1
  245. package/dist/lib/render.js.map +0 -1
  246. package/dist/lib/schema-cache.js.map +0 -1
  247. package/dist/lib/sse.js.map +0 -1
  248. package/dist/lib/telemetry.js.map +0 -1
  249. package/dist/lib/token-resolver.js.map +0 -1
  250. package/dist/lib/url-allowlist.js.map +0 -1
  251. package/dist/lib/version-check.js.map +0 -1
  252. package/dist/utils/logger.js.map +0 -1
  253. package/dist/utils/logo.js.map +0 -1
  254. package/dist/utils/safe-exit.js.map +0 -1
  255. package/dist/utils/spinner-registry.js.map +0 -1
  256. package/dist/version.js.map +0 -1
  257. package/templates/dev/Dockerfile.agent +0 -39
  258. package/templates/dev/agent-entrypoint.sh +0 -41
  259. package/templates/dev/docker-compose.dev.yml +0 -63
@@ -4,7 +4,23 @@ export interface TableColumn<T> {
4
4
  value: (row: T) => string;
5
5
  /** Largura máxima da coluna (trunca com `…`). Default: 40. */
6
6
  maxWidth?: number;
7
+ /**
8
+ * A célula é um IDENTIFICADOR que o usuário copia pra outro comando
9
+ * (`show`, `get`, `claim`…). Nunca é cortada — `maxWidth` é ignorado.
10
+ *
11
+ * 2026-10-12: as colunas ID tinham `maxWidth` de 18 a 36, mas `bug_` tem 36
12
+ * caracteres, `tkt_<uuid>` 40 e `tenant_<uuid>` 43. A tabela imprimia um id
13
+ * que NÃO EXISTE (`bug_a4f84c1b0184448cbb0…`), o usuário copiava, e o comando
14
+ * seguinte respondia "não encontrado" — negando a existência de algo que
15
+ * existe. Um id cortado não é um id mais curto; é um id falso.
16
+ *
17
+ * Célula com FORMA de identificador já não é cortada mesmo sem esta opção
18
+ * (ver `pareceIdentificador`). A opção continua valendo para deixar a
19
+ * intenção explícita na coluna e para um id que um dia não tenha essa forma.
20
+ */
21
+ identificador?: boolean;
7
22
  }
23
+ export declare function pareceIdentificador(celula: string): boolean;
8
24
  /**
9
25
  * Imprime uma tabela. Quando `rows` está vazio, imprime uma linha discreta.
10
26
  */
@@ -5,6 +5,30 @@
5
5
  * largura máxima de cada uma, trunca células longas e pinta o header.
6
6
  */
7
7
  import chalk from 'chalk';
8
+ // eslint-disable-next-line no-control-regex
9
+ const ANSI = /\u001b\[[0-9;]*m/g;
10
+ /**
11
+ * Forma de IDENTIFICADOR: um token só, sem espaço, começando por letra ou
12
+ * dígito — `bug_<hex>`, `tkt_<uuid>`, `tenant_<uuid>`, uid do Firebase (28),
13
+ * auto-id do Firestore (20), slug, e-mail, `host:porta`, caminho relativo.
14
+ *
15
+ * Revisão do PR #75: a guarda de fonte (`id-que-existe.test.ts`) só via a
16
+ * coluna escrita do jeito que a regex dela espera, e deixou passar a
17
+ * RESPONSÁVEL do `bug list` (uid de 28 cortado em 24, que `--assigned-to`
18
+ * aceita de volta). Decidir aqui, pelo DADO, protege a próxima coluna sem
19
+ * depender de como alguém a escreveu (`maxWidth` ausente, coluna em várias
20
+ * linhas, `chalk.cyan(r.id)`, `r.slug ?? r.id`…).
21
+ *
22
+ * Custo aceito: um slug longo, um e-mail, um hash ou uma URL (sem `?`, `&`,
23
+ * `=`, que a regex não aceita) também deixam de ser cortados e podem alargar
24
+ * a tabela. Também são handles que alguém copia; cortados, seriam tão falsos
25
+ * quanto um id cortado. Texto com espaço (título, descrição, mensagem)
26
+ * continua truncando no `maxWidth`.
27
+ */
28
+ const FORMA_DE_IDENTIFICADOR = /^[A-Za-z0-9][A-Za-z0-9_.:@\/-]*$/;
29
+ export function pareceIdentificador(celula) {
30
+ return FORMA_DE_IDENTIFICADOR.test(celula.replace(ANSI, ''));
31
+ }
8
32
  function truncate(s, max) {
9
33
  if (s.length <= max)
10
34
  return s;
@@ -12,10 +36,10 @@ function truncate(s, max) {
12
36
  return s.slice(0, max);
13
37
  return `${s.slice(0, max - 1)}…`;
14
38
  }
15
- // Comprimento visível ignorando ANSI escapes (chalk).
39
+ // Comprimento visível ignorando ANSI escapes (chalk) — a mesma `ANSI` que
40
+ // `pareceIdentificador` usa, em vez de uma segunda cópia da regex.
16
41
  function visibleLength(s) {
17
- // eslint-disable-next-line no-control-regex
18
- return s.replace(/\[[0-9;]*m/g, '').length;
42
+ return s.replace(ANSI, '').length;
19
43
  }
20
44
  function padEndVisible(s, width) {
21
45
  const len = visibleLength(s);
@@ -29,7 +53,13 @@ export function renderTable(columns, rows) {
29
53
  console.log(chalk.dim(' (nenhum resultado)'));
30
54
  return;
31
55
  }
32
- const cells = rows.map((row) => columns.map((col) => truncate(col.value(row) ?? '', col.maxWidth ?? 40)));
56
+ const cells = rows.map((row) => columns.map((col) => {
57
+ const celula = col.value(row) ?? '';
58
+ // Identificador nunca é cortado: pela opção da coluna OU pela forma do dado.
59
+ if (col.identificador || pareceIdentificador(celula))
60
+ return celula;
61
+ return truncate(celula, col.maxWidth ?? 40);
62
+ }));
33
63
  const widths = columns.map((col, i) => {
34
64
  const headerLen = col.header.length;
35
65
  const maxCell = cells.reduce((m, r) => Math.max(m, visibleLength(r[i])), 0);
@@ -0,0 +1,124 @@
1
+ export { CARACTERE_DE_TROCA, CodificacaoInvalida, decodificarUtf8, semBomInicial, type OrigemDosBytes, } from './texto-utf8.js';
2
+ /**
3
+ * Aviso de que o segredo registrado ainda não chega ao app na VM. Com data:
4
+ * quando o agente for liberado, a frase fica visivelmente velha em vez de
5
+ * mentir calada. Mesmo fato que o Core declara no `assertNoSecretEnvVars`.
6
+ */
7
+ export declare const AVISO_AGENTE_PENDENTE: string;
8
+ /**
9
+ * O comando que registra `chave` no ambiente do deploy. `env` é o `--env` cru
10
+ * do deploy (`dev`, `production`…): sai no nome canônico que o Core aceita, ou
11
+ * `<ambiente>` quando não dá para saber.
12
+ */
13
+ export declare function comandoParaRegistrarSegredo(chave: string, env: string | undefined): string;
14
+ /** Nome de variável de ambiente em CAIXA_ALTA, 2 a 64 caracteres. */
15
+ export declare const CHAVE_SEGREDO_RE: RegExp;
16
+ /** Teto do valor: o do arquivo de ambiente que o agente escreve na VM. */
17
+ export declare const TETO_VALOR_BYTES = 4096;
18
+ /** Entropia aceita no `--generate` (bytes sorteados pelo Core). */
19
+ export declare const BYTES_GERADO_MIN = 16;
20
+ export declare const BYTES_GERADO_MAX = 128;
21
+ /**
22
+ * Teto do que se aceita LER (arquivo/stdin) antes de validar. Bem acima do
23
+ * `TETO_VALOR_BYTES` para a mensagem de "grande demais" sair certa, e baixo o
24
+ * bastante para um `--from-file` apontado por engano para um arquivo enorme
25
+ * (ou que nunca acaba, como `/dev/zero`) não encher a memória: a leitura para
26
+ * em `TETO_LEITURA_BYTES + 1` bytes, nunca lê o arquivo inteiro.
27
+ */
28
+ export declare const TETO_LEITURA_BYTES: number;
29
+ /**
30
+ * Variáveis que o Core e o agente JÁ entregam ao app na VM, por outro canal.
31
+ * Espelho de `CORE_INJECTED_ENV_KEYS` (`neetru_core/src/lib/core-injected-env.ts`),
32
+ * na mesma ordem: o teste `reservadas de hoje` trava o literal e a conferência
33
+ * opt-in (`NEETRU_CORE_DIR`) compara com o Core.
34
+ */
35
+ export declare const CHAVES_INJETADAS_PELO_CORE: readonly string[];
36
+ /** Famílias inteiras entregues por um canal do Core (hoje: módulos). Espelho de `CORE_INJECTED_ENV_PREFIXES`. */
37
+ export declare const PREFIXOS_INJETADOS_PELO_CORE: readonly string[];
38
+ /**
39
+ * Variáveis que mudam como o processo carrega código: um "segredo" com esse
40
+ * nome é execução de código na VM. Espelho de `LOADER_ENV_KEYS` do Core.
41
+ */
42
+ export declare const CHAVES_DO_CARREGADOR: readonly string[];
43
+ /** O Core já entrega esta variável ao app na VM (nome exato ou família)? */
44
+ export declare function chaveInjetadaPeloCore(chave: string): boolean;
45
+ /**
46
+ * Por que o Core recusaria registrar esta chave como segredo do produto, ou
47
+ * `null` se ele aceita. Só chama depois de `problemaNaChave` aprovar o
48
+ * formato. Espelho de `assertValidProductSecretKey` (a parte da reserva).
49
+ *
50
+ * Revisão do #81 (M1): sem isto, a CLI pedia o valor, mandava, e só então o
51
+ * Core dizia "reservada". O nome exato é repetido na mensagem porque é um nome
52
+ * da lista fixa; a família `NEETRU_MODULE_` é citada pelo prefixo, sem
53
+ * repetir o resto do que foi digitado.
54
+ */
55
+ export declare function problemaNaChaveReservada(chave: string): string | null;
56
+ /**
57
+ * Por que a chave é recusada, ou `null` se serve. NUNCA repete o que foi
58
+ * digitado: `KEY=valor` e o valor colado no lugar do nome são os dois erros
59
+ * mais prováveis, e ecoar a entrada imprimiria o segredo.
60
+ */
61
+ export declare function problemaNaChave(chave: string | undefined): string | null;
62
+ /**
63
+ * Por que o valor é recusado, ou `null` se serve. Mesma regra do Core e do
64
+ * agente: o valor vira uma linha de `EnvironmentFile` do systemd, e o agente
65
+ * recusa a resposta INTEIRA (todos os segredos do produto) se um valor tiver
66
+ * quebra de linha, NUL, aspas, barra invertida ou espaço nas pontas.
67
+ */
68
+ export declare function problemaNoValor(valor: string): string | null;
69
+ /** Valida `--bytes` (só faz sentido com `--generate`). `null` se serve. */
70
+ export declare function problemaNosBytes(bytes: number): string | null;
71
+ /**
72
+ * Tira UMA quebra de linha do fim (`\n` ou `\r\n`) — a que `echo`, `cat` e
73
+ * os editores deixam. Só uma: o resto de espaço ou quebra nas pontas é
74
+ * recusado por `problemaNoValor`, com mensagem, em vez de ser "consertado"
75
+ * em silêncio (cortar demais grava um segredo diferente do que o dev tem).
76
+ */
77
+ export declare function semQuebraFinal(texto: string): string;
78
+ /**
79
+ * Texto que OUTRO decodificador já converteu (o `readline` do pedido na tela,
80
+ * ou um fluxo com `setEncoding`): os bytes originais se perderam e o U+FFFD é
81
+ * o único rastro de byte que não era UTF-8. Recusa quando ele aparece.
82
+ * Limite declarado: um U+FFFD digitado de propósito também é recusado — por
83
+ * esta porta não dá para distinguir.
84
+ */
85
+ export declare function problemaNoTextoDoTerminal(texto: string): string | null;
86
+ /** O mínimo de um arquivo aberto que a leitura usa (o `FileHandle` do Node; trocável no teste). */
87
+ export interface ArquivoAberto {
88
+ read(buffer: Buffer, offset: number, length: number, position: number | null): Promise<{
89
+ bytesRead: number;
90
+ }>;
91
+ close(): Promise<void>;
92
+ }
93
+ /**
94
+ * Lê o valor de um arquivo. Erro de leitura vira mensagem sem conteúdo e sem
95
+ * o caminho (revisão do #81, B2: `--from-file <valor>` repetia o valor em
96
+ * "Arquivo não encontrado: <valor>").
97
+ *
98
+ * Lê no máximo `TETO_LEITURA_BYTES + 1` bytes (revisão do #81: o `readFile`
99
+ * de antes punha o arquivo inteiro na memória antes de conferir o teto).
100
+ */
101
+ export declare function lerValorDeArquivo(caminho: string, abrir?: (caminho: string) => Promise<ArquivoAberto>): Promise<string>;
102
+ /** Lê o valor inteiro de um fluxo (a entrada padrão, no `--stdin`). */
103
+ export declare function lerValorDeFluxo(fluxo: NodeJS.ReadableStream): Promise<string>;
104
+ /** Pedido cancelado (Ctrl+C ou fim da entrada antes da resposta). */
105
+ export declare class PedidoCancelado extends Error {
106
+ constructor();
107
+ }
108
+ export interface FluxosDoPedido {
109
+ /** De onde vêm as teclas (o terminal). */
110
+ entrada: NodeJS.ReadableStream;
111
+ /** Onde a PERGUNTA aparece. Nunca recebe o que é digitado. */
112
+ saida: NodeJS.WritableStream;
113
+ }
114
+ /**
115
+ * Faz as perguntas SEM ECO, numa interface só (duas interfaces seguidas no
116
+ * mesmo fluxo perdem a linha que a primeira já tinha lido).
117
+ *
118
+ * Como o eco some: o `readline` desenha o que é digitado no `output` dele —
119
+ * aqui um fluxo MUDO, que descarta tudo. A pergunta vai direto para `saida`,
120
+ * fora do `readline`. Com `terminal: true` o `readline` também liga o modo
121
+ * cru do terminal (`setRawMode`), então nem o eco do próprio sistema
122
+ * operacional aparece.
123
+ */
124
+ export declare function perguntarSemEco(perguntas: string[], fluxos: FluxosDoPedido): Promise<string[]>;
@@ -0,0 +1,336 @@
1
+ /**
2
+ * Entrada e validação do VALOR de um segredo próprio do produto
3
+ * (bug_8b85353b, Parte 3 — `neetru secrets`).
4
+ *
5
+ * REGRA DURA que molda este módulo: o valor NUNCA vem da linha de comando.
6
+ * Tudo o que vai no argv fica no histórico do shell (`~/.bash_history`,
7
+ * `PSReadLine`), aparece em `ps`/Gerenciador de Tarefas para qualquer
8
+ * processo da máquina e costuma ir parar em log de CI. Por isso o valor só
9
+ * entra por três portas:
10
+ *
11
+ * 1. `--stdin` — lido da entrada padrão (pipe);
12
+ * 2. `--from-file <caminho>` — lido de um arquivo;
13
+ * 3. prompt SEM ECO — o terminal não mostra o que é digitado.
14
+ *
15
+ * (A quarta saída — `--generate` — nem passa por aqui: o Core sorteia o valor
16
+ * e ninguém o vê.)
17
+ *
18
+ * E o valor NUNCA é impresso: nenhuma mensagem deste módulo repete o que foi
19
+ * digitado — nem quando a entrada está errada, porque a entrada errada pode
20
+ * ser justamente o valor colado no lugar do nome.
21
+ *
22
+ * Espelhos do Core (`neetru_core/src/lib/product-secrets.ts`, origin/main):
23
+ * `CHAVE_SEGREDO_RE` = `PRODUCT_SECRET_KEY_RE`, `TETO_VALOR_BYTES` =
24
+ * `PRODUCT_SECRET_MAX_VALUE_BYTES`, `problemaNoValor` = `productSecretValueProblem`,
25
+ * `BYTES_GERADO_*` = os limites de `generateProductSecretValue`, e
26
+ * `problemaNaChaveReservada` = `isReservedProductSecretKey` (listas de
27
+ * `core-injected-env.ts`). Validar aqui também é para o valor recusado não
28
+ * sair da máquina. Se o Core mudar a regra, ele continua sendo o juiz final —
29
+ * aqui é só falhar mais cedo.
30
+ */
31
+ import { open } from 'node:fs/promises';
32
+ import { createInterface } from 'node:readline';
33
+ import { Writable } from 'node:stream';
34
+ import { normalizarAmbienteTenant } from './envs.js';
35
+ import { CARACTERE_DE_TROCA, decodificarUtf8, recusaNaoUtf8, semBomInicial } from './texto-utf8.js';
36
+ // A leitura estrita em UTF-8 mora em `texto-utf8.ts` (o `--env-file` do deploy e do build usa a
37
+ // mesma). Reexportada para quem já importava daqui.
38
+ export { CARACTERE_DE_TROCA, CodificacaoInvalida, decodificarUtf8, semBomInicial, } from './texto-utf8.js';
39
+ /**
40
+ * Aviso de que o segredo registrado ainda não chega ao app na VM. Com data:
41
+ * quando o agente for liberado, a frase fica visivelmente velha em vez de
42
+ * mentir calada. Mesmo fato que o Core declara no `assertNoSecretEnvVars`.
43
+ */
44
+ export const AVISO_AGENTE_PENDENTE = 'Atenção: em 2026-10-16 o agente da VM ainda não entrega segredos do produto ao app ' +
45
+ '(Parte 2 de bug_8b85353b). O registro já vale e passa a chegar ao app no primeiro ' +
46
+ 'deploy depois que o agente for atualizado.';
47
+ /**
48
+ * O comando que registra `chave` no ambiente do deploy. `env` é o `--env` cru
49
+ * do deploy (`dev`, `production`…): sai no nome canônico que o Core aceita, ou
50
+ * `<ambiente>` quando não dá para saber.
51
+ */
52
+ export function comandoParaRegistrarSegredo(chave, env) {
53
+ return `neetru secrets set ${chave} --env ${normalizarAmbienteTenant(env) ?? '<ambiente>'}`;
54
+ }
55
+ /** Nome de variável de ambiente em CAIXA_ALTA, 2 a 64 caracteres. */
56
+ export const CHAVE_SEGREDO_RE = /^[A-Z][A-Z0-9_]{1,63}$/;
57
+ /** Teto do valor: o do arquivo de ambiente que o agente escreve na VM. */
58
+ export const TETO_VALOR_BYTES = 4096;
59
+ /** Entropia aceita no `--generate` (bytes sorteados pelo Core). */
60
+ export const BYTES_GERADO_MIN = 16;
61
+ export const BYTES_GERADO_MAX = 128;
62
+ /**
63
+ * Teto do que se aceita LER (arquivo/stdin) antes de validar. Bem acima do
64
+ * `TETO_VALOR_BYTES` para a mensagem de "grande demais" sair certa, e baixo o
65
+ * bastante para um `--from-file` apontado por engano para um arquivo enorme
66
+ * (ou que nunca acaba, como `/dev/zero`) não encher a memória: a leitura para
67
+ * em `TETO_LEITURA_BYTES + 1` bytes, nunca lê o arquivo inteiro.
68
+ */
69
+ export const TETO_LEITURA_BYTES = 64 * 1024;
70
+ /**
71
+ * Variáveis que o Core e o agente JÁ entregam ao app na VM, por outro canal.
72
+ * Espelho de `CORE_INJECTED_ENV_KEYS` (`neetru_core/src/lib/core-injected-env.ts`),
73
+ * na mesma ordem: o teste `reservadas de hoje` trava o literal e a conferência
74
+ * opt-in (`NEETRU_CORE_DIR`) compara com o Core.
75
+ */
76
+ export const CHAVES_INJETADAS_PELO_CORE = [
77
+ 'PORT',
78
+ 'NODE_ENV',
79
+ 'HOME',
80
+ 'NODE_OPTIONS',
81
+ 'NEETRU_ENV',
82
+ 'NEETRU_SDK_CLIENT_SECRET',
83
+ 'NEETRU_CLIENT_ID',
84
+ 'NEETRU_CLIENT_SECRET',
85
+ 'NEETRU_OIDC_CLIENT_ID',
86
+ 'NEETRU_OIDC_CLIENT_SECRET',
87
+ 'NEETRU_OAUTH_CLIENT_ID',
88
+ 'NEETRU_OAUTH_CLIENT_SECRET',
89
+ 'NEETRU_API_KEY',
90
+ ];
91
+ /** Famílias inteiras entregues por um canal do Core (hoje: módulos). Espelho de `CORE_INJECTED_ENV_PREFIXES`. */
92
+ export const PREFIXOS_INJETADOS_PELO_CORE = ['NEETRU_MODULE_'];
93
+ /**
94
+ * Variáveis que mudam como o processo carrega código: um "segredo" com esse
95
+ * nome é execução de código na VM. Espelho de `LOADER_ENV_KEYS` do Core.
96
+ */
97
+ export const CHAVES_DO_CARREGADOR = [
98
+ 'LD_PRELOAD',
99
+ 'LD_LIBRARY_PATH',
100
+ 'LD_AUDIT',
101
+ 'DYLD_INSERT_LIBRARIES',
102
+ 'DYLD_LIBRARY_PATH',
103
+ 'PATH',
104
+ 'NODE_PATH',
105
+ 'NODE_OPTIONS',
106
+ ];
107
+ /** O Core já entrega esta variável ao app na VM (nome exato ou família)? */
108
+ export function chaveInjetadaPeloCore(chave) {
109
+ return CHAVES_INJETADAS_PELO_CORE.includes(chave) || PREFIXOS_INJETADOS_PELO_CORE.some((p) => chave.startsWith(p));
110
+ }
111
+ /**
112
+ * Por que o Core recusaria registrar esta chave como segredo do produto, ou
113
+ * `null` se ele aceita. Só chama depois de `problemaNaChave` aprovar o
114
+ * formato. Espelho de `assertValidProductSecretKey` (a parte da reserva).
115
+ *
116
+ * Revisão do #81 (M1): sem isto, a CLI pedia o valor, mandava, e só então o
117
+ * Core dizia "reservada". O nome exato é repetido na mensagem porque é um nome
118
+ * da lista fixa; a família `NEETRU_MODULE_` é citada pelo prefixo, sem
119
+ * repetir o resto do que foi digitado.
120
+ */
121
+ export function problemaNaChaveReservada(chave) {
122
+ if (CHAVES_DO_CARREGADOR.includes(chave)) {
123
+ return (`A chave ${chave} é reservada: ela muda como o processo carrega código (bibliotecas, ` +
124
+ 'caminhos, opções do Node) e não pode vir de um segredo do produto. Escolha outro nome.');
125
+ }
126
+ if (CHAVES_INJETADAS_PELO_CORE.includes(chave)) {
127
+ return (`A chave ${chave} é reservada: o Core já a entrega ao app na VM por outro canal ` +
128
+ '(credencial SDK, OIDC ou CLI, ambiente ou unit do app). Não precisa registrá-la; ' +
129
+ 'para um segredo seu, escolha outro nome.');
130
+ }
131
+ const prefixo = PREFIXOS_INJETADOS_PELO_CORE.find((p) => chave.startsWith(p));
132
+ if (prefixo) {
133
+ return (`Chaves que começam com ${prefixo} são reservadas: o Core já as entrega ao app na VM ` +
134
+ 'quando o módulo está ativo. Não precisa registrá-las; para um segredo seu, escolha ' +
135
+ 'outro nome.');
136
+ }
137
+ return null;
138
+ }
139
+ /**
140
+ * Por que a chave é recusada, ou `null` se serve. NUNCA repete o que foi
141
+ * digitado: `KEY=valor` e o valor colado no lugar do nome são os dois erros
142
+ * mais prováveis, e ecoar a entrada imprimiria o segredo.
143
+ */
144
+ export function problemaNaChave(chave) {
145
+ if (typeof chave === 'string' && CHAVE_SEGREDO_RE.test(chave))
146
+ return null;
147
+ if (typeof chave === 'string' && chave.includes('=')) {
148
+ return ('Parece que você passou CHAVE=VALOR. O valor do segredo nunca vai na linha de ' +
149
+ 'comando (fica no histórico do shell). Passe só o nome e mande o valor por ' +
150
+ '--stdin, --from-file ou pelo pedido na tela. Se o valor já foi digitado, ' +
151
+ 'considere-o exposto e troque-o.');
152
+ }
153
+ return ('Nome de segredo inválido. Use CAIXA_ALTA com letras, dígitos e underscore, ' +
154
+ 'começando por letra, de 2 a 64 caracteres (ex.: NEETRU_WEBHOOK_SECRET). ' +
155
+ 'O que foi digitado não é repetido aqui, caso seja o próprio valor.');
156
+ }
157
+ /**
158
+ * Por que o valor é recusado, ou `null` se serve. Mesma regra do Core e do
159
+ * agente: o valor vira uma linha de `EnvironmentFile` do systemd, e o agente
160
+ * recusa a resposta INTEIRA (todos os segredos do produto) se um valor tiver
161
+ * quebra de linha, NUL, aspas, barra invertida ou espaço nas pontas.
162
+ */
163
+ export function problemaNoValor(valor) {
164
+ if (valor.length === 0)
165
+ return 'O valor do segredo está vazio.';
166
+ if (Buffer.byteLength(valor, 'utf8') > TETO_VALOR_BYTES) {
167
+ return `O valor do segredo passa de ${TETO_VALOR_BYTES} bytes (teto do arquivo de ambiente na VM).`;
168
+ }
169
+ if (/[\r\n\0"'\\]/.test(valor) || valor !== valor.trim()) {
170
+ return ('O valor do segredo tem caractere que o arquivo de ambiente da VM não aceita ' +
171
+ '(quebra de linha, NUL, aspas, barra invertida, ou espaço ou caractere invisível ' +
172
+ 'nas pontas, como a marca BOM de arquivo salvo em "UTF-8 com BOM"). Para chave ' +
173
+ 'PEM ou JSON, grave em base64 e decodifique no app.');
174
+ }
175
+ return null;
176
+ }
177
+ /** Valida `--bytes` (só faz sentido com `--generate`). `null` se serve. */
178
+ export function problemaNosBytes(bytes) {
179
+ if (!Number.isInteger(bytes) || bytes < BYTES_GERADO_MIN || bytes > BYTES_GERADO_MAX) {
180
+ return `--bytes deve ser um número inteiro de ${BYTES_GERADO_MIN} a ${BYTES_GERADO_MAX}.`;
181
+ }
182
+ return null;
183
+ }
184
+ /**
185
+ * Tira UMA quebra de linha do fim (`\n` ou `\r\n`) — a que `echo`, `cat` e
186
+ * os editores deixam. Só uma: o resto de espaço ou quebra nas pontas é
187
+ * recusado por `problemaNoValor`, com mensagem, em vez de ser "consertado"
188
+ * em silêncio (cortar demais grava um segredo diferente do que o dev tem).
189
+ */
190
+ export function semQuebraFinal(texto) {
191
+ if (texto.endsWith('\r\n'))
192
+ return texto.slice(0, -2);
193
+ if (texto.endsWith('\n'))
194
+ return texto.slice(0, -1);
195
+ return texto;
196
+ }
197
+ /**
198
+ * Texto que OUTRO decodificador já converteu (o `readline` do pedido na tela,
199
+ * ou um fluxo com `setEncoding`): os bytes originais se perderam e o U+FFFD é
200
+ * o único rastro de byte que não era UTF-8. Recusa quando ele aparece.
201
+ * Limite declarado: um U+FFFD digitado de propósito também é recusado — por
202
+ * esta porta não dá para distinguir.
203
+ */
204
+ export function problemaNoTextoDoTerminal(texto) {
205
+ if (!texto.includes(CARACTERE_DE_TROCA))
206
+ return null;
207
+ return ('O terminal mandou um caractere que não está em UTF-8, e ele foi trocado por um caractere de ' +
208
+ 'substituição: o segredo gravado seria diferente do digitado. Ajuste o terminal para UTF-8 ' +
209
+ '(ex.: LANG=pt_BR.UTF-8), ou grave o valor num arquivo UTF-8 e use --from-file. ' +
210
+ 'O conteúdo não é repetido aqui. Nada foi enviado.');
211
+ }
212
+ /** Nunca repete o caminho: o engano provável é colar o VALOR no lugar dele. */
213
+ const CAMINHO_NAO_REPETIDO = '(o caminho não é repetido aqui, caso seja o próprio valor)';
214
+ function mensagemDeLeitura(err) {
215
+ const codigo = err?.code;
216
+ if (codigo === 'ENOENT')
217
+ return `Arquivo não encontrado ${CAMINHO_NAO_REPETIDO}.`;
218
+ if (codigo === 'EISDIR')
219
+ return `O caminho é uma pasta, não um arquivo ${CAMINHO_NAO_REPETIDO}.`;
220
+ return `Não consegui ler o arquivo (${codigo ?? 'erro desconhecido'}) ${CAMINHO_NAO_REPETIDO}.`;
221
+ }
222
+ /**
223
+ * Lê o valor de um arquivo. Erro de leitura vira mensagem sem conteúdo e sem
224
+ * o caminho (revisão do #81, B2: `--from-file <valor>` repetia o valor em
225
+ * "Arquivo não encontrado: <valor>").
226
+ *
227
+ * Lê no máximo `TETO_LEITURA_BYTES + 1` bytes (revisão do #81: o `readFile`
228
+ * de antes punha o arquivo inteiro na memória antes de conferir o teto).
229
+ */
230
+ export async function lerValorDeArquivo(caminho, abrir = (c) => open(c, 'r')) {
231
+ const bruto = Buffer.alloc(TETO_LEITURA_BYTES + 1);
232
+ let lidos = 0;
233
+ let arquivo;
234
+ try {
235
+ arquivo = await abrir(caminho);
236
+ while (lidos < bruto.length) {
237
+ const { bytesRead } = await arquivo.read(bruto, lidos, bruto.length - lidos, null);
238
+ if (bytesRead === 0)
239
+ break;
240
+ lidos += bytesRead;
241
+ }
242
+ }
243
+ catch (err) {
244
+ throw new Error(mensagemDeLeitura(err));
245
+ }
246
+ finally {
247
+ await arquivo?.close().catch(() => undefined);
248
+ }
249
+ if (lidos > TETO_LEITURA_BYTES) {
250
+ throw new Error(`O arquivo passa de ${TETO_LEITURA_BYTES} bytes — não parece um segredo.`);
251
+ }
252
+ return semQuebraFinal(semBomInicial(decodificarUtf8(bruto.subarray(0, lidos), 'arquivo')));
253
+ }
254
+ /** Lê o valor inteiro de um fluxo (a entrada padrão, no `--stdin`). */
255
+ export async function lerValorDeFluxo(fluxo) {
256
+ const partes = [];
257
+ let total = 0;
258
+ for await (const pedaco of fluxo) {
259
+ // Pedaço em texto = alguém já decodificou o fluxo (`setEncoding`), e o
260
+ // U+FFFD é o único rastro de byte trocado. Os pedaços em bytes são
261
+ // conferidos JUNTOS no fim: uma letra acentuada pode vir partida em dois.
262
+ // Revisão do #81 (M-r3-1): o U+FFFD que chega em bytes (EF BF BD, a
263
+ // conversão com perda feita antes da CLI) também é recusado, lá no fim,
264
+ // por `decodificarUtf8` — a regra não depende mais do formato do pedaço.
265
+ if (typeof pedaco === 'string' && pedaco.includes(CARACTERE_DE_TROCA))
266
+ throw recusaNaoUtf8('entrada');
267
+ const buf = typeof pedaco === 'string' ? Buffer.from(pedaco, 'utf8') : pedaco;
268
+ total += buf.length;
269
+ if (total > TETO_LEITURA_BYTES) {
270
+ throw new Error(`A entrada passa de ${TETO_LEITURA_BYTES} bytes — não parece um segredo.`);
271
+ }
272
+ partes.push(buf);
273
+ }
274
+ return semQuebraFinal(semBomInicial(decodificarUtf8(Buffer.concat(partes), 'entrada')));
275
+ }
276
+ /** Pedido cancelado (Ctrl+C ou fim da entrada antes da resposta). */
277
+ export class PedidoCancelado extends Error {
278
+ constructor() {
279
+ super('Pedido cancelado — nada foi enviado.');
280
+ this.name = 'PedidoCancelado';
281
+ }
282
+ }
283
+ /**
284
+ * Faz as perguntas SEM ECO, numa interface só (duas interfaces seguidas no
285
+ * mesmo fluxo perdem a linha que a primeira já tinha lido).
286
+ *
287
+ * Como o eco some: o `readline` desenha o que é digitado no `output` dele —
288
+ * aqui um fluxo MUDO, que descarta tudo. A pergunta vai direto para `saida`,
289
+ * fora do `readline`. Com `terminal: true` o `readline` também liga o modo
290
+ * cru do terminal (`setRawMode`), então nem o eco do próprio sistema
291
+ * operacional aparece.
292
+ */
293
+ export function perguntarSemEco(perguntas, fluxos) {
294
+ const mudo = new Writable({
295
+ write(_pedaco, _codificacao, pronto) {
296
+ pronto();
297
+ },
298
+ });
299
+ const rl = createInterface({ input: fluxos.entrada, output: mudo, terminal: true });
300
+ const respostas = [];
301
+ return new Promise((resolve, reject) => {
302
+ let terminado = false;
303
+ const encerrar = (erro) => {
304
+ if (terminado)
305
+ return;
306
+ terminado = true;
307
+ rl.close();
308
+ if (erro)
309
+ reject(erro);
310
+ else
311
+ resolve(respostas);
312
+ };
313
+ // Ctrl+C em modo cru não vira sinal: chega aqui. Sem este ouvinte o
314
+ // readline só pausa, e o comando ficaria pendurado.
315
+ rl.on('SIGINT', () => {
316
+ fluxos.saida.write('\n');
317
+ encerrar(new PedidoCancelado());
318
+ });
319
+ rl.on('close', () => encerrar(new PedidoCancelado()));
320
+ const proxima = (i) => {
321
+ if (i >= perguntas.length) {
322
+ encerrar();
323
+ return;
324
+ }
325
+ fluxos.saida.write(` ${perguntas[i]} `);
326
+ rl.question('', (resposta) => {
327
+ // O Enter também não foi ecoado: a quebra de linha é nossa.
328
+ fluxos.saida.write('\n');
329
+ respostas.push(resposta);
330
+ proxima(i + 1);
331
+ });
332
+ };
333
+ proxima(0);
334
+ });
335
+ }
336
+ //# sourceMappingURL=segredo-produto.js.map
@@ -0,0 +1,96 @@
1
+ /**
2
+ * O texto não está em UTF-8 (ou traz U+FFFD) — recusa com o código
3
+ * `invalid_encoding`. A mensagem diz como salvar o arquivo, nunca o que ele
4
+ * contém.
5
+ */
6
+ export declare class CodificacaoInvalida extends Error {
7
+ constructor(mensagem: string);
8
+ }
9
+ /** U+FFFD: o caractere que um decodificador UTF-8 põe no lugar de byte que não é UTF-8. */
10
+ export declare const CARACTERE_DE_TROCA = "\uFFFD";
11
+ /**
12
+ * De onde vieram os bytes (só muda o texto da recusa): `arquivo` e `entrada`
13
+ * são o `--from-file` e o `--stdin` do segredo; `env-file` é o `--env-file`
14
+ * do deploy e do build.
15
+ */
16
+ export type OrigemDosBytes = 'arquivo' | 'entrada' | 'env-file';
17
+ /**
18
+ * Tira UMA marca BOM (U+FEFF) do começo — a que o Bloco de Notas ("UTF-8 com
19
+ * BOM") e o `Set-Content -Encoding utf8` do Windows PowerShell 5.1 gravam no
20
+ * arquivo (medido: `ef bb bf …`). O PowerShell 7 grava sem a marca. Revisão do
21
+ * #81 (B3): sem isto o `trim()` de `problemaNoValor` via a marca como espaço
22
+ * e recusava com "espaço nas pontas", sem espaço nenhum à vista. Só uma, pelo
23
+ * mesmo motivo de `semQuebraFinal`.
24
+ */
25
+ export declare function semBomInicial(texto: string): string;
26
+ /**
27
+ * Como converter um arquivo ANSI para UTF-8 — uma receita por versão do
28
+ * PowerShell, porque o `Get-Content` lê diferente nas duas.
29
+ *
30
+ * Revisão do #81 (M-r3-1): a receita era só a do 5.1. No Windows PowerShell
31
+ * 5.1, o `Get-Content` lê arquivo sem marca em ANSI, e o `Set-Content
32
+ * -Encoding utf8` grava UTF-8 COM a marca BOM (`semBomInicial` a tira). No
33
+ * PowerShell 7, o `Get-Content` lê arquivo sem marca em UTF-8 (o padrão é
34
+ * `utf8NoBOM`): o "é" do ANSI vira U+FFFD antes de chegar à CLI, e o
35
+ * `Set-Content -Encoding utf8` (no 7, sem BOM) grava esse U+FFFD em UTF-8
36
+ * válido. Lá a leitura certa é `-Encoding ansi` (página ANSI da cultura,
37
+ * existe a partir do 7.4, segundo a documentação do `Get-Content`). No 5.1,
38
+ * `-Encoding ansi` dá erro de parâmetro e não cria o arquivo novo (medido), e
39
+ * a receita do 5.1 rodada no 7 grava U+FFFD — que `decodificarUtf8` recusa.
40
+ * Assim, a receita da versão errada nunca passa calada.
41
+ */
42
+ export declare const COMO_CONVERTER_ANSI: string;
43
+ /**
44
+ * UTF-16 tem marca (FF FE / FE FF), e a marca vence o `-Encoding` na leitura
45
+ * (medido no 5.1: o arquivo do `>` lido com `-Encoding UTF8` sai certo). Por
46
+ * isso a mesma receita serve nas duas versões.
47
+ */
48
+ export declare const COMO_CONVERTER_UTF16: string;
49
+ /** Exportada para o `--stdin` que já chega decodificado (`setEncoding`). */
50
+ export declare function recusaNaoUtf8(origem: OrigemDosBytes): CodificacaoInvalida;
51
+ /**
52
+ * O que impede estes bytes de serem lidos como o arquivo de origem — ou `null`
53
+ * quando são UTF-8 limpo:
54
+ *
55
+ * 1. `utf16`: marca UTF-16 (FF FE / FE FF) — antes caía em recusa que
56
+ * citava "UTF-8 com BOM" (segredo) ou virava chave com NUL (env-file);
57
+ * 2. `nao-utf8`: bytes que não são UTF-8 (`isUtf8`) — o ANSI do 5.1;
58
+ * 3. `troca-gravada`: U+FFFD em UTF-8 válido (revisão do #81, M-r3-1) — a
59
+ * conversão com perda feita antes da CLI. Limite declarado: um U+FFFD
60
+ * gravado de propósito cai aqui também (não dá para distinguir).
61
+ *
62
+ * A marca BOM do UTF-8 (EF BB BF) é UTF-8 válido: não é problema.
63
+ *
64
+ * Fonte única da regra: `decodificarUtf8` recusa por ela, e o `env switch` e a
65
+ * varredura de segredo público decidem por ela (rodada final 2 do #81).
66
+ */
67
+ export type ProblemaDeCodificacao = 'utf16' | 'nao-utf8' | 'troca-gravada';
68
+ export declare function problemaDeCodificacao(bruto: Buffer): ProblemaDeCodificacao | null;
69
+ /**
70
+ * Converte os bytes lidos em texto, RECUSANDO o que não é o arquivo de origem
71
+ * (os três casos de `problemaDeCodificacao`). No U+FFFD, a CLI fica mais
72
+ * estrita que o Core.
73
+ *
74
+ * A marca BOM do UTF-8 (EF BB BF) passa daqui, e quem chama a tira com
75
+ * `semBomInicial`.
76
+ */
77
+ export declare function decodificarUtf8(bruto: Buffer, origem: OrigemDosBytes): string;
78
+ /**
79
+ * `neetru env switch` (rodada final 2 do #81, achado BAIXA): o comando lia o
80
+ * `.env.local` com `readFile(…, 'utf-8')` e REGRAVAVA o arquivo inteiro. Medido
81
+ * no processo real: um `.env.local` ANSI com `NOME_LOJA=Café` terminava com
82
+ * `ef bf bd` no lugar do `e9`, e um UTF-16 virava texto ilegível — exit 0 e
83
+ * "✓" nos dois. Agora ele recusa ANTES de gravar e não toca no arquivo.
84
+ *
85
+ * Recusa só o que a regravação estragaria (`utf16` e `nao-utf8`). Um U+FFFD
86
+ * que já está no arquivo (`troca-gravada`) sai byte a byte igual — o comando
87
+ * não perde nada ali, então segue.
88
+ */
89
+ export declare function recusaDoEnvSwitch(arquivo: string, problema: 'utf16' | 'nao-utf8'): CodificacaoInvalida;
90
+ /**
91
+ * Varredura de segredo público do `neetru deploy` (rodada final 2 do #81): um
92
+ * `.env*` que o bundler lê não está em UTF-8 limpo. A varredura segue — ela
93
+ * confere o valor como o bundler o lê —, mas o app vai receber outro valor, e
94
+ * isso não passa calado. Nunca repete o conteúdo.
95
+ */
96
+ export declare function avisoEnvDoBundlerForaDeUtf8(arquivo: string, problema: ProblemaDeCodificacao): string;