@neetru/cli 2.31.0 → 2.32.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (245) hide show
  1. package/CHANGELOG.md +99 -2
  2. package/README.md +2 -0
  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 +118 -6
  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 +20 -33
  26. package/dist/commands/deploy.d.ts +91 -2
  27. package/dist/commands/deploy.js +423 -78
  28. package/dist/commands/deployments.js +11 -6
  29. package/dist/commands/dev-context.js +15 -5
  30. package/dist/commands/docs-produto.d.ts +25 -0
  31. package/dist/commands/docs-produto.js +70 -8
  32. package/dist/commands/doctor.js +36 -1
  33. package/dist/commands/domains.js +85 -4
  34. package/dist/commands/env.d.ts +9 -3
  35. package/dist/commands/env.js +23 -3
  36. package/dist/commands/infra-read.js +2 -2
  37. package/dist/commands/init.js +8 -1
  38. package/dist/commands/login.js +35 -8
  39. package/dist/commands/logs.js +12 -4
  40. package/dist/commands/mobile.js +33 -9
  41. package/dist/commands/new.d.ts +29 -1
  42. package/dist/commands/new.js +92 -4
  43. package/dist/commands/open.js +16 -6
  44. package/dist/commands/ops.js +1 -1
  45. package/dist/commands/products-db.d.ts +1 -0
  46. package/dist/commands/products-db.js +41 -8
  47. package/dist/commands/products.d.ts +17 -3
  48. package/dist/commands/products.js +86 -18
  49. package/dist/commands/promote.js +30 -5
  50. package/dist/commands/secrets.d.ts +91 -0
  51. package/dist/commands/secrets.js +471 -0
  52. package/dist/commands/servers.js +8 -5
  53. package/dist/commands/status.js +2 -2
  54. package/dist/commands/support.js +5 -3
  55. package/dist/commands/tenants.js +28 -16
  56. package/dist/commands/upgrade.d.ts +9 -0
  57. package/dist/commands/upgrade.js +50 -1
  58. package/dist/commands/vm-memory.js +1 -0
  59. package/dist/commands/webhooks.d.ts +2 -0
  60. package/dist/commands/webhooks.js +9 -3
  61. package/dist/commands/workspaces.js +42 -11
  62. package/dist/index.js +70 -14
  63. package/dist/lib/api-client.d.ts +14 -0
  64. package/dist/lib/api-client.js +59 -6
  65. package/dist/lib/api-error.d.ts +14 -0
  66. package/dist/lib/api-error.js +107 -46
  67. package/dist/lib/build-targets.d.ts +127 -0
  68. package/dist/lib/build-targets.js +348 -0
  69. package/dist/lib/cli-error.d.ts +13 -0
  70. package/dist/lib/cli-error.js +24 -0
  71. package/dist/lib/cli-write.d.ts +43 -9
  72. package/dist/lib/cli-write.js +154 -45
  73. package/dist/lib/config-schema.d.ts +8 -8
  74. package/dist/lib/db-local/db-json.d.ts +1 -1
  75. package/dist/lib/db-local/db-json.js +13 -5
  76. package/dist/lib/db-local/env.d.ts +15 -3
  77. package/dist/lib/db-local/env.js +37 -10
  78. package/dist/lib/db-pipeline/build-deps.d.ts +5 -1
  79. package/dist/lib/db-pipeline/build-deps.js +23 -3
  80. package/dist/lib/db-pipeline/types.d.ts +6 -2
  81. package/dist/lib/db-pipeline/types.js +0 -18
  82. package/dist/lib/deploy-target-contract.d.ts +152 -0
  83. package/dist/lib/deploy-target-contract.js +411 -0
  84. package/dist/lib/env-file-do-build.d.ts +19 -0
  85. package/dist/lib/env-file-do-build.js +44 -0
  86. package/dist/lib/envs.d.ts +108 -0
  87. package/dist/lib/envs.js +127 -0
  88. package/dist/lib/fs-seguro.d.ts +63 -0
  89. package/dist/lib/fs-seguro.js +108 -0
  90. package/dist/lib/inquirer-sob-demanda.d.ts +14 -0
  91. package/dist/lib/inquirer-sob-demanda.js +18 -0
  92. package/dist/lib/mobile-local/mobile-json.js +10 -4
  93. package/dist/lib/pickers.d.ts +44 -7
  94. package/dist/lib/pickers.js +65 -7
  95. package/dist/lib/product-docs/sync.d.ts +6 -0
  96. package/dist/lib/product-docs/sync.js +10 -1
  97. package/dist/lib/public-secret-scan.d.ts +44 -3
  98. package/dist/lib/public-secret-scan.js +361 -36
  99. package/dist/lib/render.d.ts +16 -0
  100. package/dist/lib/render.js +34 -4
  101. package/dist/lib/segredo-produto.d.ts +124 -0
  102. package/dist/lib/segredo-produto.js +336 -0
  103. package/dist/lib/texto-utf8.d.ts +96 -0
  104. package/dist/lib/texto-utf8.js +196 -0
  105. package/package.json +2 -1
  106. package/dist/cli-kit/format.js.map +0 -1
  107. package/dist/cli-kit/glyphs.js.map +0 -1
  108. package/dist/cli-kit/index.js.map +0 -1
  109. package/dist/cli-kit/palette.js.map +0 -1
  110. package/dist/commands/add.js.map +0 -1
  111. package/dist/commands/admin/iam-check.js.map +0 -1
  112. package/dist/commands/admin/oidc-login.js.map +0 -1
  113. package/dist/commands/admin/sdk-credential.js.map +0 -1
  114. package/dist/commands/agent-release.js.map +0 -1
  115. package/dist/commands/agent-write.js.map +0 -1
  116. package/dist/commands/ai-models.js.map +0 -1
  117. package/dist/commands/ai.js.map +0 -1
  118. package/dist/commands/api-catalog.js.map +0 -1
  119. package/dist/commands/archive.js.map +0 -1
  120. package/dist/commands/artifact-registry.js.map +0 -1
  121. package/dist/commands/audit.js.map +0 -1
  122. package/dist/commands/auth.js.map +0 -1
  123. package/dist/commands/autocomplete.js.map +0 -1
  124. package/dist/commands/billing.js.map +0 -1
  125. package/dist/commands/bootstrap.js.map +0 -1
  126. package/dist/commands/bug.js.map +0 -1
  127. package/dist/commands/build.js.map +0 -1
  128. package/dist/commands/chat-menu.js.map +0 -1
  129. package/dist/commands/chat.js.map +0 -1
  130. package/dist/commands/cloud-run.js.map +0 -1
  131. package/dist/commands/code.js.map +0 -1
  132. package/dist/commands/config.js.map +0 -1
  133. package/dist/commands/customers.js.map +0 -1
  134. package/dist/commands/db-move.js.map +0 -1
  135. package/dist/commands/db.js.map +0 -1
  136. package/dist/commands/deploy.js.map +0 -1
  137. package/dist/commands/deployments.js.map +0 -1
  138. package/dist/commands/dev-context.js.map +0 -1
  139. package/dist/commands/dev-up.js.map +0 -1
  140. package/dist/commands/dev.js.map +0 -1
  141. package/dist/commands/docs-produto.js.map +0 -1
  142. package/dist/commands/docs.js.map +0 -1
  143. package/dist/commands/doctor.js.map +0 -1
  144. package/dist/commands/domains.js.map +0 -1
  145. package/dist/commands/dr.js.map +0 -1
  146. package/dist/commands/env.js.map +0 -1
  147. package/dist/commands/hosting-write.js.map +0 -1
  148. package/dist/commands/infra-read.js.map +0 -1
  149. package/dist/commands/init.js.map +0 -1
  150. package/dist/commands/login.js.map +0 -1
  151. package/dist/commands/logout.js.map +0 -1
  152. package/dist/commands/logs.js.map +0 -1
  153. package/dist/commands/mobile.js.map +0 -1
  154. package/dist/commands/mocks.js.map +0 -1
  155. package/dist/commands/new.js.map +0 -1
  156. package/dist/commands/open.js.map +0 -1
  157. package/dist/commands/ops.js.map +0 -1
  158. package/dist/commands/products-db.js.map +0 -1
  159. package/dist/commands/products.js.map +0 -1
  160. package/dist/commands/promote.js.map +0 -1
  161. package/dist/commands/publish.js.map +0 -1
  162. package/dist/commands/schema.js.map +0 -1
  163. package/dist/commands/servers.js.map +0 -1
  164. package/dist/commands/smoke.js.map +0 -1
  165. package/dist/commands/status.js.map +0 -1
  166. package/dist/commands/support.js.map +0 -1
  167. package/dist/commands/surface-status.js.map +0 -1
  168. package/dist/commands/tenants.js.map +0 -1
  169. package/dist/commands/ui.js.map +0 -1
  170. package/dist/commands/upgrade.js.map +0 -1
  171. package/dist/commands/validate.js.map +0 -1
  172. package/dist/commands/vm-memory.js.map +0 -1
  173. package/dist/commands/vm.js.map +0 -1
  174. package/dist/commands/webhooks.js.map +0 -1
  175. package/dist/commands/whoami.js.map +0 -1
  176. package/dist/commands/workspaces.js.map +0 -1
  177. package/dist/index.js.map +0 -1
  178. package/dist/lib/agent-artifact-url.js.map +0 -1
  179. package/dist/lib/agent-commands.js.map +0 -1
  180. package/dist/lib/ai/auth-discovery.js.map +0 -1
  181. package/dist/lib/ai/context.js.map +0 -1
  182. package/dist/lib/ai/orchestrator.js.map +0 -1
  183. package/dist/lib/api-client.js.map +0 -1
  184. package/dist/lib/api-error.js.map +0 -1
  185. package/dist/lib/auth.js.map +0 -1
  186. package/dist/lib/cli-read.js.map +0 -1
  187. package/dist/lib/cli-write.js.map +0 -1
  188. package/dist/lib/code-agent-budget.js.map +0 -1
  189. package/dist/lib/code-agent-files.js.map +0 -1
  190. package/dist/lib/code-agent-gateway.js.map +0 -1
  191. package/dist/lib/code-agent-git-tools.js.map +0 -1
  192. package/dist/lib/code-agent-protocol.js.map +0 -1
  193. package/dist/lib/code-agent-session.js.map +0 -1
  194. package/dist/lib/code-agent-settings.js.map +0 -1
  195. package/dist/lib/code-agent-skills.js.map +0 -1
  196. package/dist/lib/code-agent-subagents.js.map +0 -1
  197. package/dist/lib/code-agent-tools.js.map +0 -1
  198. package/dist/lib/code-agent-tui.js.map +0 -1
  199. package/dist/lib/code-agent-types.js.map +0 -1
  200. package/dist/lib/code-agent-ui.js.map +0 -1
  201. package/dist/lib/code-agent-web-tools.js.map +0 -1
  202. package/dist/lib/code-agent-write-tools.js.map +0 -1
  203. package/dist/lib/compose-generator.js.map +0 -1
  204. package/dist/lib/config-schema.js.map +0 -1
  205. package/dist/lib/config.js.map +0 -1
  206. package/dist/lib/db-local/db-json.js.map +0 -1
  207. package/dist/lib/db-local/env.js.map +0 -1
  208. package/dist/lib/db-local/fingerprint.js.map +0 -1
  209. package/dist/lib/db-local/index.js.map +0 -1
  210. package/dist/lib/db-pipeline/build-deps.js.map +0 -1
  211. package/dist/lib/db-pipeline/errors.js.map +0 -1
  212. package/dist/lib/db-pipeline/index.js.map +0 -1
  213. package/dist/lib/db-pipeline/pipeline.js.map +0 -1
  214. package/dist/lib/db-pipeline/rehearse.js.map +0 -1
  215. package/dist/lib/db-pipeline/types.js.map +0 -1
  216. package/dist/lib/dev-local-context.js.map +0 -1
  217. package/dist/lib/iam-preflight.js.map +0 -1
  218. package/dist/lib/mobile-local/index.js.map +0 -1
  219. package/dist/lib/mobile-local/mobile-json.js.map +0 -1
  220. package/dist/lib/pickers.js.map +0 -1
  221. package/dist/lib/product-config.js.map +0 -1
  222. package/dist/lib/product-docs/check.js.map +0 -1
  223. package/dist/lib/product-docs/config.js.map +0 -1
  224. package/dist/lib/product-docs/dates.js.map +0 -1
  225. package/dist/lib/product-docs/frontmatter.js.map +0 -1
  226. package/dist/lib/product-docs/git.js.map +0 -1
  227. package/dist/lib/product-docs/markdown.js.map +0 -1
  228. package/dist/lib/product-docs/ops.js.map +0 -1
  229. package/dist/lib/product-docs/structure.js.map +0 -1
  230. package/dist/lib/product-docs/sync.js.map +0 -1
  231. package/dist/lib/product-docs/taxonomy.js.map +0 -1
  232. package/dist/lib/product-docs/templates.js.map +0 -1
  233. package/dist/lib/public-secret-scan.js.map +0 -1
  234. package/dist/lib/render.js.map +0 -1
  235. package/dist/lib/schema-cache.js.map +0 -1
  236. package/dist/lib/sse.js.map +0 -1
  237. package/dist/lib/telemetry.js.map +0 -1
  238. package/dist/lib/token-resolver.js.map +0 -1
  239. package/dist/lib/url-allowlist.js.map +0 -1
  240. package/dist/lib/version-check.js.map +0 -1
  241. package/dist/utils/logger.js.map +0 -1
  242. package/dist/utils/logo.js.map +0 -1
  243. package/dist/utils/safe-exit.js.map +0 -1
  244. package/dist/utils/spinner-registry.js.map +0 -1
  245. package/dist/version.js.map +0 -1
@@ -33,6 +33,8 @@ import fs from 'node:fs';
33
33
  import fsp from 'node:fs/promises';
34
34
  import path from 'node:path';
35
35
  import zlib from 'node:zlib';
36
+ import { problemaDeCodificacao, semBomInicial } from './texto-utf8.js';
37
+ import { variaveisDoEnvFile } from './env-file-do-build.js';
36
38
  export const PUBLIC_SECRET_PATTERNS = [
37
39
  { id: 'nrt_key', label: 'credencial nrt_ do produto (só de servidor)', re: /nrt_[A-Za-z0-9_-]{20,}/g },
38
40
  { id: 'npk_key', label: 'chave npk_ do produto', re: /npk_[A-Za-z0-9_-]{20,}/g },
@@ -105,7 +107,141 @@ export const MAX_SCAN_FILE_BYTES = 25 * 1024 * 1024;
105
107
  export function isScannableFile(relPath) {
106
108
  return !BINARY_EXT.has(path.extname(relPath).toLowerCase());
107
109
  }
108
- /** Varre um conteúdo de arquivo. Dedup por (arquivo, padrão). */
110
+ // ─── Formas do valor da env dentro do pacote ────────────────────────────────
111
+ const ESPECIAIS_DE_REGEX = /[.*+?^${}()|[\]\\]/g;
112
+ /** Fonte de regex que casa `s` ao pé da letra (o caractere de controle cru casa ele mesmo). */
113
+ function literalDeRegex(s) {
114
+ return s.replace(ESPECIAIS_DE_REGEX, '\\$&');
115
+ }
116
+ /** Hex de `n` com `digitos` no mínimo, cada letra casando nas duas caixas (`e1` → `[eE]1`). */
117
+ function hexNasDuasCaixas(n, digitos) {
118
+ return n
119
+ .toString(16)
120
+ .padStart(digitos, '0')
121
+ .replace(/[a-f]/g, (c) => `[${c}${c.toUpperCase()}]`);
122
+ }
123
+ /**
124
+ * Escape curto que pode estar no pacote no lugar do caractere ASCII (texto do
125
+ * pacote, não da regex). Medido na rodada final 4 do cli#81, num `next build`
126
+ * real (Next 15.5.27) e no esbuild 0.28.1:
127
+ * - `\\` (barra invertida): os dois;
128
+ * - `\"` e `\'`: o SWC, quando o valor tem as duas aspas (o esbuild usa crase);
129
+ * - `\/`: os dois, em `<\/script>`;
130
+ * - `\n`: o SWC (o esbuild grava a quebra crua, dentro de crase);
131
+ * - `\r`, `\b`, `\f`, `\v` e `\0`: os dois;
132
+ * - `\$` (em `\${`) e a crase escapada: o esbuild (0.21.5 do Vite 5.4 e 0.28), quando
133
+ * o valor tem as duas aspas repetidas e ele escolhe a crase (rodada final
134
+ * 5, medido num `vite build` real). O SWC não escapa nenhum dos dois.
135
+ * A tabulação não aparece como `\t`: os dois a gravam crua.
136
+ */
137
+ const ESCAPE_CURTO = new Map([
138
+ [0x5c, '\\\\'],
139
+ [0x22, '\\"'],
140
+ [0x27, "\\'"],
141
+ [0x2f, '\\/'],
142
+ [0x0a, '\\n'],
143
+ [0x0d, '\\r'],
144
+ [0x08, '\\b'],
145
+ [0x0c, '\\f'],
146
+ [0x0b, '\\v'],
147
+ [0x00, '\\0'],
148
+ [0x24, '\\$'],
149
+ [0x60, '\\`'],
150
+ ]);
151
+ /**
152
+ * O minificador pode gravar este caractere de outro jeito? Sim para letra fora
153
+ * do ASCII, caractere de controle (incluindo U+007F) e `\ " ' / $` e a crase.
154
+ * As demais letras ASCII saem sempre cruas.
155
+ */
156
+ function podeSairEscapado(cp) {
157
+ return cp >= 0x7f || cp < 0x20 || ESCAPE_CURTO.has(cp);
158
+ }
159
+ /**
160
+ * Fonte de regex com todas as formas em que UM caractere que pode sair
161
+ * escapado estaria numa string JS do pacote. Medido nas rodadas finais 3 e 4
162
+ * do cli#81:
163
+ * - cru: nos bytes UTF-8 (Vite 5.4, esbuild com `charset: "utf8"`, e o SWC
164
+ * do Next 15.5 de U+0100 a U+FFFF), e o controle cru (a tabulação nos dois,
165
+ * e a quebra dentro de crase no esbuild);
166
+ * - o escape curto de `ESCAPE_CURTO`;
167
+ * - `\xNN`, até U+00FF: o SWC do Next 15.5 (`\xe1`, `\x01`, `\x7f`) e o
168
+ * esbuild no padrão (`\xE1`, `\x1B`, `\x7F`);
169
+ * - `\uNNNN`, no resto do plano básico: o esbuild no padrão (`\u20AC`);
170
+ * - o par `\uD83D\uDE00`, acima do plano básico: o SWC do Next 15.5;
171
+ * - `\u{1F600}`: o esbuild no padrão com alvo ES2015 ou mais novo.
172
+ * O hex casa nas duas caixas, porque o SWC grava `\xe1` e `\uD83D`, e o
173
+ * esbuild grava `\xE1`.
174
+ *
175
+ * A barra invertida NÃO tem a forma crua aqui (rodada final 5): dentro de uma
176
+ * string JS uma barra crua sempre abre um escape, e a forma crua `\` é prefixo
177
+ * do escape `\\`. Com as duas, cada barra do valor casava uma ou duas barras do
178
+ * pacote: o tempo crescia 1,7 vez por barra (24 barras, 1,6 s) e um valor com
179
+ * duas barras casava o JS de uma. Sem ela, as formas de cada caractere
180
+ * começam diferentes, no máximo uma casa em cada posição e a busca é linear.
181
+ * A barra crua fora de JS e JSON é coberta à parte, em `valorApareceNoPacote`.
182
+ */
183
+ function formasDaLetra(letra, emBytes) {
184
+ const cp = letra.codePointAt(0);
185
+ const crua = emBytes ? Buffer.from(letra, 'utf8').toString('latin1') : letra;
186
+ const formas = cp === 0x5c ? [] : [literalDeRegex(crua)];
187
+ const curto = ESCAPE_CURTO.get(cp);
188
+ if (curto)
189
+ formas.push(literalDeRegex(curto));
190
+ if (cp <= 0xff)
191
+ formas.push(`\\\\x${hexNasDuasCaixas(cp, 2)}`);
192
+ if (cp <= 0xffff)
193
+ formas.push(`\\\\u${hexNasDuasCaixas(cp, 4)}`);
194
+ if (cp > 0xffff) {
195
+ const alto = 0xd800 + ((cp - 0x10000) >> 10);
196
+ const baixo = 0xdc00 + ((cp - 0x10000) & 0x3ff);
197
+ formas.push(`\\\\u${hexNasDuasCaixas(alto, 4)}\\\\u${hexNasDuasCaixas(baixo, 4)}`);
198
+ }
199
+ formas.push(`\\\\u\\{0*${hexNasDuasCaixas(cp, 1)}\\}`);
200
+ return `(?:${formas.join('|')})`;
201
+ }
202
+ /**
203
+ * Regex que acha o valor no pacote com cada caractere que pode sair escapado
204
+ * em qualquer uma das suas formas, letra a letra: o SWC do Next mistura as
205
+ * formas dentro do MESMO valor (`chave-€-łódź` sai com `€`, `ł` e `ź` crus
206
+ * e `ó` como `\xf3`). `emBytes` diz se o pacote foi lido em latin1 (a forma
207
+ * crua é então a dos bytes UTF-8). Devolve `null` quando nenhum caractere do
208
+ * valor pode sair escapado: aí basta `includes`.
209
+ */
210
+ export function padraoDoValorNoPacote(valor, emBytes) {
211
+ const letras = [...valor];
212
+ if (!letras.some((l) => podeSairEscapado(l.codePointAt(0))))
213
+ return null;
214
+ let fonte = '';
215
+ for (const letra of letras) {
216
+ fonte += podeSairEscapado(letra.codePointAt(0))
217
+ ? formasDaLetra(letra, emBytes)
218
+ : literalDeRegex(letra);
219
+ }
220
+ return new RegExp(fonte);
221
+ }
222
+ /**
223
+ * Arquivo em que o valor só pode estar dentro de string JS ou JSON: ali a barra
224
+ * invertida nunca aparece crua (uma barra crua sempre abre um escape).
225
+ */
226
+ const BARRA_SEMPRE_ESCAPADA = new Set(['.js', '.mjs', '.cjs', '.json', '.map']);
227
+ function valorApareceNoPacote(arquivo, texto, valor, emBytes) {
228
+ const padrao = padraoDoValorNoPacote(valor, emBytes);
229
+ if (!padrao)
230
+ return texto.includes(valor);
231
+ if (padrao.test(texto))
232
+ return true;
233
+ // Valor com barra invertida num arquivo que não é JS nem JSON (`.txt`,
234
+ // `.html`, `.css`…): o texto pode trazer a barra crua, que a regex não aceita.
235
+ if (!valor.includes('\\') || BARRA_SEMPRE_ESCAPADA.has(path.extname(arquivo).toLowerCase()))
236
+ return false;
237
+ return texto.includes(emBytes ? Buffer.from(valor, 'utf8').toString('latin1') : valor);
238
+ }
239
+ /**
240
+ * Varre um conteúdo de arquivo. Um achado por (arquivo, padrão): desde a
241
+ * rodada final 5 do cli#81 cada nome tem até três valores procurados (o do
242
+ * Next, o do Vite e o cru), e quando um é pedaço do outro os dois casavam. A
243
+ * busca do nome para no primeiro valor achado.
244
+ */
109
245
  export function scanContent(file, content, envSecrets) {
110
246
  const text = typeof content === 'string' ? content : content.toString('latin1');
111
247
  const out = [];
@@ -115,8 +251,17 @@ export function scanContent(file, content, envSecrets) {
115
251
  if (m)
116
252
  out.push({ file, pattern: pat.id, label: pat.label, preview: maskSecret(m[0]) });
117
253
  }
254
+ const nomesAchados = new Set();
118
255
  for (const env of envSecrets) {
119
- if (text.includes(env.value)) {
256
+ if (nomesAchados.has(env.name))
257
+ continue;
258
+ // Rodadas finais 2 a 4 do cli#81: o pacote vem em bytes e vira texto em
259
+ // latin1 (1 byte = 1 caractere), mas o valor da env é texto, e cada
260
+ // caractere fora do ASCII, de controle, barra invertida, aspa ou barra pode
261
+ // estar no pacote cru ou escapado — ver `padraoDoValorNoPacote`. Para valor
262
+ // só com letras ASCII comuns nada muda.
263
+ if (valorApareceNoPacote(file, text, env.value, typeof content !== 'string')) {
264
+ nomesAchados.add(env.name);
120
265
  out.push({
121
266
  file,
122
267
  pattern: `public_env:${env.name}`,
@@ -128,61 +273,241 @@ export function scanContent(file, content, envSecrets) {
128
273
  return out;
129
274
  }
130
275
  // ─── Env pública com cara de segredo ────────────────────────────────────────
131
- /** Parser mínimo de .env (KEY=VALUE, aspas simples/duplas, comentários). */
276
+ /**
277
+ * Lê o .env com a regra do dotenv, a MESMA que o `@next/env` 15.5 e o Vite 5.4
278
+ * embutem (copiada dos dois no `node_modules` da core-deps; rodada final 4 do
279
+ * cli#81). A varredura procura no pacote o valor que o bundler leu, e o parser
280
+ * mínimo de antes divergia em quatro pontos, medidos num `next build` real:
281
+ * - `CHAVE=valor # nota`: o dotenv corta no `#`; o parser antigo levava a nota;
282
+ * - valor entre crases: o dotenv tira as crases; o antigo não;
283
+ * - `CHAVE="a\nb"`: entre aspas duplas, o dotenv troca `\n` e `\r` pela
284
+ * quebra de linha e pelo retorno; o antigo deixava a barra e a letra;
285
+ * - valor entre aspas em mais de uma linha: o dotenv junta; o antigo cortava.
286
+ */
132
287
  export function parseDotenv(content) {
288
+ const LINHA = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/gm;
133
289
  const out = {};
134
- for (const line of content.split(/\r?\n/)) {
135
- const t = line.trim();
136
- if (!t || t.startsWith('#'))
137
- continue;
138
- const body = t.startsWith('export ') ? t.slice(7) : t;
139
- const eq = body.indexOf('=');
140
- if (eq <= 0)
141
- continue;
142
- const k = body.slice(0, eq).trim();
143
- let v = body.slice(eq + 1).trim();
144
- if (v.length >= 2 && ((v[0] === '"' && v.endsWith('"')) || (v[0] === "'" && v.endsWith("'")))) {
145
- v = v.slice(1, -1);
146
- }
147
- out[k] = v;
290
+ const texto = content.replace(/\r\n?/gm, '\n');
291
+ let m;
292
+ while ((m = LINHA.exec(texto)) != null) {
293
+ let valor = (m[2] || '').trim();
294
+ const aspa = valor[0];
295
+ valor = valor.replace(/^(['"`])([\s\S]*)\1$/gm, '$2');
296
+ if (aspa === '"')
297
+ valor = valor.replace(/\\n/g, '\n').replace(/\\r/g, '\r');
298
+ out[m[1]] = valor;
148
299
  }
149
300
  return out;
150
301
  }
151
302
  /** Arquivos .env que o bundler lê num build de produção (o `.env*` NÃO viaja no tarball). */
152
303
  export const BUILD_ENV_FILES = ['.env', '.env.production', '.env.local', '.env.production.local'];
304
+ // ─── Expansão de $VAR, que o bundler faz depois do parse ────────────────────
305
+ //
306
+ // Rodada final 5 do cli#81: o Next 15.5 (`@next/env`) e o Vite 5.4 (`loadEnv`)
307
+ // expandem `$VAR`, `${VAR}` e `${VAR:-padrão}` depois de ler o `.env`, e é o
308
+ // valor EXPANDIDO que vai ao JS do navegador. Medido num `next build` e num
309
+ // `vite build` reais: com a busca pelo valor cru, 15 de 16 e 12 de 14 segredos
310
+ // passavam. Os dois embutem versões DIFERENTES do dotenv-expand, com
311
+ // resultados diferentes (no Vite, `$A-fim` lê o `-fim` como padrão e o some;
312
+ // no Next, `${A:-x}` com o `A` só no mesmo arquivo dá `x`). As duas regras
313
+ // estão copiadas abaixo, cada uma do seu `node_modules` na core-deps, e a
314
+ // varredura procura os dois resultados, e o valor cru para bundler que não expande.
315
+ /**
316
+ * Orçamento de substituições por valor e teto de tamanho do valor expandido.
317
+ * Um ciclo (`A=$B`, `B=$A`) estoura a pilha do bundler e derruba o build; aqui
318
+ * a expansão só para. Sem o teto, o valor de um ciclo crescia a cada passo e a
319
+ * varredura ficava sem memória (visto no teste do ciclo).
320
+ */
321
+ const ORCAMENTO_DE_SUBSTITUICOES = 200;
322
+ const TETO_DO_VALOR_EXPANDIDO = 32 * 1024;
323
+ /** `_resolveEscapeSequences`, igual nos dois: `\$` vira `$`. */
324
+ function resolverEscapeDoDolar(valor) {
325
+ return valor.replace(/\\\$/g, '$');
326
+ }
327
+ /** `_searchLast` do `@next/env` 15.5. */
328
+ function ultimaPosicao(texto, re) {
329
+ const achados = Array.from(texto.matchAll(re));
330
+ return achados.length > 0 ? achados[achados.length - 1].index : -1;
331
+ }
332
+ /**
333
+ * `_interpolate` do `@next/env` 15.5, copiado: troca o `$` mais à direita sem
334
+ * barra antes, e repete. A ordem é a do Next, ambiente, depois padrão, depois
335
+ * o arquivo: com `${A:-x}`, um `A` que só existe no mesmo arquivo perde para o `x`.
336
+ */
337
+ function interpolarComoONext(valor, ambiente, arquivo, orcamento = { resta: ORCAMENTO_DE_SUBSTITUICOES }) {
338
+ if (--orcamento.resta < 0 || valor.length > TETO_DO_VALOR_EXPANDIDO)
339
+ return valor;
340
+ const ultimo = ultimaPosicao(valor, /(?!(?<=\\))\$/g);
341
+ if (ultimo === -1)
342
+ return valor;
343
+ const m = valor.slice(ultimo).match(/((?!(?<=\\))\${?([\w]+)(?::-([^}\\]*))?}?)/);
344
+ if (m == null)
345
+ return valor;
346
+ const [, grupo, nome, padrao] = m;
347
+ return interpolarComoONext(valor.replace(grupo, ambiente[nome] || padrao || arquivo[nome] || ''), ambiente, arquivo, orcamento);
348
+ }
349
+ /** `expand` do `@next/env` 15.5: lê do ambiente, que é o `process.env` do build, e o altera ao fim de cada arquivo. */
350
+ function expandirComoONext(arquivo, ambiente) {
351
+ for (const chave in arquivo) {
352
+ const valor = Object.prototype.hasOwnProperty.call(ambiente, chave) ? ambiente[chave] : arquivo[chave];
353
+ arquivo[chave] = resolverEscapeDoDolar(interpolarComoONext(valor, ambiente, arquivo));
354
+ }
355
+ for (const chave in arquivo)
356
+ ambiente[chave] = arquivo[chave];
357
+ }
358
+ /**
359
+ * O env do build no Next 15.5 (`loadEnvConfig` → `processEnv`): os arquivos vêm
360
+ * do de maior prioridade para o de menor, cada um expandido com o ambiente, e
361
+ * a chave que já está no ambiente do build não é trocada por arquivo nenhum.
362
+ */
363
+ function envComoONextCarrega(arquivos, ambienteDoBuild) {
364
+ const ambiente = { ...ambienteDoBuild };
365
+ const dosArquivos = {};
366
+ for (const lido of arquivos) {
367
+ const arquivo = { ...lido };
368
+ expandirComoONext(arquivo, ambiente);
369
+ for (const chave of Object.keys(arquivo)) {
370
+ if (dosArquivos[chave] === undefined && ambienteDoBuild[chave] === undefined)
371
+ dosArquivos[chave] = arquivo[chave];
372
+ }
373
+ }
374
+ return Object.assign(ambiente, dosArquivos);
375
+ }
376
+ /** `DOTENV_SUBSTITUTION_REGEX` do dotenv-expand que o Vite 5.4 embute, copiada. */
377
+ const SUBSTITUICAO_DO_VITE = /(\\)?(\$)(?!\()(\{?)([\w.]+)(?::?-((?:\$\{(?:\$\{(?:\$\{[^}]*\}|[^}])*}|[^}])*}|[^}])+))?(\}?)/gi;
378
+ /**
379
+ * `interpolate` do Vite 5.4, copiado. Difere do Next: troca da esquerda para a
380
+ * direita, aceita `-padrão` sem os dois-pontos (por isso `$A-fim` vira só o
381
+ * valor de `A`) e prefere o ambiente, depois o arquivo, depois o padrão.
382
+ */
383
+ function interpolarComoOVite(valor, ambiente, arquivo, orcamento = { resta: ORCAMENTO_DE_SUBSTITUICOES }) {
384
+ if (--orcamento.resta < 0 || valor.length > TETO_DO_VALOR_EXPANDIDO)
385
+ return valor;
386
+ return valor.replace(SUBSTITUICAO_DO_VITE, (casado, escapado, _dolar, _abre, chave, padrao) => {
387
+ if (escapado === '\\')
388
+ return casado.slice(1);
389
+ if (ambiente[chave]) {
390
+ if (ambiente[chave] === arquivo[chave])
391
+ return ambiente[chave];
392
+ return interpolarComoOVite(ambiente[chave], ambiente, arquivo, orcamento);
393
+ }
394
+ if (arquivo[chave]) {
395
+ if (arquivo[chave] === valor)
396
+ return arquivo[chave];
397
+ return interpolarComoOVite(arquivo[chave], ambiente, arquivo, orcamento);
398
+ }
399
+ if (padrao) {
400
+ if (padrao.startsWith('$'))
401
+ return interpolarComoOVite(padrao, ambiente, arquivo, orcamento);
402
+ return padrao;
403
+ }
404
+ return '';
405
+ });
406
+ }
407
+ /** `expand` do Vite 5.4: a chave que o ambiente já tem com OUTRO valor fica com o do ambiente, sem expandir. */
408
+ function expandirComoOVite(arquivo, ambiente) {
409
+ for (const chave in arquivo) {
410
+ let valor = arquivo[chave];
411
+ if (Object.prototype.hasOwnProperty.call(ambiente, chave)) {
412
+ valor = ambiente[chave] === arquivo[chave] ? interpolarComoOVite(valor, ambiente, arquivo) : ambiente[chave];
413
+ }
414
+ else {
415
+ valor = interpolarComoOVite(valor, ambiente, arquivo);
416
+ }
417
+ arquivo[chave] = resolverEscapeDoDolar(valor);
418
+ }
419
+ for (const chave in arquivo)
420
+ ambiente[chave] = arquivo[chave];
421
+ }
422
+ /**
423
+ * O env que o Vite 5.4 expõe (`loadEnv`): junta os arquivos, o de maior
424
+ * prioridade por último; expande uma vez, com uma CÓPIA do ambiente do build;
425
+ * e a variável do ambiente do build vence a do arquivo.
426
+ */
427
+ function envComoOViteCarrega(arquivos, ambienteDoBuild) {
428
+ const arquivo = Object.fromEntries(arquivos.flatMap((a) => Object.entries(a)));
429
+ expandirComoOVite(arquivo, { ...ambienteDoBuild });
430
+ return { ...arquivo, ...ambienteDoBuild };
431
+ }
153
432
  /**
154
433
  * Coleta envs públicas com nome sensível a partir do ambiente do build:
155
434
  * `.env*` do projeto, `--env-file` e `process.env` do CLI (o build roda com ele).
435
+ *
436
+ * Rodada final 2 do cli#81: cada arquivo é lido COMO O BUNDLER O LÊ, porque a
437
+ * varredura procura no pacote o valor que o bundler gravou lá. O `@next/env`
438
+ * (Next 15.5) lê o `.env` com `readFileSync(…, "utf8")` e o Vite 5.4 passa os
439
+ * bytes ao `dotenv`, que faz `toString()`: os dois trocam byte que não é UTF-8
440
+ * por U+FFFD, e é esse valor trocado que vai ao JS do navegador. Por isso aqui
441
+ * a leitura NÃO recusa (como faz o `--env-file`, que a CLI injeta ela mesma):
442
+ * recusar esconderia o valor que de fato está no pacote. Em vez de passar
443
+ * calado, `aoLerForaDeUtf8` recebe cada arquivo que não é UTF-8 limpo, para o
444
+ * deploy avisar que o app vai receber outro valor.
445
+ *
446
+ * Rodada final 5: o `--env-file` não é um `.env` que o bundler lê. O
447
+ * `neetru build` o injeta no ambiente do processo do build, com a regra
448
+ * própria da CLI (`variaveisDoEnvFile`). Por isso ele entra aqui como ambiente:
449
+ * vence os arquivos, serve de fonte para a expansão e não é expandido. Cada
450
+ * valor é procurado como o Next o deixa, como o Vite o deixa e cru.
156
451
  */
157
- export async function collectPublicEnvSecrets(cwd, envFile, processEnv = process.env) {
158
- const merged = {};
159
- const files = BUILD_ENV_FILES.map((f) => path.join(cwd, f));
160
- if (envFile)
161
- files.push(path.resolve(cwd, envFile));
162
- for (const f of files) {
452
+ export async function collectPublicEnvSecrets(cwd, envFile, processEnv = process.env, aoLerForaDeUtf8) {
453
+ const lidos = new Map();
454
+ for (const nome of BUILD_ENV_FILES) {
455
+ let bruto;
163
456
  try {
164
- Object.assign(merged, parseDotenv(await fsp.readFile(f, 'utf8')));
457
+ bruto = await fsp.readFile(path.join(cwd, nome));
165
458
  }
166
459
  catch {
167
- /* arquivo ausente/ilegível — ignora */
460
+ continue; /* arquivo ausente/ilegível — ignora */
168
461
  }
462
+ const problema = problemaDeCodificacao(bruto);
463
+ if (problema)
464
+ aoLerForaDeUtf8?.(nome, problema);
465
+ lidos.set(nome, parseDotenv(bruto.toString('utf8')));
169
466
  }
467
+ // O ambiente do processo do build: o da CLI e, por cima, o `--env-file`.
468
+ const ambiente = {};
170
469
  for (const [k, v] of Object.entries(processEnv)) {
171
470
  if (typeof v === 'string')
172
- merged[k] = v;
471
+ ambiente[k] = v;
472
+ }
473
+ if (envFile) {
474
+ const caminho = path.resolve(cwd, envFile);
475
+ let bruto;
476
+ try {
477
+ bruto = await fsp.readFile(caminho);
478
+ }
479
+ catch {
480
+ bruto = undefined; /* ausente/ilegível — ignora */
481
+ }
482
+ if (bruto) {
483
+ const problema = problemaDeCodificacao(bruto);
484
+ if (problema)
485
+ aoLerForaDeUtf8?.(path.relative(cwd, caminho) || caminho, problema);
486
+ Object.assign(ambiente, variaveisDoEnvFile(semBomInicial(bruto.toString('utf8'))));
487
+ }
173
488
  }
489
+ const emOrdem = (nomes) => nomes.flatMap((n) => {
490
+ const lido = lidos.get(n);
491
+ return lido ? [lido] : [];
492
+ });
493
+ // Cru: do de menor para o de maior prioridade (o `.env.local` vence o `.env.production`), e o ambiente por cima.
494
+ const cru = Object.assign({}, ...emOrdem(BUILD_ENV_FILES), ambiente);
495
+ const comoONext = envComoONextCarrega(emOrdem(['.env.production.local', '.env.local', '.env.production', '.env']), ambiente);
496
+ const comoOVite = envComoOViteCarrega(emOrdem(['.env', '.env.local', '.env.production', '.env.production.local']), ambiente);
174
497
  const seen = new Set();
175
498
  const out = [];
176
- for (const [name, value] of Object.entries(merged)) {
177
- if (!isPublicSensitiveEnvName(name))
178
- continue;
179
- if (value.length < MIN_ENV_VALUE_LENGTH || isKnownPublicValue(value))
180
- continue;
181
- const key = `${name}\u0000${value}`;
182
- if (seen.has(key))
183
- continue;
184
- seen.add(key);
185
- out.push({ name, value });
499
+ for (const fonte of [cru, comoONext, comoOVite]) {
500
+ for (const [name, value] of Object.entries(fonte)) {
501
+ if (!isPublicSensitiveEnvName(name))
502
+ continue;
503
+ if (value.length < MIN_ENV_VALUE_LENGTH || isKnownPublicValue(value))
504
+ continue;
505
+ const key = `${name}\u0000${value}`;
506
+ if (seen.has(key))
507
+ continue;
508
+ seen.add(key);
509
+ out.push({ name, value });
510
+ }
186
511
  }
187
512
  return out;
188
513
  }
@@ -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[]>;