@neetru/cli 2.31.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 (243) hide show
  1. package/CHANGELOG.md +91 -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 +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 +91 -2
  27. package/dist/commands/deploy.js +414 -69
  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 +7 -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 +84 -16
  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 +6 -4
  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/mobile-local/mobile-json.js +10 -4
  91. package/dist/lib/pickers.d.ts +44 -7
  92. package/dist/lib/pickers.js +60 -4
  93. package/dist/lib/product-docs/sync.d.ts +6 -0
  94. package/dist/lib/product-docs/sync.js +10 -1
  95. package/dist/lib/public-secret-scan.d.ts +44 -3
  96. package/dist/lib/public-secret-scan.js +361 -36
  97. package/dist/lib/render.d.ts +16 -0
  98. package/dist/lib/render.js +34 -4
  99. package/dist/lib/segredo-produto.d.ts +124 -0
  100. package/dist/lib/segredo-produto.js +336 -0
  101. package/dist/lib/texto-utf8.d.ts +96 -0
  102. package/dist/lib/texto-utf8.js +196 -0
  103. package/package.json +2 -1
  104. package/dist/cli-kit/format.js.map +0 -1
  105. package/dist/cli-kit/glyphs.js.map +0 -1
  106. package/dist/cli-kit/index.js.map +0 -1
  107. package/dist/cli-kit/palette.js.map +0 -1
  108. package/dist/commands/add.js.map +0 -1
  109. package/dist/commands/admin/iam-check.js.map +0 -1
  110. package/dist/commands/admin/oidc-login.js.map +0 -1
  111. package/dist/commands/admin/sdk-credential.js.map +0 -1
  112. package/dist/commands/agent-release.js.map +0 -1
  113. package/dist/commands/agent-write.js.map +0 -1
  114. package/dist/commands/ai-models.js.map +0 -1
  115. package/dist/commands/ai.js.map +0 -1
  116. package/dist/commands/api-catalog.js.map +0 -1
  117. package/dist/commands/archive.js.map +0 -1
  118. package/dist/commands/artifact-registry.js.map +0 -1
  119. package/dist/commands/audit.js.map +0 -1
  120. package/dist/commands/auth.js.map +0 -1
  121. package/dist/commands/autocomplete.js.map +0 -1
  122. package/dist/commands/billing.js.map +0 -1
  123. package/dist/commands/bootstrap.js.map +0 -1
  124. package/dist/commands/bug.js.map +0 -1
  125. package/dist/commands/build.js.map +0 -1
  126. package/dist/commands/chat-menu.js.map +0 -1
  127. package/dist/commands/chat.js.map +0 -1
  128. package/dist/commands/cloud-run.js.map +0 -1
  129. package/dist/commands/code.js.map +0 -1
  130. package/dist/commands/config.js.map +0 -1
  131. package/dist/commands/customers.js.map +0 -1
  132. package/dist/commands/db-move.js.map +0 -1
  133. package/dist/commands/db.js.map +0 -1
  134. package/dist/commands/deploy.js.map +0 -1
  135. package/dist/commands/deployments.js.map +0 -1
  136. package/dist/commands/dev-context.js.map +0 -1
  137. package/dist/commands/dev-up.js.map +0 -1
  138. package/dist/commands/dev.js.map +0 -1
  139. package/dist/commands/docs-produto.js.map +0 -1
  140. package/dist/commands/docs.js.map +0 -1
  141. package/dist/commands/doctor.js.map +0 -1
  142. package/dist/commands/domains.js.map +0 -1
  143. package/dist/commands/dr.js.map +0 -1
  144. package/dist/commands/env.js.map +0 -1
  145. package/dist/commands/hosting-write.js.map +0 -1
  146. package/dist/commands/infra-read.js.map +0 -1
  147. package/dist/commands/init.js.map +0 -1
  148. package/dist/commands/login.js.map +0 -1
  149. package/dist/commands/logout.js.map +0 -1
  150. package/dist/commands/logs.js.map +0 -1
  151. package/dist/commands/mobile.js.map +0 -1
  152. package/dist/commands/mocks.js.map +0 -1
  153. package/dist/commands/new.js.map +0 -1
  154. package/dist/commands/open.js.map +0 -1
  155. package/dist/commands/ops.js.map +0 -1
  156. package/dist/commands/products-db.js.map +0 -1
  157. package/dist/commands/products.js.map +0 -1
  158. package/dist/commands/promote.js.map +0 -1
  159. package/dist/commands/publish.js.map +0 -1
  160. package/dist/commands/schema.js.map +0 -1
  161. package/dist/commands/servers.js.map +0 -1
  162. package/dist/commands/smoke.js.map +0 -1
  163. package/dist/commands/status.js.map +0 -1
  164. package/dist/commands/support.js.map +0 -1
  165. package/dist/commands/surface-status.js.map +0 -1
  166. package/dist/commands/tenants.js.map +0 -1
  167. package/dist/commands/ui.js.map +0 -1
  168. package/dist/commands/upgrade.js.map +0 -1
  169. package/dist/commands/validate.js.map +0 -1
  170. package/dist/commands/vm-memory.js.map +0 -1
  171. package/dist/commands/vm.js.map +0 -1
  172. package/dist/commands/webhooks.js.map +0 -1
  173. package/dist/commands/whoami.js.map +0 -1
  174. package/dist/commands/workspaces.js.map +0 -1
  175. package/dist/index.js.map +0 -1
  176. package/dist/lib/agent-artifact-url.js.map +0 -1
  177. package/dist/lib/agent-commands.js.map +0 -1
  178. package/dist/lib/ai/auth-discovery.js.map +0 -1
  179. package/dist/lib/ai/context.js.map +0 -1
  180. package/dist/lib/ai/orchestrator.js.map +0 -1
  181. package/dist/lib/api-client.js.map +0 -1
  182. package/dist/lib/api-error.js.map +0 -1
  183. package/dist/lib/auth.js.map +0 -1
  184. package/dist/lib/cli-read.js.map +0 -1
  185. package/dist/lib/cli-write.js.map +0 -1
  186. package/dist/lib/code-agent-budget.js.map +0 -1
  187. package/dist/lib/code-agent-files.js.map +0 -1
  188. package/dist/lib/code-agent-gateway.js.map +0 -1
  189. package/dist/lib/code-agent-git-tools.js.map +0 -1
  190. package/dist/lib/code-agent-protocol.js.map +0 -1
  191. package/dist/lib/code-agent-session.js.map +0 -1
  192. package/dist/lib/code-agent-settings.js.map +0 -1
  193. package/dist/lib/code-agent-skills.js.map +0 -1
  194. package/dist/lib/code-agent-subagents.js.map +0 -1
  195. package/dist/lib/code-agent-tools.js.map +0 -1
  196. package/dist/lib/code-agent-tui.js.map +0 -1
  197. package/dist/lib/code-agent-types.js.map +0 -1
  198. package/dist/lib/code-agent-ui.js.map +0 -1
  199. package/dist/lib/code-agent-web-tools.js.map +0 -1
  200. package/dist/lib/code-agent-write-tools.js.map +0 -1
  201. package/dist/lib/compose-generator.js.map +0 -1
  202. package/dist/lib/config-schema.js.map +0 -1
  203. package/dist/lib/config.js.map +0 -1
  204. package/dist/lib/db-local/db-json.js.map +0 -1
  205. package/dist/lib/db-local/env.js.map +0 -1
  206. package/dist/lib/db-local/fingerprint.js.map +0 -1
  207. package/dist/lib/db-local/index.js.map +0 -1
  208. package/dist/lib/db-pipeline/build-deps.js.map +0 -1
  209. package/dist/lib/db-pipeline/errors.js.map +0 -1
  210. package/dist/lib/db-pipeline/index.js.map +0 -1
  211. package/dist/lib/db-pipeline/pipeline.js.map +0 -1
  212. package/dist/lib/db-pipeline/rehearse.js.map +0 -1
  213. package/dist/lib/db-pipeline/types.js.map +0 -1
  214. package/dist/lib/dev-local-context.js.map +0 -1
  215. package/dist/lib/iam-preflight.js.map +0 -1
  216. package/dist/lib/mobile-local/index.js.map +0 -1
  217. package/dist/lib/mobile-local/mobile-json.js.map +0 -1
  218. package/dist/lib/pickers.js.map +0 -1
  219. package/dist/lib/product-config.js.map +0 -1
  220. package/dist/lib/product-docs/check.js.map +0 -1
  221. package/dist/lib/product-docs/config.js.map +0 -1
  222. package/dist/lib/product-docs/dates.js.map +0 -1
  223. package/dist/lib/product-docs/frontmatter.js.map +0 -1
  224. package/dist/lib/product-docs/git.js.map +0 -1
  225. package/dist/lib/product-docs/markdown.js.map +0 -1
  226. package/dist/lib/product-docs/ops.js.map +0 -1
  227. package/dist/lib/product-docs/structure.js.map +0 -1
  228. package/dist/lib/product-docs/sync.js.map +0 -1
  229. package/dist/lib/product-docs/taxonomy.js.map +0 -1
  230. package/dist/lib/product-docs/templates.js.map +0 -1
  231. package/dist/lib/public-secret-scan.js.map +0 -1
  232. package/dist/lib/render.js.map +0 -1
  233. package/dist/lib/schema-cache.js.map +0 -1
  234. package/dist/lib/sse.js.map +0 -1
  235. package/dist/lib/telemetry.js.map +0 -1
  236. package/dist/lib/token-resolver.js.map +0 -1
  237. package/dist/lib/url-allowlist.js.map +0 -1
  238. package/dist/lib/version-check.js.map +0 -1
  239. package/dist/utils/logger.js.map +0 -1
  240. package/dist/utils/logo.js.map +0 -1
  241. package/dist/utils/safe-exit.js.map +0 -1
  242. package/dist/utils/spinner-registry.js.map +0 -1
  243. package/dist/version.js.map +0 -1
@@ -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;
@@ -0,0 +1,196 @@
1
+ /**
2
+ * Bytes lidos de arquivo ou da entrada padrão → texto, SÓ quando são UTF-8 de
3
+ * verdade e não trazem o rastro de uma conversão que perdeu letra.
4
+ *
5
+ * Por que existe: `Buffer#toString('utf8')` e `fs.readFile(…, 'utf8')` trocam
6
+ * cada byte que não é UTF-8 por U+FFFD, sem erro. Um arquivo ANSI com "é"
7
+ * virava "senha\uFFFD" (ou "Caf\uFFFD"), e o valor seguia adiante diferente do
8
+ * arquivo, sem aviso. Quem usa:
9
+ *
10
+ * - `neetru secrets set --from-file | --stdin` (`segredo-produto.ts`): o
11
+ * Secret Manager guardaria outro segredo (revisão do #81, M-r2-1);
12
+ * - o `--env-file` do `neetru deploy` e do `neetru build`: o app ou o build
13
+ * receberiam outro valor (revisão do #81, N-r3-1);
14
+ * - o `neetru env switch`, que regrava o `.env.local` (rodada final 2 do
15
+ * #81): recusa antes de gravar o que a regravação estragaria;
16
+ * - a varredura de segredo do deploy (`collectPublicEnvSecrets`), que lê os
17
+ * `.env*` COMO O BUNDLER LÊ e só avisa (rodada final 2 do #81).
18
+ *
19
+ * As mensagens dizem como converter, e nunca repetem o conteúdo.
20
+ */
21
+ import { isUtf8 } from 'node:buffer';
22
+ /**
23
+ * O texto não está em UTF-8 (ou traz U+FFFD) — recusa com o código
24
+ * `invalid_encoding`. A mensagem diz como salvar o arquivo, nunca o que ele
25
+ * contém.
26
+ */
27
+ export class CodificacaoInvalida extends Error {
28
+ constructor(mensagem) {
29
+ super(mensagem);
30
+ this.name = 'CodificacaoInvalida';
31
+ }
32
+ }
33
+ /** U+FFFD: o caractere que um decodificador UTF-8 põe no lugar de byte que não é UTF-8. */
34
+ export const CARACTERE_DE_TROCA = '\uFFFD';
35
+ /**
36
+ * Tira UMA marca BOM (U+FEFF) do começo — a que o Bloco de Notas ("UTF-8 com
37
+ * BOM") e o `Set-Content -Encoding utf8` do Windows PowerShell 5.1 gravam no
38
+ * arquivo (medido: `ef bb bf …`). O PowerShell 7 grava sem a marca. Revisão do
39
+ * #81 (B3): sem isto o `trim()` de `problemaNoValor` via a marca como espaço
40
+ * e recusava com "espaço nas pontas", sem espaço nenhum à vista. Só uma, pelo
41
+ * mesmo motivo de `semQuebraFinal`.
42
+ */
43
+ export function semBomInicial(texto) {
44
+ return texto.charCodeAt(0) === 0xfeff ? texto.slice(1) : texto;
45
+ }
46
+ /**
47
+ * Como converter um arquivo ANSI para UTF-8 — uma receita por versão do
48
+ * PowerShell, porque o `Get-Content` lê diferente nas duas.
49
+ *
50
+ * Revisão do #81 (M-r3-1): a receita era só a do 5.1. No Windows PowerShell
51
+ * 5.1, o `Get-Content` lê arquivo sem marca em ANSI, e o `Set-Content
52
+ * -Encoding utf8` grava UTF-8 COM a marca BOM (`semBomInicial` a tira). No
53
+ * PowerShell 7, o `Get-Content` lê arquivo sem marca em UTF-8 (o padrão é
54
+ * `utf8NoBOM`): o "é" do ANSI vira U+FFFD antes de chegar à CLI, e o
55
+ * `Set-Content -Encoding utf8` (no 7, sem BOM) grava esse U+FFFD em UTF-8
56
+ * válido. Lá a leitura certa é `-Encoding ansi` (página ANSI da cultura,
57
+ * existe a partir do 7.4, segundo a documentação do `Get-Content`). No 5.1,
58
+ * `-Encoding ansi` dá erro de parâmetro e não cria o arquivo novo (medido), e
59
+ * a receita do 5.1 rodada no 7 grava U+FFFD — que `decodificarUtf8` recusa.
60
+ * Assim, a receita da versão errada nunca passa calada.
61
+ */
62
+ export const COMO_CONVERTER_ANSI = 'No Windows PowerShell 5.1: Get-Content <arquivo> | Set-Content -Encoding utf8 <arquivo-novo>. ' +
63
+ 'No PowerShell 7.4 ou mais novo: Get-Content -Encoding ansi <arquivo> | Set-Content -Encoding utf8 ' +
64
+ '<arquivo-novo>. Ou, no Bloco de Notas, "Salvar como" com a codificação UTF-8.';
65
+ /**
66
+ * UTF-16 tem marca (FF FE / FE FF), e a marca vence o `-Encoding` na leitura
67
+ * (medido no 5.1: o arquivo do `>` lido com `-Encoding UTF8` sai certo). Por
68
+ * isso a mesma receita serve nas duas versões.
69
+ */
70
+ export const COMO_CONVERTER_UTF16 = 'No PowerShell (5.1 ou 7): Get-Content <arquivo> | Set-Content -Encoding utf8 <arquivo-novo>. Ou, ' +
71
+ 'no Bloco de Notas, "Salvar como" com a codificação UTF-8.';
72
+ const SEM_CONTEUDO = 'O conteúdo não é repetido aqui. Nada foi enviado.';
73
+ const SEM_CONTEUDO_ENV = 'O conteúdo não é repetido aqui, e nenhuma variável do arquivo foi usada.';
74
+ function recusaUtf16(origem) {
75
+ const motivo = 'o `>` e o Out-File do Windows PowerShell 5.1 gravam em UTF-16';
76
+ if (origem === 'arquivo') {
77
+ return new CodificacaoInvalida(`O arquivo está gravado em UTF-16, e o valor só é lido em UTF-8 (${motivo}). Salve-o em UTF-8 e ` +
78
+ `rode de novo. ${COMO_CONVERTER_UTF16} ${SEM_CONTEUDO}`);
79
+ }
80
+ if (origem === 'env-file') {
81
+ return new CodificacaoInvalida(`O --env-file está gravado em UTF-16, e ele só é lido em UTF-8 (${motivo}). Salve-o em UTF-8 e ` +
82
+ `rode de novo. ${COMO_CONVERTER_UTF16} ${SEM_CONTEUDO_ENV}`);
83
+ }
84
+ return new CodificacaoInvalida('A entrada chegou em UTF-16, e o valor só é lido em UTF-8. Mande o valor em UTF-8, ou grave-o ' +
85
+ `num arquivo UTF-8 e use --from-file. ${SEM_CONTEUDO}`);
86
+ }
87
+ const PROVAVEL_ANSI = 'provavelmente ANSI (Windows-1252), que é o que o Set-Content do Windows PowerShell 5.1 grava ' +
88
+ 'sem -Encoding';
89
+ /** Exportada para o `--stdin` que já chega decodificado (`setEncoding`). */
90
+ export function recusaNaoUtf8(origem) {
91
+ const troca = 'Lido assim, cada letra acentuada seria trocada por um caractere de substituição';
92
+ if (origem === 'arquivo') {
93
+ return new CodificacaoInvalida(`O arquivo não está em UTF-8 — ${PROVAVEL_ANSI}. ${troca}, e o Secret Manager guardaria um ` +
94
+ `segredo diferente do que você tem. Salve-o em UTF-8 e rode de novo. ${COMO_CONVERTER_ANSI} ` +
95
+ SEM_CONTEUDO);
96
+ }
97
+ if (origem === 'env-file') {
98
+ return new CodificacaoInvalida(`O --env-file não está em UTF-8 — ${PROVAVEL_ANSI}. ${troca}, e o app receberia um valor ` +
99
+ `diferente do que está no arquivo. Salve-o em UTF-8 e rode de novo. ${COMO_CONVERTER_ANSI} ` +
100
+ SEM_CONTEUDO_ENV);
101
+ }
102
+ return new CodificacaoInvalida(`A entrada não está em UTF-8. ${troca}, e o Secret Manager guardaria um segredo diferente do ` +
103
+ `que você tem. Mande o valor em UTF-8, ou grave-o num arquivo UTF-8 e use --from-file. ` +
104
+ SEM_CONTEUDO);
105
+ }
106
+ /**
107
+ * Revisão do #81 (M-r3-1): o texto é UTF-8 válido, mas traz U+FFFD — o rastro
108
+ * de uma conversão feita ANTES da CLI que não reconheceu uma letra. É o que a
109
+ * receita do 5.1, rodada no PowerShell 7, grava (`… ef bf bd …`).
110
+ */
111
+ function recusaTrocaGravada(origem) {
112
+ const rastro = 'traz o caractere de substituição (U+FFFD), o rastro de uma conversão que não reconheceu uma ' +
113
+ 'letra — como a do Get-Content do PowerShell 7 sem -Encoding ansi, que lê o arquivo ANSI como UTF-8';
114
+ if (origem === 'arquivo') {
115
+ return new CodificacaoInvalida(`O arquivo está em UTF-8, mas ${rastro}. Gravado assim, o segredo seria diferente do original. ` +
116
+ `Converta de novo a partir do arquivo original. ${COMO_CONVERTER_ANSI} ${SEM_CONTEUDO}`);
117
+ }
118
+ if (origem === 'env-file') {
119
+ return new CodificacaoInvalida(`O --env-file está em UTF-8, mas ${rastro}. Lido assim, o app receberia um valor diferente do ` +
120
+ `original. Converta de novo a partir do arquivo original. ${COMO_CONVERTER_ANSI} ${SEM_CONTEUDO_ENV}`);
121
+ }
122
+ return new CodificacaoInvalida(`A entrada ${rastro}. Gravado assim, o segredo seria diferente do original. Grave o valor num ` +
123
+ `arquivo UTF-8 e use --from-file. ${COMO_CONVERTER_ANSI} ${SEM_CONTEUDO}`);
124
+ }
125
+ export function problemaDeCodificacao(bruto) {
126
+ const marcaUtf16 = bruto.length >= 2 && ((bruto[0] === 0xff && bruto[1] === 0xfe) || (bruto[0] === 0xfe && bruto[1] === 0xff));
127
+ if (marcaUtf16)
128
+ return 'utf16';
129
+ if (!isUtf8(bruto))
130
+ return 'nao-utf8';
131
+ if (bruto.toString('utf8').includes(CARACTERE_DE_TROCA))
132
+ return 'troca-gravada';
133
+ return null;
134
+ }
135
+ /**
136
+ * Converte os bytes lidos em texto, RECUSANDO o que não é o arquivo de origem
137
+ * (os três casos de `problemaDeCodificacao`). No U+FFFD, a CLI fica mais
138
+ * estrita que o Core.
139
+ *
140
+ * A marca BOM do UTF-8 (EF BB BF) passa daqui, e quem chama a tira com
141
+ * `semBomInicial`.
142
+ */
143
+ export function decodificarUtf8(bruto, origem) {
144
+ const problema = problemaDeCodificacao(bruto);
145
+ if (problema === 'utf16')
146
+ throw recusaUtf16(origem);
147
+ if (problema === 'nao-utf8')
148
+ throw recusaNaoUtf8(origem);
149
+ if (problema === 'troca-gravada')
150
+ throw recusaTrocaGravada(origem);
151
+ return bruto.toString('utf8');
152
+ }
153
+ /**
154
+ * `neetru env switch` (rodada final 2 do #81, achado BAIXA): o comando lia o
155
+ * `.env.local` com `readFile(…, 'utf-8')` e REGRAVAVA o arquivo inteiro. Medido
156
+ * no processo real: um `.env.local` ANSI com `NOME_LOJA=Café` terminava com
157
+ * `ef bf bd` no lugar do `e9`, e um UTF-16 virava texto ilegível — exit 0 e
158
+ * "✓" nos dois. Agora ele recusa ANTES de gravar e não toca no arquivo.
159
+ *
160
+ * Recusa só o que a regravação estragaria (`utf16` e `nao-utf8`). Um U+FFFD
161
+ * que já está no arquivo (`troca-gravada`) sai byte a byte igual — o comando
162
+ * não perde nada ali, então segue.
163
+ */
164
+ export function recusaDoEnvSwitch(arquivo, problema) {
165
+ const fecho = 'O arquivo não foi alterado, e o conteúdo dele não é repetido aqui.';
166
+ if (problema === 'utf16') {
167
+ return new CodificacaoInvalida(`O ${arquivo} está gravado em UTF-16 (o \`>\` e o Out-File do Windows PowerShell 5.1 gravam ` +
168
+ 'assim). O env switch regrava o arquivo inteiro em UTF-8, e lido como UTF-8 ele viraria texto ' +
169
+ `ilegível no disco. Salve-o em UTF-8 e rode de novo. ${COMO_CONVERTER_UTF16} ${fecho}`);
170
+ }
171
+ return new CodificacaoInvalida(`O ${arquivo} não está em UTF-8 — ${PROVAVEL_ANSI}. O env switch regrava o arquivo inteiro em ` +
172
+ 'UTF-8: cada letra acentuada viraria um caractere de substituição, e a letra original se ' +
173
+ `perderia no disco. Salve-o em UTF-8 e rode de novo. ${COMO_CONVERTER_ANSI} ${fecho}`);
174
+ }
175
+ /**
176
+ * Varredura de segredo público do `neetru deploy` (rodada final 2 do #81): um
177
+ * `.env*` que o bundler lê não está em UTF-8 limpo. A varredura segue — ela
178
+ * confere o valor como o bundler o lê —, mas o app vai receber outro valor, e
179
+ * isso não passa calado. Nunca repete o conteúdo.
180
+ */
181
+ export function avisoEnvDoBundlerForaDeUtf8(arquivo, problema) {
182
+ const fecho = 'A varredura de segredo seguiu, conferindo o valor como o bundler o lê. O conteúdo não é repetido aqui.';
183
+ if (problema === 'utf16') {
184
+ return (`Aviso: o ${arquivo} está gravado em UTF-16. O Next e o Vite leem os .env em UTF-8, e lidas assim ` +
185
+ `as variáveis dele não chegam ao app como estão escritas. Salve-o em UTF-8. ${COMO_CONVERTER_UTF16} ${fecho}`);
186
+ }
187
+ if (problema === 'nao-utf8') {
188
+ return (`Aviso: o ${arquivo} não está em UTF-8 — ${PROVAVEL_ANSI}. O Next e o Vite leem os .env em UTF-8, e ` +
189
+ 'cada letra acentuada chega ao app como o caractere de substituição (U+FFFD), diferente do arquivo. ' +
190
+ `Salve-o em UTF-8. ${COMO_CONVERTER_ANSI} ${fecho}`);
191
+ }
192
+ return (`Aviso: o ${arquivo} está em UTF-8, mas traz o caractere de substituição (U+FFFD), o rastro de uma ` +
193
+ 'conversão que não reconheceu uma letra. O app recebe esse caractere no lugar da letra original. ' +
194
+ `Converta de novo a partir do arquivo original. ${COMO_CONVERTER_ANSI} ${fecho}`);
195
+ }
196
+ //# sourceMappingURL=texto-utf8.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neetru/cli",
3
- "version": "2.31.0",
3
+ "version": "2.32.0",
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": [
@@ -27,6 +27,7 @@
27
27
  "main": "./dist/index.js",
28
28
  "files": [
29
29
  "dist",
30
+ "!dist/**/*.map",
30
31
  "templates",
31
32
  "README.md",
32
33
  "CHANGELOG.md"
@@ -1 +0,0 @@
1
- {"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/cli-kit/format.ts"],"names":[],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"glyphs.js","sourceRoot":"","sources":["../../src/cli-kit/glyphs.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;IAClC,iCAAiC;IACjC,EAAE,EAAE,GAAG;IACP,oBAAoB;IACpB,GAAG,EAAE,GAAG;IACR,uBAAuB;IACvB,IAAI,EAAE,GAAG;IACT,qEAAqE;IACrE,KAAK,EAAE,GAAG;IACV,qBAAqB;IACrB,MAAM,EAAE,GAAG;IACX,+BAA+B;IAC/B,OAAO,EAAE,GAAG;CACb,CAAC,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli-kit/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE3D,OAAO,EACL,IAAI,EACJ,UAAU,EACV,WAAW,GACZ,MAAM,aAAa,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"palette.js","sourceRoot":"","sources":["../../src/cli-kit/palette.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,mDAAmD;AACnD,SAAS,MAAM,CAAC,CAAU;IACxB,OAAO,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;AAC/C,CAAC;AAED,uBAAuB;AACvB,MAAM,UAAU,EAAE,CAAC,CAAS;IAC1B,OAAO,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AAChC,CAAC;AAED,uBAAuB;AACvB,MAAM,UAAU,GAAG,CAAC,CAAS;IAC3B,OAAO,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AAC9B,CAAC;AAED,uBAAuB;AACvB,MAAM,UAAU,IAAI,CAAC,CAAS;IAC5B,OAAO,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AACjC,CAAC;AAED,oCAAoC;AACpC,MAAM,UAAU,GAAG,CAAC,CAAS;IAC3B,OAAO,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AAC9B,CAAC;AAED,iCAAiC;AACjC,MAAM,UAAU,OAAO,CAAC,CAAS;IAC/B,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AAC/B,CAAC"}