@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.
- package/CHANGELOG.md +91 -2
- package/README.md +2 -0
- package/dist/cli-kit/choices.d.ts +33 -0
- package/dist/cli-kit/choices.js +32 -0
- package/dist/cli-kit/command-context.d.ts +98 -0
- package/dist/cli-kit/command-context.js +108 -0
- package/dist/cli-kit/errors.d.ts +79 -0
- package/dist/cli-kit/errors.js +94 -0
- package/dist/cli-kit/index.d.ts +8 -0
- package/dist/cli-kit/index.js +8 -0
- package/dist/cli-kit/suggest.d.ts +74 -0
- package/dist/cli-kit/suggest.js +207 -0
- package/dist/commands/admin/oidc-login.js +1 -1
- package/dist/commands/admin/sdk-credential.js +1 -1
- package/dist/commands/autocomplete.d.ts +1 -1
- package/dist/commands/autocomplete.js +1 -0
- package/dist/commands/bug.d.ts +59 -0
- package/dist/commands/bug.js +114 -2
- package/dist/commands/build.d.ts +24 -1
- package/dist/commands/build.js +195 -73
- package/dist/commands/chat.d.ts +94 -1
- package/dist/commands/chat.js +258 -26
- package/dist/commands/customers.js +1 -1
- package/dist/commands/db-move.js +3 -3
- package/dist/commands/db.js +17 -24
- package/dist/commands/deploy.d.ts +91 -2
- package/dist/commands/deploy.js +414 -69
- package/dist/commands/deployments.js +11 -6
- package/dist/commands/dev-context.js +15 -5
- package/dist/commands/docs-produto.d.ts +25 -0
- package/dist/commands/docs-produto.js +70 -8
- package/dist/commands/doctor.js +36 -1
- package/dist/commands/domains.js +85 -4
- package/dist/commands/env.d.ts +9 -3
- package/dist/commands/env.js +23 -3
- package/dist/commands/infra-read.js +2 -2
- package/dist/commands/init.js +7 -1
- package/dist/commands/login.js +35 -8
- package/dist/commands/logs.js +12 -4
- package/dist/commands/mobile.js +33 -9
- package/dist/commands/new.d.ts +29 -1
- package/dist/commands/new.js +92 -4
- package/dist/commands/open.js +16 -6
- package/dist/commands/ops.js +1 -1
- package/dist/commands/products-db.d.ts +1 -0
- package/dist/commands/products-db.js +41 -8
- package/dist/commands/products.d.ts +17 -3
- package/dist/commands/products.js +84 -16
- package/dist/commands/promote.js +30 -5
- package/dist/commands/secrets.d.ts +91 -0
- package/dist/commands/secrets.js +471 -0
- package/dist/commands/servers.js +6 -4
- package/dist/commands/status.js +2 -2
- package/dist/commands/support.js +5 -3
- package/dist/commands/tenants.js +28 -16
- package/dist/commands/upgrade.d.ts +9 -0
- package/dist/commands/upgrade.js +50 -1
- package/dist/commands/vm-memory.js +1 -0
- package/dist/commands/webhooks.d.ts +2 -0
- package/dist/commands/webhooks.js +9 -3
- package/dist/commands/workspaces.js +42 -11
- package/dist/index.js +70 -14
- package/dist/lib/api-client.d.ts +14 -0
- package/dist/lib/api-client.js +59 -6
- package/dist/lib/api-error.d.ts +14 -0
- package/dist/lib/api-error.js +107 -46
- package/dist/lib/build-targets.d.ts +127 -0
- package/dist/lib/build-targets.js +348 -0
- package/dist/lib/cli-error.d.ts +13 -0
- package/dist/lib/cli-error.js +24 -0
- package/dist/lib/cli-write.d.ts +43 -9
- package/dist/lib/cli-write.js +154 -45
- package/dist/lib/config-schema.d.ts +8 -8
- package/dist/lib/db-local/db-json.d.ts +1 -1
- package/dist/lib/db-local/db-json.js +13 -5
- package/dist/lib/db-local/env.d.ts +15 -3
- package/dist/lib/db-local/env.js +37 -10
- package/dist/lib/db-pipeline/build-deps.d.ts +5 -1
- package/dist/lib/db-pipeline/build-deps.js +23 -3
- package/dist/lib/db-pipeline/types.d.ts +6 -2
- package/dist/lib/db-pipeline/types.js +0 -18
- package/dist/lib/deploy-target-contract.d.ts +152 -0
- package/dist/lib/deploy-target-contract.js +411 -0
- package/dist/lib/env-file-do-build.d.ts +19 -0
- package/dist/lib/env-file-do-build.js +44 -0
- package/dist/lib/envs.d.ts +108 -0
- package/dist/lib/envs.js +127 -0
- package/dist/lib/fs-seguro.d.ts +63 -0
- package/dist/lib/fs-seguro.js +108 -0
- package/dist/lib/mobile-local/mobile-json.js +10 -4
- package/dist/lib/pickers.d.ts +44 -7
- package/dist/lib/pickers.js +60 -4
- package/dist/lib/product-docs/sync.d.ts +6 -0
- package/dist/lib/product-docs/sync.js +10 -1
- package/dist/lib/public-secret-scan.d.ts +44 -3
- package/dist/lib/public-secret-scan.js +361 -36
- package/dist/lib/render.d.ts +16 -0
- package/dist/lib/render.js +34 -4
- package/dist/lib/segredo-produto.d.ts +124 -0
- package/dist/lib/segredo-produto.js +336 -0
- package/dist/lib/texto-utf8.d.ts +96 -0
- package/dist/lib/texto-utf8.js +196 -0
- package/package.json +2 -1
- package/dist/cli-kit/format.js.map +0 -1
- package/dist/cli-kit/glyphs.js.map +0 -1
- package/dist/cli-kit/index.js.map +0 -1
- package/dist/cli-kit/palette.js.map +0 -1
- package/dist/commands/add.js.map +0 -1
- package/dist/commands/admin/iam-check.js.map +0 -1
- package/dist/commands/admin/oidc-login.js.map +0 -1
- package/dist/commands/admin/sdk-credential.js.map +0 -1
- package/dist/commands/agent-release.js.map +0 -1
- package/dist/commands/agent-write.js.map +0 -1
- package/dist/commands/ai-models.js.map +0 -1
- package/dist/commands/ai.js.map +0 -1
- package/dist/commands/api-catalog.js.map +0 -1
- package/dist/commands/archive.js.map +0 -1
- package/dist/commands/artifact-registry.js.map +0 -1
- package/dist/commands/audit.js.map +0 -1
- package/dist/commands/auth.js.map +0 -1
- package/dist/commands/autocomplete.js.map +0 -1
- package/dist/commands/billing.js.map +0 -1
- package/dist/commands/bootstrap.js.map +0 -1
- package/dist/commands/bug.js.map +0 -1
- package/dist/commands/build.js.map +0 -1
- package/dist/commands/chat-menu.js.map +0 -1
- package/dist/commands/chat.js.map +0 -1
- package/dist/commands/cloud-run.js.map +0 -1
- package/dist/commands/code.js.map +0 -1
- package/dist/commands/config.js.map +0 -1
- package/dist/commands/customers.js.map +0 -1
- package/dist/commands/db-move.js.map +0 -1
- package/dist/commands/db.js.map +0 -1
- package/dist/commands/deploy.js.map +0 -1
- package/dist/commands/deployments.js.map +0 -1
- package/dist/commands/dev-context.js.map +0 -1
- package/dist/commands/dev-up.js.map +0 -1
- package/dist/commands/dev.js.map +0 -1
- package/dist/commands/docs-produto.js.map +0 -1
- package/dist/commands/docs.js.map +0 -1
- package/dist/commands/doctor.js.map +0 -1
- package/dist/commands/domains.js.map +0 -1
- package/dist/commands/dr.js.map +0 -1
- package/dist/commands/env.js.map +0 -1
- package/dist/commands/hosting-write.js.map +0 -1
- package/dist/commands/infra-read.js.map +0 -1
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/login.js.map +0 -1
- package/dist/commands/logout.js.map +0 -1
- package/dist/commands/logs.js.map +0 -1
- package/dist/commands/mobile.js.map +0 -1
- package/dist/commands/mocks.js.map +0 -1
- package/dist/commands/new.js.map +0 -1
- package/dist/commands/open.js.map +0 -1
- package/dist/commands/ops.js.map +0 -1
- package/dist/commands/products-db.js.map +0 -1
- package/dist/commands/products.js.map +0 -1
- package/dist/commands/promote.js.map +0 -1
- package/dist/commands/publish.js.map +0 -1
- package/dist/commands/schema.js.map +0 -1
- package/dist/commands/servers.js.map +0 -1
- package/dist/commands/smoke.js.map +0 -1
- package/dist/commands/status.js.map +0 -1
- package/dist/commands/support.js.map +0 -1
- package/dist/commands/surface-status.js.map +0 -1
- package/dist/commands/tenants.js.map +0 -1
- package/dist/commands/ui.js.map +0 -1
- package/dist/commands/upgrade.js.map +0 -1
- package/dist/commands/validate.js.map +0 -1
- package/dist/commands/vm-memory.js.map +0 -1
- package/dist/commands/vm.js.map +0 -1
- package/dist/commands/webhooks.js.map +0 -1
- package/dist/commands/whoami.js.map +0 -1
- package/dist/commands/workspaces.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/lib/agent-artifact-url.js.map +0 -1
- package/dist/lib/agent-commands.js.map +0 -1
- package/dist/lib/ai/auth-discovery.js.map +0 -1
- package/dist/lib/ai/context.js.map +0 -1
- package/dist/lib/ai/orchestrator.js.map +0 -1
- package/dist/lib/api-client.js.map +0 -1
- package/dist/lib/api-error.js.map +0 -1
- package/dist/lib/auth.js.map +0 -1
- package/dist/lib/cli-read.js.map +0 -1
- package/dist/lib/cli-write.js.map +0 -1
- package/dist/lib/code-agent-budget.js.map +0 -1
- package/dist/lib/code-agent-files.js.map +0 -1
- package/dist/lib/code-agent-gateway.js.map +0 -1
- package/dist/lib/code-agent-git-tools.js.map +0 -1
- package/dist/lib/code-agent-protocol.js.map +0 -1
- package/dist/lib/code-agent-session.js.map +0 -1
- package/dist/lib/code-agent-settings.js.map +0 -1
- package/dist/lib/code-agent-skills.js.map +0 -1
- package/dist/lib/code-agent-subagents.js.map +0 -1
- package/dist/lib/code-agent-tools.js.map +0 -1
- package/dist/lib/code-agent-tui.js.map +0 -1
- package/dist/lib/code-agent-types.js.map +0 -1
- package/dist/lib/code-agent-ui.js.map +0 -1
- package/dist/lib/code-agent-web-tools.js.map +0 -1
- package/dist/lib/code-agent-write-tools.js.map +0 -1
- package/dist/lib/compose-generator.js.map +0 -1
- package/dist/lib/config-schema.js.map +0 -1
- package/dist/lib/config.js.map +0 -1
- package/dist/lib/db-local/db-json.js.map +0 -1
- package/dist/lib/db-local/env.js.map +0 -1
- package/dist/lib/db-local/fingerprint.js.map +0 -1
- package/dist/lib/db-local/index.js.map +0 -1
- package/dist/lib/db-pipeline/build-deps.js.map +0 -1
- package/dist/lib/db-pipeline/errors.js.map +0 -1
- package/dist/lib/db-pipeline/index.js.map +0 -1
- package/dist/lib/db-pipeline/pipeline.js.map +0 -1
- package/dist/lib/db-pipeline/rehearse.js.map +0 -1
- package/dist/lib/db-pipeline/types.js.map +0 -1
- package/dist/lib/dev-local-context.js.map +0 -1
- package/dist/lib/iam-preflight.js.map +0 -1
- package/dist/lib/mobile-local/index.js.map +0 -1
- package/dist/lib/mobile-local/mobile-json.js.map +0 -1
- package/dist/lib/pickers.js.map +0 -1
- package/dist/lib/product-config.js.map +0 -1
- package/dist/lib/product-docs/check.js.map +0 -1
- package/dist/lib/product-docs/config.js.map +0 -1
- package/dist/lib/product-docs/dates.js.map +0 -1
- package/dist/lib/product-docs/frontmatter.js.map +0 -1
- package/dist/lib/product-docs/git.js.map +0 -1
- package/dist/lib/product-docs/markdown.js.map +0 -1
- package/dist/lib/product-docs/ops.js.map +0 -1
- package/dist/lib/product-docs/structure.js.map +0 -1
- package/dist/lib/product-docs/sync.js.map +0 -1
- package/dist/lib/product-docs/taxonomy.js.map +0 -1
- package/dist/lib/product-docs/templates.js.map +0 -1
- package/dist/lib/public-secret-scan.js.map +0 -1
- package/dist/lib/render.js.map +0 -1
- package/dist/lib/schema-cache.js.map +0 -1
- package/dist/lib/sse.js.map +0 -1
- package/dist/lib/telemetry.js.map +0 -1
- package/dist/lib/token-resolver.js.map +0 -1
- package/dist/lib/url-allowlist.js.map +0 -1
- package/dist/lib/version-check.js.map +0 -1
- package/dist/utils/logger.js.map +0 -1
- package/dist/utils/logo.js.map +0 -1
- package/dist/utils/safe-exit.js.map +0 -1
- package/dist/utils/spinner-registry.js.map +0 -1
- 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
|
-
|
|
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 (
|
|
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
|
-
/**
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
if (
|
|
141
|
-
|
|
142
|
-
|
|
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
|
|
159
|
-
const
|
|
160
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 [
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
}
|
package/dist/lib/render.d.ts
CHANGED
|
@@ -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
|
*/
|
package/dist/lib/render.js
CHANGED
|
@@ -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
|
-
|
|
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) =>
|
|
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[]>;
|