@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
package/dist/commands/deploy.js
CHANGED
|
@@ -27,6 +27,9 @@ import ora from 'ora';
|
|
|
27
27
|
import { log } from '../utils/logger.js';
|
|
28
28
|
import { apiRequest, CliNetworkError } from '../lib/api-client.js';
|
|
29
29
|
import { handleApiError } from '../lib/api-error.js';
|
|
30
|
+
import { exitWithCliError } from '../lib/cli-error.js';
|
|
31
|
+
import { invalidChoiceError } from '../cli-kit/choices.js';
|
|
32
|
+
import { DEPLOY_ENVIRONMENT_IDS, deployEnvParaRuntime, } from '../lib/envs.js';
|
|
30
33
|
import { resolveStepUp } from '../lib/cli-write.js';
|
|
31
34
|
import { runBuild } from './build.js';
|
|
32
35
|
import { formatDeployConsole, isDeployUnverified, deployStatusLabel } from './deployments.js';
|
|
@@ -35,7 +38,10 @@ import { readDbJson, schemaFingerprint } from '../lib/db-local/index.js';
|
|
|
35
38
|
import { assertIamReady } from '../lib/iam-preflight.js';
|
|
36
39
|
import { ALLOW_PUBLIC_SECRET_FLAG, MIN_OVERRIDE_REASON_LENGTH, buildOverrideRecord, collectPublicEnvSecrets, formatFindingsMessage, scanDirectoryForPublicSecrets, scanTarballForPublicSecrets, } from '../lib/public-secret-scan.js';
|
|
37
40
|
import { getActiveTokenSync } from '../lib/token-resolver.js';
|
|
41
|
+
import { BYTES_FAREJADOS, checkDeployTargetContract, farejarConteudoDePacote, mensagemSourceAcimaDoTeto, mensagemSourceNaoEhHandler, pareceArquivoDePacote, WORKSPACE_SOURCE_MAX_BYTES, } from '../lib/deploy-target-contract.js';
|
|
38
42
|
import { safeExit } from '../utils/safe-exit.js';
|
|
43
|
+
import { AVISO_AGENTE_PENDENTE, CHAVES_DO_CARREGADOR, CHAVE_SEGREDO_RE, chaveInjetadaPeloCore, comandoParaRegistrarSegredo, problemaNaChaveReservada, } from '../lib/segredo-produto.js';
|
|
44
|
+
import { avisoEnvDoBundlerForaDeUtf8, CodificacaoInvalida, decodificarUtf8, semBomInicial, } from '../lib/texto-utf8.js';
|
|
39
45
|
/**
|
|
40
46
|
* bug_a5786e03 (2026-05-31): o Core/agente só conhecem os stacks canônicos
|
|
41
47
|
* `node|docker|php-apache|static` (PRODUCT_STACKS). `nextjs` é um alias de
|
|
@@ -109,7 +115,16 @@ export function validateAllowPublicSecretReason(raw, json) {
|
|
|
109
115
|
*/
|
|
110
116
|
export async function runPublicSecretGate(source, ctx) {
|
|
111
117
|
const lg = scopedLog(ctx.json);
|
|
112
|
-
|
|
118
|
+
// Rodada final 2 do cli#81: `.env*` fora de UTF-8 limpo não para a varredura
|
|
119
|
+
// (ela confere o valor como o bundler o lê), mas não passa calado. Em --json
|
|
120
|
+
// o aviso vai ao stderr: o stdout segue com a linha JSON única.
|
|
121
|
+
const envSecrets = await collectPublicEnvSecrets(ctx.cwd, ctx.envFile, process.env, (arquivo, problema) => {
|
|
122
|
+
const aviso = avisoEnvDoBundlerForaDeUtf8(arquivo, problema);
|
|
123
|
+
if (ctx.json)
|
|
124
|
+
console.error(aviso);
|
|
125
|
+
else
|
|
126
|
+
lg.warn(aviso);
|
|
127
|
+
});
|
|
113
128
|
let result;
|
|
114
129
|
try {
|
|
115
130
|
result =
|
|
@@ -138,6 +153,53 @@ export async function runPublicSecretGate(source, ctx) {
|
|
|
138
153
|
'Trate a chave exposta como VAZADA: rotacione-a assim que possível.');
|
|
139
154
|
return { scanned: true, scannedFiles: result.scannedFiles, override };
|
|
140
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* bug_678ddc98 (elo que faltava): decide se este deploy precisa DIZER ao Core
|
|
158
|
+
* qual é o entrypoint, em vez de deixar o agente adivinhar.
|
|
159
|
+
*
|
|
160
|
+
* O #68 ensinou o `neetru build` a empacotar monorepo e a calcular o entrypoint
|
|
161
|
+
* aninhado (`apps/web/server.js`), mas a cadeia parava no `manifest.json`:
|
|
162
|
+
* `manifest.entrypoint` nunca foi lido por ninguém — era campo write-only.
|
|
163
|
+
* Num monorepo o tarball fica assim:
|
|
164
|
+
*
|
|
165
|
+
* <versionDir>/node_modules/… ← tracejado, na RAIZ
|
|
166
|
+
* <versionDir>/packages/ui/… ← pacotes internos do workspace
|
|
167
|
+
* <versionDir>/apps/web/server.js ← entrypoint REAL
|
|
168
|
+
* <versionDir>/apps/web/package.json ← o package.json DO APP mora aqui
|
|
169
|
+
*
|
|
170
|
+
* O agente (`neetru-agent-service-vm/src/lib/deploy-plan.ts:117-124`) infere o
|
|
171
|
+
* comando de start olhando SÓ a raiz do versionDir: `server.js` na raiz,
|
|
172
|
+
* `.next/` na raiz, `.next/standalone/server.js`, e `scripts.start` do
|
|
173
|
+
* `package.json` da raiz. Em monorepo os três primeiros não existem, e o
|
|
174
|
+
* quarto não ajuda: o Next.js escreve o package.json do app em
|
|
175
|
+
* `<appPrefix>/package.json` (`next/dist/build/utils.js:1197`), então o que
|
|
176
|
+
* houver na raiz é o package.json do WORKSPACE — que ou não tem `start`, ou
|
|
177
|
+
* tem um `start` de ferramenta de monorepo (`turbo start`), nunca
|
|
178
|
+
* `node apps/web/server.js`. Nos dois casos o agente erra: cai no fallback
|
|
179
|
+
* `'npm start'` (deploy-plan.ts:124) e o systemd entra em crash-loop.
|
|
180
|
+
*
|
|
181
|
+
* REGRA: só mandamos o entrypoint quando o agente NÃO consegue acertar sozinho,
|
|
182
|
+
* isto é quando o standalone está aninhado (`appPrefix` preenchido). Para app
|
|
183
|
+
* único o agente já resolve certo — e mandar `pkg.main`/`index.js` ali
|
|
184
|
+
* ATROPELARIA o `npm start` de produtos como o gestovendas, que dependem de
|
|
185
|
+
* `scripts.start`. Conserto que quebra o caso que funcionava não é conserto.
|
|
186
|
+
*
|
|
187
|
+
* Também não mandamos para stack não-node: ali `entrypoint` é `index.html` /
|
|
188
|
+
* `index.php` / referência de imagem, que não é "arquivo para o `node` rodar".
|
|
189
|
+
*
|
|
190
|
+
* ATENÇÃO — a cadeia ainda NÃO fecha só com isto: o Core descarta o campo hoje
|
|
191
|
+
* (`deployRequestSchema` não o declara, e objeto zod sem `.passthrough()` remove
|
|
192
|
+
* chave desconhecida em silêncio). Ver "o que falta no Core" na descrição do PR.
|
|
193
|
+
*/
|
|
194
|
+
export function entrypointParaDeploy(manifest) {
|
|
195
|
+
if (!manifest)
|
|
196
|
+
return undefined;
|
|
197
|
+
if (manifest.stack !== 'node')
|
|
198
|
+
return undefined;
|
|
199
|
+
if (!manifest.appPrefix)
|
|
200
|
+
return undefined;
|
|
201
|
+
return manifest.entrypoint ?? undefined;
|
|
202
|
+
}
|
|
141
203
|
/** `true` pra `prod`/`production` (o Core normaliza `production`→`prod`). */
|
|
142
204
|
export function isProdEnv(envName) {
|
|
143
205
|
const e = (envName ?? '').trim().toLowerCase();
|
|
@@ -146,7 +208,7 @@ export function isProdEnv(envName) {
|
|
|
146
208
|
/**
|
|
147
209
|
* M7 (auditoria cli/v1 2026-09-29): `POST /api/cli/v1/deploy` com
|
|
148
210
|
* `environmentId` prod exige step-up MFA no Core (mesmo gate de `promote`).
|
|
149
|
-
* Resolve os headers (
|
|
211
|
+
* Resolve os headers (--mfa-token → sessão 1h → prompt TOTP; fail-closed em
|
|
150
212
|
* --json/--non-interactive/sem TTY) só quando o alvo é prod. Chamado logo
|
|
151
213
|
* antes do POST — depois do build/upload — pra o TOTP não vencer no meio.
|
|
152
214
|
*/
|
|
@@ -160,26 +222,74 @@ export async function resolveDeployStepUpHeaders(opts, envName, nonInteractive)
|
|
|
160
222
|
});
|
|
161
223
|
return headers;
|
|
162
224
|
}
|
|
225
|
+
/**
|
|
226
|
+
* Mostra os `warnings[]` que o Core devolve no deploy.
|
|
227
|
+
*
|
|
228
|
+
* Medido em 2026-10-12: o Core devolve `warnings` nos TRES alvos e o comentario
|
|
229
|
+
* dele diz, literalmente, "`warnings[]` na resposta JSON (o CLI exibe pro
|
|
230
|
+
* operador)" (`cli/v1/deploy/route.ts:309`). `grep warnings` em `deploy.ts`
|
|
231
|
+
* devolvia ZERO — a promessa nunca foi cumprida.
|
|
232
|
+
*
|
|
233
|
+
* O caso que mais doia: a CLI manda o ambiente default e o Core normaliza, e o
|
|
234
|
+
* aviso de que `environmentId` foi ASSUMIDO disparava em todo deploy sem
|
|
235
|
+
* `--env` — e sumia 100% das vezes. O operador achava que tinha escolhido.
|
|
236
|
+
*
|
|
237
|
+
* Vai pro STDERR de proposito: em `--json` o stdout e contrato de maquina (uma
|
|
238
|
+
* linha JSON) e nao pode ser poluido. Quem le JSON ja recebe `warnings` no
|
|
239
|
+
* proprio corpo.
|
|
240
|
+
*/
|
|
241
|
+
export function mostrarAvisosDoCore(warnings, json) {
|
|
242
|
+
if (!Array.isArray(warnings))
|
|
243
|
+
return;
|
|
244
|
+
const linhas = warnings.filter((w) => typeof w === 'string' && w.trim() !== '');
|
|
245
|
+
if (linhas.length === 0)
|
|
246
|
+
return;
|
|
247
|
+
if (json) {
|
|
248
|
+
for (const w of linhas)
|
|
249
|
+
process.stderr.write(`[aviso do Core] ${w}\n`);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
for (const w of linhas)
|
|
253
|
+
log.warn(` aviso do Core: ${w}`);
|
|
254
|
+
}
|
|
163
255
|
const TERMINAL_STATUSES = new Set(['success', 'failed', 'cancelled']);
|
|
164
|
-
/**
|
|
165
|
-
|
|
256
|
+
/**
|
|
257
|
+
* Estágios de ambiente válidos para o deploy workspace/cloud-run. P105: era
|
|
258
|
+
* `new Set(['dev','staging','prod'])` escrito à mão; agora sai da tabela
|
|
259
|
+
* `VOCABULARIOS_DE_AMBIENTE` (`lib/envs.ts`), derivada de
|
|
260
|
+
* `TENANT_ENVIRONMENT_IDS`.
|
|
261
|
+
*/
|
|
262
|
+
const WORKSPACE_ENVS = new Set(DEPLOY_ENVIRONMENT_IDS);
|
|
263
|
+
/**
|
|
264
|
+
* `--env` do deploy workspace/cloud-run: fora do vocabulário, encerra com exit
|
|
265
|
+
* 1 e os valores válidos da MESMA lista que validou (`invalid_env`, com `flag`,
|
|
266
|
+
* `value` e `valid` no JSON). Exportada para teste.
|
|
267
|
+
*/
|
|
268
|
+
export function validarAmbienteDoDeploy(envName, json) {
|
|
269
|
+
if (WORKSPACE_ENVS.has(envName))
|
|
270
|
+
return;
|
|
271
|
+
exitWithCliError(invalidChoiceError({
|
|
272
|
+
flag: '--env',
|
|
273
|
+
value: envName,
|
|
274
|
+
valid: DEPLOY_ENVIRONMENT_IDS,
|
|
275
|
+
subject: 'Ambiente',
|
|
276
|
+
code: 'invalid_env',
|
|
277
|
+
}), json);
|
|
278
|
+
}
|
|
166
279
|
/**
|
|
167
280
|
* Mapeia o `envName` do deploy ('dev' | 'staging' | 'prod' — vocabulário do
|
|
168
281
|
* `--env` deste comando) pro `targetEnv` que `neetru build` entende
|
|
169
282
|
* ('dev' | 'workspace' | 'prod' — vocabulário de `BuildOptions`/SDK).
|
|
170
283
|
* 'staging' vira 'workspace' (mesmo conceito de ambiente não-dev/não-prod).
|
|
171
284
|
* Valor desconhecido cai em 'prod' (mesmo default conservador de `runBuild`).
|
|
285
|
+
* P105: a correspondência sai de `VOCABULARIOS_DE_AMBIENTE`, não de `if`s.
|
|
172
286
|
*
|
|
173
287
|
* Fix (2026-07-22, achado da revisão adversarial): sem isso, todo build
|
|
174
288
|
* implícito de `neetru deploy --env dev`/`--env staging` (quando não há
|
|
175
289
|
* artifact em cache) forçava NEXT_PUBLIC_NEETRU_ENV=prod incondicionalmente.
|
|
176
290
|
*/
|
|
177
291
|
export function deployEnvToBuildTargetEnv(envName) {
|
|
178
|
-
|
|
179
|
-
return 'dev';
|
|
180
|
-
if (envName === 'staging')
|
|
181
|
-
return 'workspace';
|
|
182
|
-
return 'prod';
|
|
292
|
+
return deployEnvParaRuntime(envName) ?? 'prod';
|
|
183
293
|
}
|
|
184
294
|
/** Candidatos default de arquivo de source para um deploy workspace. */
|
|
185
295
|
const WORKSPACE_SOURCE_CANDIDATES = [
|
|
@@ -220,6 +330,16 @@ async function loadLocalConfig(cwd) {
|
|
|
220
330
|
}
|
|
221
331
|
return null;
|
|
222
332
|
}
|
|
333
|
+
/**
|
|
334
|
+
* `runtime` do neetru.config.json do diretório atual, só quando o config é
|
|
335
|
+
* DESTE produto. `--product outro` com o config do diretório atual: o `runtime`
|
|
336
|
+
* lido é de OUTRO produto e não diz nada sobre este deploy (revisão do PR #74).
|
|
337
|
+
* Sem `--product`, ou config sem `slug`, conta como deste produto.
|
|
338
|
+
*/
|
|
339
|
+
function runtimeDoConfigDesteProduto(cfg, product) {
|
|
340
|
+
const configEhDesteProduto = !product || !cfg?.slug || cfg.slug === product;
|
|
341
|
+
return configEhDesteProduto ? cfg?.runtime : undefined;
|
|
342
|
+
}
|
|
223
343
|
/**
|
|
224
344
|
* Nomes de variáveis que NUNCA devem ser injetados no deploy —
|
|
225
345
|
* podem alterar o comportamento do runtime de formas perigosas.
|
|
@@ -293,10 +413,82 @@ const SDK_CRED_ENV_KEYS = new Set([
|
|
|
293
413
|
'NEETRU_API_KEY',
|
|
294
414
|
'NEETRU_CLIENT_ID',
|
|
295
415
|
]);
|
|
296
|
-
|
|
416
|
+
/**
|
|
417
|
+
* Aviso do segredo descartado no `--env-file` de um deploy VM.
|
|
418
|
+
*
|
|
419
|
+
* bug_8b85353b (Parte 3): até aqui a mensagem mandava "usar o Secret Manager e
|
|
420
|
+
* referenciar por nome na VM" — mecanismo que NÃO existia para segredo do
|
|
421
|
+
* próprio produto (era a 4ª cópia do padrão que o core#632 corrigiu nas outras
|
|
422
|
+
* três). O caminho real é `neetru secrets set`, que grava no registro
|
|
423
|
+
* `product_secrets` do Core com o valor direto no Secret Manager. `env` é o
|
|
424
|
+
* `--env` do deploy, para o comando sugerido já sair com o ambiente certo.
|
|
425
|
+
*
|
|
426
|
+
* Revisão do #81 (M1): o comando só é sugerido quando o Core o aceitaria.
|
|
427
|
+
* Chave reservada (o Core já a entrega ao app: credencial SDK/OIDC/CLI,
|
|
428
|
+
* módulo, unit) é recusada em `assertValidProductSecretKey`, e nome fora de
|
|
429
|
+
* CAIXA_ALTA é recusado pela própria CLI (`problemaNaChave`) — sugerir
|
|
430
|
+
* `neetru secrets set` nesses casos mandava o dev para um caminho que falha.
|
|
431
|
+
*/
|
|
432
|
+
export function avisoSegredoDescartadoNaVm(chave, env) {
|
|
433
|
+
const inicio = `Variável com aparência de segredo ignorada (${chave}): `;
|
|
434
|
+
if (CHAVES_DO_CARREGADOR.includes(chave)) {
|
|
435
|
+
return inicio + 'ela muda como o processo carrega código e não pode vir de um segredo do produto.';
|
|
436
|
+
}
|
|
437
|
+
if (chaveInjetadaPeloCore(chave)) {
|
|
438
|
+
return (inicio +
|
|
439
|
+
'o Core já a entrega ao app na VM por outro canal (credencial SDK, OIDC ou CLI, módulo ' +
|
|
440
|
+
'ativo, ambiente ou unit do app). Não precisa passá-la no --env-file nem registrá-la como ' +
|
|
441
|
+
'segredo do produto.');
|
|
442
|
+
}
|
|
443
|
+
if (!CHAVE_SEGREDO_RE.test(chave)) {
|
|
444
|
+
return (inicio +
|
|
445
|
+
'segredo não vai no deploy por variável de ambiente (ficaria gravado no Firestore, que ' +
|
|
446
|
+
'toda a equipe lê), e o registro de segredos do produto só aceita nome em CAIXA_ALTA ' +
|
|
447
|
+
'(letras, dígitos e underscore, começando por letra, de 2 a 64 caracteres). Renomeie a ' +
|
|
448
|
+
`variável no app e registre-a com \`${comandoParaRegistrarSegredo('<NOME>', env)}\`.`);
|
|
449
|
+
}
|
|
450
|
+
return (inicio +
|
|
451
|
+
'segredo não vai no deploy por variável de ambiente (ficaria gravado no Firestore, que toda ' +
|
|
452
|
+
`a equipe lê). Registre-o com \`${comandoParaRegistrarSegredo(chave, env)}\` — o valor vai ` +
|
|
453
|
+
'direto para o Secret Manager; use --generate quando o Core puder sortear o valor.');
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* O aviso desta chave manda para `neetru secrets set`? Não manda quando o
|
|
457
|
+
* Core reserva a chave — aí o aviso de que o agente ainda não entrega
|
|
458
|
+
* segredos do produto também não cabe.
|
|
459
|
+
*/
|
|
460
|
+
export function segredoDescartadoApontaSecretsSet(chave) {
|
|
461
|
+
return problemaNaChaveReservada(chave) === null;
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Lê o `--env-file` do deploy e devolve o texto. Revisão do cli#81 (N-r3-1):
|
|
465
|
+
* `readFile(…, 'utf8')` trocava cada byte que não é UTF-8 por U+FFFD sem aviso
|
|
466
|
+
* — um .env ANSI com `NOME_LOJA=Café` ia ao deploy como `Caf\uFFFD`. Agora o
|
|
467
|
+
* arquivo é lido em bytes e recusado com `invalid_encoding`, pela mesma regra
|
|
468
|
+
* do `neetru secrets set --from-file`. `runDeploy` chama esta função antes do
|
|
469
|
+
* catálogo, de qualquer pergunta e de qualquer chamada de rede (rodada final 3).
|
|
470
|
+
*/
|
|
471
|
+
async function lerEnvFileDoDeploy(envFile, json) {
|
|
472
|
+
const bruto = await fsp.readFile(envFile);
|
|
473
|
+
try {
|
|
474
|
+
return semBomInicial(decodificarUtf8(bruto, 'env-file'));
|
|
475
|
+
}
|
|
476
|
+
catch (err) {
|
|
477
|
+
if (err instanceof CodificacaoInvalida)
|
|
478
|
+
deployFail(json, 'invalid_encoding', err.message);
|
|
479
|
+
throw err;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Monta as variáveis do `--env-file` que `lerEnvFileDoDeploy` já leu. Fica
|
|
484
|
+
* para depois do `pickTarget` porque os avisos dependem do alvo.
|
|
485
|
+
*/
|
|
486
|
+
function envVarsDoEnvFile(content, target = 'cloud-run', json,
|
|
487
|
+
/** `--env` do deploy (só para o aviso de segredo descartado na VM). */
|
|
488
|
+
env) {
|
|
297
489
|
const lg = scopedLog(json);
|
|
298
|
-
const content = await fsp.readFile(envFile, 'utf8');
|
|
299
490
|
const out = {};
|
|
491
|
+
let descartouSegredoNaVm = false;
|
|
300
492
|
if (target === 'vm') {
|
|
301
493
|
// (3) Lembrete único: a credencial SDK não precisa ir no --env-file.
|
|
302
494
|
lg.info('A credencial SDK é injetada automaticamente na VM pelo Core/agente — ' +
|
|
@@ -342,13 +534,23 @@ async function loadEnvFile(envFile, target = 'cloud-run', json) {
|
|
|
342
534
|
}
|
|
343
535
|
// (1) Segredos são dropados aqui (o Core rejeitaria com HTTP 400).
|
|
344
536
|
if (isSecretEnvVarKey(k)) {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
537
|
+
if (target === 'vm') {
|
|
538
|
+
if (segredoDescartadoApontaSecretsSet(k))
|
|
539
|
+
descartouSegredoNaVm = true;
|
|
540
|
+
lg.warn(avisoSegredoDescartadoNaVm(k, env));
|
|
541
|
+
}
|
|
542
|
+
else {
|
|
543
|
+
lg.warn(`Variável com aparência de segredo ignorada (${k}): segredos não podem ` +
|
|
544
|
+
'ir no deploy via env (vazariam no Firestore, legível por todo o staff). ' +
|
|
545
|
+
'Use o Secret Manager e referencie por nome na VM.');
|
|
546
|
+
}
|
|
348
547
|
continue;
|
|
349
548
|
}
|
|
350
549
|
out[k] = v;
|
|
351
550
|
}
|
|
551
|
+
// Uma vez só, depois da lista: o registro ainda não chega ao app na VM.
|
|
552
|
+
if (descartouSegredoNaVm)
|
|
553
|
+
lg.warn(AVISO_AGENTE_PENDENTE);
|
|
352
554
|
return out;
|
|
353
555
|
}
|
|
354
556
|
// handleApiError unificado em ../lib/api-error.js (auditoria qualidade 2026-06-20).
|
|
@@ -387,7 +589,7 @@ async function pickProduct(opts, nonInteractive, cwd) {
|
|
|
387
589
|
]);
|
|
388
590
|
return slug;
|
|
389
591
|
}
|
|
390
|
-
async function pickTarget(opts, nonInteractive) {
|
|
592
|
+
export async function pickTarget(opts, nonInteractive, runtime) {
|
|
391
593
|
if (opts.target)
|
|
392
594
|
return opts.target;
|
|
393
595
|
// Não-interativo (CI/scripts) mantém `vm` como default — mudar pra workspace
|
|
@@ -395,17 +597,27 @@ async function pickTarget(opts, nonInteractive) {
|
|
|
395
597
|
// interativo o menu já oferece workspace como recomendado/primeiro.
|
|
396
598
|
if (nonInteractive)
|
|
397
599
|
return 'vm';
|
|
600
|
+
// Produto `runtime: nextjs` (o padrão do `neetru init`): o workspace roda UM
|
|
601
|
+
// handler .ts, não um app Next.js. Recomendar "Workspace" aqui levava quem
|
|
602
|
+
// aceita o padrão direto ao aviso do contrato (confirmação final do PR #74).
|
|
603
|
+
const ehNext = runtime === 'nextjs';
|
|
398
604
|
const { target } = await inquirer.prompt([
|
|
399
605
|
{
|
|
400
606
|
type: 'list',
|
|
401
607
|
name: 'target',
|
|
402
608
|
message: 'Onde deployar?',
|
|
403
|
-
default: 'workspace',
|
|
404
|
-
choices:
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
609
|
+
default: ehNext ? 'vm' : 'workspace',
|
|
610
|
+
choices: ehNext
|
|
611
|
+
? [
|
|
612
|
+
{ name: 'VM existente — serve o app Next.js (recomendado)', value: 'vm' },
|
|
613
|
+
{ name: 'Cloud Run — serviço dedicado, escala a zero (imagem)', value: 'cloud-run' },
|
|
614
|
+
{ name: 'Workspace — só UM handler .ts, não o app Next.js', value: 'workspace' },
|
|
615
|
+
]
|
|
616
|
+
: [
|
|
617
|
+
{ name: 'Workspace — runtime compartilhado, mais barato (recomendado)', value: 'workspace' },
|
|
618
|
+
{ name: 'Cloud Run — serviço dedicado, escala a zero', value: 'cloud-run' },
|
|
619
|
+
{ name: 'VM existente — custo fixo, sem cold start', value: 'vm' },
|
|
620
|
+
],
|
|
409
621
|
},
|
|
410
622
|
]);
|
|
411
623
|
return target;
|
|
@@ -962,16 +1174,32 @@ async function probeDeployedHealth(domain) {
|
|
|
962
1174
|
* Resolve o arquivo de source `.ts` de um deploy workspace. Ordem de busca:
|
|
963
1175
|
* `--artifact <arquivo>` → campo `source` do neetru.config.json → candidatos
|
|
964
1176
|
* default (`handler.ts`, `src/handler.ts`, `index.ts`, `src/index.ts`).
|
|
1177
|
+
*
|
|
1178
|
+
* Devolve também os primeiros BYTES CRUS (`inicio`) e de onde o arquivo veio
|
|
1179
|
+
* (`origem`): o farejador de pacote precisa dos bytes antes da decodificação
|
|
1180
|
+
* UTF-8 (depois dela o gzip já virou U+FFFD) e a mensagem de recusa precisa
|
|
1181
|
+
* dizer qual porta apontou o arquivo.
|
|
965
1182
|
*/
|
|
966
1183
|
async function resolveWorkspaceSource(opts, cwd) {
|
|
967
|
-
const cfg =
|
|
968
|
-
const candidates = [
|
|
969
|
-
|
|
970
|
-
|
|
1184
|
+
const cfg = await loadLocalConfig(cwd);
|
|
1185
|
+
const candidates = [
|
|
1186
|
+
{ caminho: opts.artifact, origem: 'artifact' },
|
|
1187
|
+
{ caminho: cfg?.source, origem: 'config' },
|
|
1188
|
+
...WORKSPACE_SOURCE_CANDIDATES.map((c) => ({ caminho: c, origem: 'padrao' })),
|
|
1189
|
+
];
|
|
1190
|
+
for (const { caminho, origem } of candidates) {
|
|
1191
|
+
if (typeof caminho !== 'string' || caminho.length === 0)
|
|
1192
|
+
continue;
|
|
1193
|
+
const abs = path.resolve(cwd, caminho);
|
|
971
1194
|
if (fileExists(abs)) {
|
|
1195
|
+
// Uma leitura só: `toString('utf8')` é exatamente o que
|
|
1196
|
+
// `readFile(abs, 'utf8')` fazia.
|
|
1197
|
+
const bruto = await fsp.readFile(abs);
|
|
972
1198
|
return {
|
|
973
|
-
relPath: path.relative(cwd, abs) ||
|
|
974
|
-
content:
|
|
1199
|
+
relPath: path.relative(cwd, abs) || caminho,
|
|
1200
|
+
content: bruto.toString('utf8'),
|
|
1201
|
+
inicio: bruto.subarray(0, BYTES_FAREJADOS),
|
|
1202
|
+
origem,
|
|
975
1203
|
};
|
|
976
1204
|
}
|
|
977
1205
|
}
|
|
@@ -995,11 +1223,38 @@ const WORKSPACE_SOURCE_GCS_THRESHOLD_BYTES = 1.5 * 1024 * 1024;
|
|
|
995
1223
|
* inline. O Core aceita os dois (campo `sourceUrl` no schema).
|
|
996
1224
|
*/
|
|
997
1225
|
async function runWorkspaceDeploy(opts, productSlug, envName, cwd, nonInteractive) {
|
|
998
|
-
|
|
999
|
-
deployFail(opts.json, 'invalid_env', `Ambiente inválido: "${envName}". Use dev, staging ou prod.`);
|
|
1000
|
-
}
|
|
1226
|
+
validarAmbienteDoDeploy(envName, opts.json);
|
|
1001
1227
|
const source = await resolveWorkspaceSource(opts, cwd);
|
|
1002
1228
|
const sourceSizeBytes = Buffer.byteLength(source.content, 'utf8');
|
|
1229
|
+
// bug_a8375858 — PACOTE no workspace, pela porta que o gate não vê.
|
|
1230
|
+
//
|
|
1231
|
+
// O gate de contrato (`deploy-target-contract.ts`) só enxerga a extensão de
|
|
1232
|
+
// `--artifact`. Um pacote apontado pelo `source` do neetru.config.json, ou
|
|
1233
|
+
// com extensão que a lista não prevê (`.bin`, nome sem extensão…), chegava
|
|
1234
|
+
// até aqui e só batia no teto abaixo. Lido como UTF-8 o binário incha ~1,75x,
|
|
1235
|
+
// então um pacote abaixo de ~1,15 MiB passava no teto, subia e voltava do
|
|
1236
|
+
// Core como erro de compilação do esbuild, que não diz a causa (revisão do
|
|
1237
|
+
// PR #74). Recusa aqui, ANTES de qualquer signed URL ou POST, pelo nome e
|
|
1238
|
+
// pelos bytes crus — e vem ANTES do teto, pra que um pacote grande ouça
|
|
1239
|
+
// "é pacote" e não "é grande".
|
|
1240
|
+
const motivoDePacote = pareceArquivoDePacote(source.relPath)
|
|
1241
|
+
? 'a extensão é de pacote de aplicação'
|
|
1242
|
+
: farejarConteudoDePacote(source.inicio);
|
|
1243
|
+
if (motivoDePacote) {
|
|
1244
|
+
deployFail(opts.json, 'artifact_nao_eh_handler_workspace', mensagemSourceNaoEhHandler(source.relPath, motivoDePacote, source.origem));
|
|
1245
|
+
}
|
|
1246
|
+
// bug_a8375858 — teto de source, depois da leitura: na prática, o handler
|
|
1247
|
+
// que simplesmente cresceu demais.
|
|
1248
|
+
//
|
|
1249
|
+
// O que faltava: a CLI conhecia só o limiar de 1.5 MiB do bug_eae4bef6, que
|
|
1250
|
+
// decide *inline vs GCS*, e NUNCA o teto absoluto do Core
|
|
1251
|
+
// (`WORKSPACE_SOURCE_MAX_BYTES`, 2 MiB, aplicado nos dois caminhos). Então
|
|
1252
|
+
// tudo acima de 2 MiB subia pro GCS pra só depois voltar 413 "Falha ao
|
|
1253
|
+
// resolver a source do deploy" — mensagem que aponta a dimensão errada.
|
|
1254
|
+
if (sourceSizeBytes > WORKSPACE_SOURCE_MAX_BYTES) {
|
|
1255
|
+
const cfgTeto = await loadLocalConfig(cwd);
|
|
1256
|
+
deployFail(opts.json, 'source_acima_do_teto_workspace', mensagemSourceAcimaDoTeto(source.relPath, sourceSizeBytes, cfgTeto?.runtime));
|
|
1257
|
+
}
|
|
1003
1258
|
if (!nonInteractive) {
|
|
1004
1259
|
log.heading('Resumo');
|
|
1005
1260
|
log.dim(` produto: ${productSlug}`);
|
|
@@ -1100,6 +1355,7 @@ async function runWorkspaceDeploy(opts, productSlug, envName, cwd, nonInteractiv
|
|
|
1100
1355
|
headers: mfaHeaders,
|
|
1101
1356
|
body: deployBody,
|
|
1102
1357
|
});
|
|
1358
|
+
mostrarAvisosDoCore(resp.warnings, opts.json);
|
|
1103
1359
|
}
|
|
1104
1360
|
catch (err) {
|
|
1105
1361
|
spinner.fail('Falha ao publicar.');
|
|
@@ -1436,28 +1692,20 @@ async function resolveCloudRunImage(opts, productSlug, envName, cwd) {
|
|
|
1436
1692
|
* Deploy `target=cloud-run` — cria/atualiza o serviço Cloud Run dedicado do
|
|
1437
1693
|
* ambiente. Síncrono: o POST retorna com o desfecho (`live`/`failed`/...).
|
|
1438
1694
|
*/
|
|
1439
|
-
async function runCloudRunDeploy(opts, productSlug, envName, cwd, nonInteractive
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
//
|
|
1444
|
-
//
|
|
1445
|
-
//
|
|
1446
|
-
//
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
' Exemplo: --image=us-central1-docker.pkg.dev/neetru/gestovendas-staging/app:1.0.0\n' +
|
|
1454
|
-
'\n' +
|
|
1455
|
-
' 2. --registry <host|host/project/repo> — CLI usa Cloud Build remoto automaticamente.\n' +
|
|
1456
|
-
' Não exige Docker instalado. Requer gcloud autenticado.\n' +
|
|
1457
|
-
' Exemplo: --registry=us-central1-docker.pkg.dev/neetru/gestovendas-staging\n' +
|
|
1458
|
-
'\n' +
|
|
1459
|
-
' 3. Mude o target pra vm ou workspace se quiser usar o tarball:\n' +
|
|
1460
|
-
' --target=vm --artifact=' + (opts.artifact ?? opts.artifactUrl ?? '<file>'));
|
|
1695
|
+
async function runCloudRunDeploy(opts, productSlug, envName, cwd, nonInteractive,
|
|
1696
|
+
/** O `--env-file` já lido e conferido por `runDeploy` (`lerEnvFileDoDeploy`). */
|
|
1697
|
+
envFileTexto) {
|
|
1698
|
+
validarAmbienteDoDeploy(envName, opts.json);
|
|
1699
|
+
// bug_ec17af08 (cloud-run não aceita tarball) agora mora na tabela do
|
|
1700
|
+
// `deploy-target-contract.ts`, avaliada em `runDeploy` logo após o
|
|
1701
|
+
// `pickTarget` — ANTES de qualquer build/upload, pra TODOS os alvos. O
|
|
1702
|
+
// código de erro e o texto são os mesmos; só deixou de ser um `if` solto.
|
|
1703
|
+
// Rodadas finais 2 e 3 do cli#81: o `--env-file` era lido depois de
|
|
1704
|
+
// `resolveCloudRunImage`, que manda a imagem ao Cloud Build. Agora
|
|
1705
|
+
// `runDeploy` o lê e confere antes de tudo; aqui só se montam as variáveis.
|
|
1706
|
+
let envVars;
|
|
1707
|
+
if (envFileTexto !== undefined) {
|
|
1708
|
+
envVars = envVarsDoEnvFile(envFileTexto, 'cloud-run', opts.json);
|
|
1461
1709
|
}
|
|
1462
1710
|
// bug_863ae5: pre-flight IAM check — abort cedo se SA Core não tiver permissão.
|
|
1463
1711
|
// MASTER-HIGH-048 (2.11.0): prefere auth.json sobre Conf legado.
|
|
@@ -1476,10 +1724,6 @@ async function runCloudRunDeploy(opts, productSlug, envName, cwd, nonInteractive
|
|
|
1476
1724
|
publicSecretScan = await runPublicSecretGate({ kind: 'dir', root: cwd }, { cwd, envFile: opts.envFile, stack: opts.stack, allowReason: opts.allowPublicSecret, json: opts.json });
|
|
1477
1725
|
}
|
|
1478
1726
|
const imageUrl = await resolveCloudRunImage(opts, productSlug, envName, cwd);
|
|
1479
|
-
let envVars;
|
|
1480
|
-
if (opts.envFile) {
|
|
1481
|
-
envVars = await loadEnvFile(path.resolve(cwd, opts.envFile), 'cloud-run', opts.json);
|
|
1482
|
-
}
|
|
1483
1727
|
if (!nonInteractive) {
|
|
1484
1728
|
log.heading('Resumo');
|
|
1485
1729
|
log.dim(` produto: ${productSlug}`);
|
|
@@ -1512,6 +1756,7 @@ async function runCloudRunDeploy(opts, productSlug, envName, cwd, nonInteractive
|
|
|
1512
1756
|
...(publicSecretScan.override ? { publicSecretOverride: publicSecretScan.override } : {}),
|
|
1513
1757
|
},
|
|
1514
1758
|
});
|
|
1759
|
+
mostrarAvisosDoCore(resp.warnings, opts.json);
|
|
1515
1760
|
}
|
|
1516
1761
|
catch (err) {
|
|
1517
1762
|
spinner.fail('Falha ao publicar.');
|
|
@@ -1679,6 +1924,17 @@ export async function runDeploy(opts) {
|
|
|
1679
1924
|
const nonInteractive = ensureNonInteractive(opts);
|
|
1680
1925
|
// 2.30.0: motivo do override validado ANTES de build/upload.
|
|
1681
1926
|
opts = { ...opts, allowPublicSecret: validateAllowPublicSecretReason(opts.allowPublicSecret, opts.json) };
|
|
1927
|
+
// Rodadas finais 2 e 3 do cli#81: o `--env-file` é lido e conferido AQUI,
|
|
1928
|
+
// antes do catálogo e da pergunta do `pickProduct`, da pergunta do
|
|
1929
|
+
// `checkSchemaGate` e do `pickTarget`, da lista de servidores, do build e
|
|
1930
|
+
// do upload. Um arquivo fora de UTF-8 sai como `invalid_encoding` antes de
|
|
1931
|
+
// tudo isso. As variáveis só são montadas depois do `pickTarget`, porque os
|
|
1932
|
+
// avisos dependem do alvo. Com `--target workspace` o arquivo não é lido: o
|
|
1933
|
+
// workspace o descarta, com o aviso do contrato de alvo. Sem `--target`, o
|
|
1934
|
+
// menu ainda pode escolher o workspace, e então o arquivo foi conferido à toa.
|
|
1935
|
+
const envFileTexto = opts.envFile && opts.target !== 'workspace'
|
|
1936
|
+
? await lerEnvFileDoDeploy(path.resolve(cwd, opts.envFile), opts.json)
|
|
1937
|
+
: undefined;
|
|
1682
1938
|
const productSlug = await pickProduct(opts, nonInteractive, cwd);
|
|
1683
1939
|
// GAP-M1-5 — gate de schema: detecta drift antes de prosseguir.
|
|
1684
1940
|
const canDeploy = await checkSchemaGate(cwd, opts);
|
|
@@ -1697,7 +1953,62 @@ export async function runDeploy(opts) {
|
|
|
1697
1953
|
docsCheck = undefined;
|
|
1698
1954
|
}
|
|
1699
1955
|
}
|
|
1700
|
-
|
|
1956
|
+
// O `runtime` do config serve a duas coisas aqui: recomendar o alvo certo no
|
|
1957
|
+
// menu (que só abre em modo interativo e sem `--target`) e o gate de contrato
|
|
1958
|
+
// logo abaixo. Uma leitura só, e só vale se o config é DESTE produto.
|
|
1959
|
+
const runtimeDoConfig = runtimeDoConfigDesteProduto(await loadLocalConfig(cwd), opts.product);
|
|
1960
|
+
const target = await pickTarget(opts, nonInteractive, runtimeDoConfig);
|
|
1961
|
+
// bug_a8375858 — GATE DE CONTRATO `target × runtime × flags`.
|
|
1962
|
+
//
|
|
1963
|
+
// Roda aqui, entre a escolha do alvo e QUALQUER trabalho caro (build,
|
|
1964
|
+
// empacotamento, signed URL, upload pro GCS, POST no Core). Era exatamente
|
|
1965
|
+
// esse o custo do bug: `--target workspace` com tarball de 20 MB subia
|
|
1966
|
+
// ~35 MB pro GCS e só então voltava um 413 que falava de tamanho quando o
|
|
1967
|
+
// problema era o TIPO do alvo.
|
|
1968
|
+
//
|
|
1969
|
+
// O `runtime` já vinha no neetru.config.json e já era lido aqui (o
|
|
1970
|
+
// `loadLocalConfig` devolve o JSON cru) — só não era olhado. Ausente =
|
|
1971
|
+
// nenhuma opinião; o gate não inventa bloqueio pra repo antigo.
|
|
1972
|
+
{
|
|
1973
|
+
const veredito = checkDeployTargetContract({
|
|
1974
|
+
target,
|
|
1975
|
+
runtime: runtimeDoConfig,
|
|
1976
|
+
flags: {
|
|
1977
|
+
artifact: opts.artifact,
|
|
1978
|
+
artifactUrl: opts.artifactUrl,
|
|
1979
|
+
artifactSha256: opts.artifactSha256,
|
|
1980
|
+
image: opts.image,
|
|
1981
|
+
registry: opts.registry,
|
|
1982
|
+
registryRepo: opts.registryRepo,
|
|
1983
|
+
buildLocal: opts.buildLocal,
|
|
1984
|
+
server: opts.server,
|
|
1985
|
+
port: opts.port,
|
|
1986
|
+
stack: opts.stack,
|
|
1987
|
+
envFile: opts.envFile,
|
|
1988
|
+
domain: opts.domain,
|
|
1989
|
+
localArtifact: opts.localArtifact,
|
|
1990
|
+
},
|
|
1991
|
+
});
|
|
1992
|
+
for (const aviso of veredito.warnings) {
|
|
1993
|
+
// Em `--json` o stdout é contrato de 1 linha — aviso vai pro stderr
|
|
1994
|
+
// (bug_6d92ef71: calar por completo em modo máquina é pior).
|
|
1995
|
+
if (opts.json)
|
|
1996
|
+
console.error(aviso);
|
|
1997
|
+
else
|
|
1998
|
+
log.warn(aviso);
|
|
1999
|
+
}
|
|
2000
|
+
if (veredito.errors.length > 0) {
|
|
2001
|
+
const primeiro = veredito.errors[0];
|
|
2002
|
+
// `deployFail` emite 1 erro e sai; os demais entram no corpo pra que uma
|
|
2003
|
+
// rodada só mostre tudo que está errado (meio-conserto é não contar os
|
|
2004
|
+
// outros e o dev voltar aqui amanhã).
|
|
2005
|
+
const extras = veredito.errors
|
|
2006
|
+
.slice(1)
|
|
2007
|
+
.map((e) => `\n\nTambém: ${e.message}`)
|
|
2008
|
+
.join('');
|
|
2009
|
+
deployFail(opts.json, primeiro.code, primeiro.message + extras);
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
1701
2012
|
// Caminho workspace (Fase 1 do Fluxo de Ambientes) — sem server/tar/upload.
|
|
1702
2013
|
if (target === 'workspace') {
|
|
1703
2014
|
await runWorkspaceDeploy(opts, productSlug, (opts.env ?? 'dev').toLowerCase(), cwd, nonInteractive);
|
|
@@ -1705,9 +2016,21 @@ export async function runDeploy(opts) {
|
|
|
1705
2016
|
}
|
|
1706
2017
|
// Caminho cloud-run (Fase 3) — imagem de container, serviço dedicado.
|
|
1707
2018
|
if (target === 'cloud-run') {
|
|
1708
|
-
await runCloudRunDeploy(opts, productSlug, (opts.env ?? 'dev').toLowerCase(), cwd, nonInteractive);
|
|
2019
|
+
await runCloudRunDeploy(opts, productSlug, (opts.env ?? 'dev').toLowerCase(), cwd, nonInteractive, envFileTexto);
|
|
1709
2020
|
return;
|
|
1710
2021
|
}
|
|
2022
|
+
// F-11: env name pra GCS path (`gs://neetru-deploy-artifacts/<product>/<env>/…`).
|
|
2023
|
+
// Default 'dev' quando não definido — alinhado com o middleware OAuth.
|
|
2024
|
+
const envName = (opts.env ?? 'dev').toLowerCase();
|
|
2025
|
+
// EnvVars. Rodadas finais 2 e 3 do cli#81: o `--env-file` era lido DEPOIS
|
|
2026
|
+
// de `resolveArtifact`. Um arquivo fora de UTF-8 só era recusado depois do
|
|
2027
|
+
// build interno (que recusava no stderr, e o `deploy --json` ficava sem a
|
|
2028
|
+
// linha JSON) ou depois de o pacote em cache subir ao GCS. Agora o começo de
|
|
2029
|
+
// `runDeploy` o lê e confere; aqui só se montam as variáveis.
|
|
2030
|
+
let envVars;
|
|
2031
|
+
if (envFileTexto !== undefined) {
|
|
2032
|
+
envVars = envVarsDoEnvFile(envFileTexto, target, opts.json, envName);
|
|
2033
|
+
}
|
|
1711
2034
|
let serverChoice = null;
|
|
1712
2035
|
if (target === 'vm') {
|
|
1713
2036
|
serverChoice = await pickServer(opts, nonInteractive);
|
|
@@ -1718,20 +2041,27 @@ export async function runDeploy(opts) {
|
|
|
1718
2041
|
// motivou o guard antigo (Core rejeitava sem --port); o Core agora ignora
|
|
1719
2042
|
// porta em deploy VM, então exigi-la aqui só atrapalharia scripts/CI.
|
|
1720
2043
|
// --port continua aceito (compat) mas é ignorado com WARN — ver abaixo.
|
|
1721
|
-
|
|
1722
|
-
|
|
2044
|
+
//
|
|
2045
|
+
// bug_6d92ef71 (low): o `!opts.json` calava o aviso POR COMPLETO em modo
|
|
2046
|
+
// machine-readable — nem no stdout (certo, quebraria o contrato de 1 linha
|
|
2047
|
+
// JSON do --json), nem em canal nenhum. Quem roda via script nunca
|
|
2048
|
+
// descobria que --port foi descartado. Mantém o aviso humano em stdout no
|
|
2049
|
+
// modo interativo e manda a MESMA informação pro stderr em --json — stderr
|
|
2050
|
+
// não faz parte do contrato de saída parseável, então não quebra nada.
|
|
2051
|
+
// Mensagem também deixou de dizer "staging/prod": o guard dispara pra
|
|
2052
|
+
// QUALQUER ambiente em target=vm (inclusive dev).
|
|
2053
|
+
if (target === 'vm' && opts.port) {
|
|
2054
|
+
const portIgnoredMsg = 'A porta é gerenciada pelo Core em deploy VM (aloca por produto/ambiente/servidor); --port é ignorado.';
|
|
2055
|
+
if (opts.json) {
|
|
2056
|
+
console.error(portIgnoredMsg);
|
|
2057
|
+
}
|
|
2058
|
+
else {
|
|
2059
|
+
log.warn(portIgnoredMsg);
|
|
2060
|
+
}
|
|
1723
2061
|
}
|
|
1724
2062
|
const { domain } = await pickDomain(opts, nonInteractive, productSlug);
|
|
1725
|
-
// F-11: env name pra GCS path (`gs://neetru-deploy-artifacts/<product>/<env>/…`).
|
|
1726
|
-
// Default 'dev' quando não definido — alinhado com o middleware OAuth.
|
|
1727
|
-
const envName = (opts.env ?? 'dev').toLowerCase();
|
|
1728
2063
|
// Build / artifact (default: faz upload pra GCS via signed URL).
|
|
1729
2064
|
const artifact = await resolveArtifact(opts, productSlug, envName, cwd);
|
|
1730
|
-
// EnvVars.
|
|
1731
|
-
let envVars;
|
|
1732
|
-
if (opts.envFile) {
|
|
1733
|
-
envVars = await loadEnvFile(path.resolve(cwd, opts.envFile), target, opts.json);
|
|
1734
|
-
}
|
|
1735
2065
|
// Confirmação interativa.
|
|
1736
2066
|
if (!nonInteractive) {
|
|
1737
2067
|
log.heading('Resumo');
|
|
@@ -1740,7 +2070,13 @@ export async function runDeploy(opts) {
|
|
|
1740
2070
|
// no resumo — só cloud-run/workspace mostravam. `ambiente` é o parâmetro que
|
|
1741
2071
|
// decide se secrets/rota de prod são tocados; tem que aparecer. Em prod,
|
|
1742
2072
|
// destacado (log.warn) pra não passar batido num Enter.
|
|
1743
|
-
|
|
2073
|
+
// 2026-10-12: era `envName === 'prod'`, mas `isProdEnv` (logo acima, e a
|
|
2074
|
+
// guarda que decide o step-up MFA) aceita TAMBEM `production`. Entao um
|
|
2075
|
+
// `--env production` passava pelo gate de MFA como producao e aparecia no
|
|
2076
|
+
// resumo como ambiente qualquer — sem o destaque, com o Enter do operador
|
|
2077
|
+
// no meio. Conserto pela metade: a mesma pergunta respondida de dois jeitos
|
|
2078
|
+
// no mesmo arquivo.
|
|
2079
|
+
if (isProdEnv(envName)) {
|
|
1744
2080
|
log.warn(` ambiente: ${envName} ← PRODUÇÃO`);
|
|
1745
2081
|
}
|
|
1746
2082
|
else {
|
|
@@ -1784,6 +2120,14 @@ export async function runDeploy(opts) {
|
|
|
1784
2120
|
artifactSha256: artifact.artifactSha256,
|
|
1785
2121
|
artifactSizeBytes: artifact.artifactSizeBytes,
|
|
1786
2122
|
};
|
|
2123
|
+
// bug_678ddc98: standalone aninhado de monorepo — o agente não tem como
|
|
2124
|
+
// inferir `apps/web/server.js` olhando a raiz do tarball. Ver
|
|
2125
|
+
// `entrypointParaDeploy` (e, no PR, o que ainda falta do lado do Core).
|
|
2126
|
+
const entrypointAninhado = entrypointParaDeploy(artifact.manifest);
|
|
2127
|
+
if (entrypointAninhado) {
|
|
2128
|
+
body.entrypoint = entrypointAninhado;
|
|
2129
|
+
log.dim(` entrypoint: ${entrypointAninhado} (standalone aninhado — monorepo)`);
|
|
2130
|
+
}
|
|
1787
2131
|
if (target === 'vm') {
|
|
1788
2132
|
body.serverId = serverChoice.id;
|
|
1789
2133
|
// P6 (2026-07-19): NÃO enviar porta em deploy VM — é detalhe interno do
|
|
@@ -1808,6 +2152,7 @@ export async function runDeploy(opts) {
|
|
|
1808
2152
|
headers: mfaHeaders,
|
|
1809
2153
|
body,
|
|
1810
2154
|
});
|
|
2155
|
+
mostrarAvisosDoCore(deploy.warnings, opts.json);
|
|
1811
2156
|
dispatchSpinner.succeed(`Deploy criado: ${chalk.bold(deploy.deploymentId)}`);
|
|
1812
2157
|
}
|
|
1813
2158
|
catch (err) {
|