fix(deploy): reverte package.json para desbloquear o deploy — cache deps do BuildKit

O deploy de fea6c645 falhou 2x: a mudança do script generate:importmap
em package.json invalidou a cache da camada deps do Dockerfile no
homeserver, e o pnpm install re-rodou do zero — o postinstall do
sharp@0.32.6 (transitivo) estourou o timeout baixando o libvips de
objects.githubusercontent.com ('sharp: Installation error: Request
timed out'; o git fetch de github.com funciona — o release vai por
outro hostname da CDN). Produção intocada (falha pré compose swap).

Reverte package.json ao byte do estado cacheado (a9f0c61e) — o install
nem roda. Remove o wrapper generate-importmap.mjs (desligado vira dead
code); proteção do importMap fica com o guard CI+deploy + pin unit +
contrato. Hardening do deps documentado no post-mortem (retry do
install / libvips via apk / proxy), changelog do follow-up incluído.
This commit is contained in:
Francisco Solla 2026-08-27 08:40:25 -03:00
parent fea6c64514
commit 81513d5b14
4 changed files with 16 additions and 49 deletions

View file

@ -0,0 +1 @@
**Admin em branco — follow-up: deploy bloqueado pelo reinstall do deps, package.json revertido (2026-08-27):** o fix do importMap (#926, main em `fea6c645`) não conseguia PUBLICAR: o deploy manual falhou 2× no build do migrator — a mudança de `package.json` (script `generate:importmap` → wrapper) invalidou a cache BuildKit da camada `deps` do Dockerfile, o `pnpm install --frozen-lockfile` re-rodou do zero e o `postinstall` do `sharp@0.32.6` (transitivo) estourou o timeout baixando o libvips de `objects.githubusercontent.com` no homeserver (`sharp: Installation error: Request timed out`; o `git fetch` de github.com funciona — o release vai por hostname diferente da CDN). Produção ficou intocada (falha antes do compose swap). Correção: `package.json` revertido ao byte do estado cacheado (`a9f0c61e`) — a cache do `deps` volta a valer e o install nem roda; wrapper `scripts/generate-importmap.mjs` **removido** (ficaria desligado — dead code); proteção mantida pelo guard `check-importmap-s3.mjs` (ci-pr.yml + verify do deploy) + pin unit + contrato. Dívida documentada no post-mortem: hardening da camada `deps` (retry do install / libvips via apk / proxy do download) — sem ele, qualquer mudança futura em package.json/pnpm-lock/pnpm-workspace volta a bustar a cache e travar o deploy igual. Ação: re-dispatch manual do `deploy.yml` (o guard do HEAD exige main atual; o `verify` já passou 19m verde no run anterior).

View file

@ -58,21 +58,22 @@ Regeneração do importMap com envs S3\_\* dummy — o diff foi de exatamente +2
## Prevenção
| Estratégia | Custo | Estado |
| ------------------------------------------------------------------------------------------------------------------------- | ------ | ------------------------------------------ |
| Pin unit `importMapS3UploadHandler.unit.spec.ts` (falha se a entrada sumir) | barata | implementada agora (mesmo PR) |
| Guard `scripts/check-importmap-s3.mjs` no ci-pr.yml (content guards) e no job verify do deploy.yml | barata | implementada agora (mesmo PR) |
| Wrapper `scripts/generate-importmap.mjs` no `pnpm generate:importmap` (injeta S3\_\* dummy quando ausentes) | barata | implementada agora (mesmo PR) |
| Contrato compartilhado `scripts/lib/importMapContract.mjs` (chave + prefixo do handler, chaves das envs S3) | barata | implementada agora (mesmo PR) |
| AGENTS.md: instrução das envs S3\_\* no generate:importmap + referência aos guards | barata | implementada agora (mesmo PR) |
| Gerar o importMap no BUILD (prebuild com S3\_\* via BuildKit secrets) em vez de commitá-lo | cara | documentada — não implementada neste fluxo |
| Tornar o plugin s3Storage incondicional no config (adaptador local em dev) | cara | documentada — não implementada neste fluxo |
| Smoke real de deploy: abrir /admin em headless e assertar o mount da raiz Payload (não só HTTP 200) | cara | documentada — não implementada neste fluxo |
| Fail-fast/erro estruturado quando getFromImportMap retorna undefined (hoje é console.error silencioso no log do servidor) | cara | documentada — não implementada neste fluxo |
| Estratégia | Custo | Estado |
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Pin unit `importMapS3UploadHandler.unit.spec.ts` (falha se a entrada sumir) | barata | implementada agora (mesmo PR) |
| Guard `scripts/check-importmap-s3.mjs` no ci-pr.yml (content guards) e no job verify do deploy.yml | barata | implementada agora (mesmo PR) |
| Wrapper `scripts/generate-importmap.mjs` no `pnpm generate:importmap` (injeta S3\_\* dummy quando ausentes) | barata | removida no follow-up (2026-08-27) — a mudança de package.json invalida a cache BuildKit da camada `deps` no homeserver e força reinstall cujo `postinstall` do `sharp@0.32.6` falha (timeout do libvips em objects.githubusercontent.com; 2 deploys falhos). Re-ligar junto com o hardening do Dockerfile |
| Contrato compartilhado `scripts/lib/importMapContract.mjs` (chave + prefixo do handler, chaves das envs S3) | barata | implementada agora (mesmo PR) |
| AGENTS.md: instrução das envs S3\_\* no generate:importmap + referência aos guards | barata | implementada agora (mesmo PR) |
| Gerar o importMap no BUILD (prebuild com S3\_\* via BuildKit secrets) em vez de commitá-lo | cara | documentada — não implementada neste fluxo |
| Tornar o plugin s3Storage incondicional no config (adaptador local em dev) | cara | documentada — não implementada neste fluxo |
| Smoke real de deploy: abrir /admin em headless e assertar o mount da raiz Payload (não só HTTP 200) | cara | documentada — não implementada neste fluxo |
| Fail-fast/erro estruturado quando getFromImportMap retorna undefined (hoje é console.error silencioso no log do servidor) | cara | documentada — não implementada neste fluxo |
| Hardening da camada `deps` do Dockerfile (retry do `pnpm install` / libvips via apk / proxy do download do sharp) — qualquer mudança em package.json/pnpm-lock/pnpm-workspace busta a cache e o egress do homeserver para objects.githubusercontent.com não conclui downloads | cara | documentada — não implementada neste fluxo |
**Estratégia implementada:** pin unit que falha se o handler sumir do importMap; guard de CI+deploy que regenera com S3\_\* dummy e falha em divergência (drift + restore do arquivo); wrapper do `generate:importmap` que injeta as envs dummy — a regeneração local nunca mais orfa o handler; contrato compartilhado da chave/prefixo e das envs; doc no AGENTS.md.
**Estratégia implementada:** pin unit que falha se o handler sumir do importMap; guard de CI+deploy que regenera com S3\_\* dummy e falha em divergência (drift + restore do arquivo); contrato compartilhado da chave/prefixo e das envs; doc no AGENTS.md. O wrapper do `generate:importmap` foi removido no follow-up (a mudança de package.json que o ligava quebrou o deploy — ver tabela); a proteção fica com o guard + pin.
**Estratégia documentada (cara):** gerar o importMap no build (elimina a classe: artefato gerado não drift; toca o pipeline do homeserver); tornar o s3Storage incondicional (estabiliza o importMap; muda storage local e caminho de media); smoke de deploy que abre /admin em headless e asserta o mount da raiz Payload; fail-fast estruturado quando getFromImportMap retorna undefined.
**Estratégia documentada (cara):** gerar o importMap no build (elimina a classe: artefato gerado não drift; toca o pipeline do homeserver); tornar o s3Storage incondicional (estabiliza o importMap; muda storage local e caminho de media); smoke de deploy que abre /admin em headless e asserta o mount da raiz Payload; fail-fast estruturado quando getFromImportMap retorna undefined; hardening da camada `deps` do Dockerfile contra o reinstall completo no homeserver.
## Lições

View file

@ -61,7 +61,7 @@
"changelog:build": "node scripts/build-changelog.mjs",
"changelog:read": "node scripts/build-changelog.mjs --stdout",
"changelog:check": "node scripts/build-changelog.mjs --check",
"generate:importmap": "node scripts/generate-importmap.mjs",
"generate:importmap": "cross-env NODE_OPTIONS=\"--no-deprecation --conditions=react-server\" payload generate:importmap",
"generate:types": "cross-env NODE_OPTIONS=\"--no-deprecation --conditions=react-server\" payload generate:types",
"migrate": "cross-env NODE_OPTIONS=\"--no-deprecation --conditions=react-server\" payload migrate",
"migrate:create": "cross-env NODE_OPTIONS=\"--no-deprecation --conditions=react-server\" payload migrate:create",

View file

@ -1,35 +0,0 @@
#!/usr/bin/env node
/**
* Wrapper for `payload generate:importmap` that always injects dummy S3_*
* envs (when unset) so the generated importMap carries the
* S3ClientUploadHandler entry — regenerating without the S3_* envs silently
* orphans it and the admin goes blank in production (class
* OPS69/OPS72/OPS73). Dummy values are fine: the generator only iterates the
* config, it never connects to the bucket.
*/
import { spawnSync } from 'node:child_process'
import { S3_STORAGE_ENV_KEYS } from './lib/importMapContract.mjs'
const DUMMY = {
S3_BUCKET: 'dev-dummy-bucket',
S3_ENDPOINT: 'http://127.0.0.1:3900',
S3_ACCESS_KEY_ID: 'dev-dummy-access-key',
S3_SECRET_ACCESS_KEY: 'dev-dummy-secret-key',
}
const env = {
...process.env,
// Same NODE_OPTIONS as before — the payload CLI loads server-only modules.
NODE_OPTIONS: '--no-deprecation --conditions=react-server',
...Object.fromEntries(
S3_STORAGE_ENV_KEYS.map((key) => [key, process.env[key]?.trim() || DUMMY[key]]),
),
}
const result = spawnSync('payload', ['generate:importmap'], {
stdio: 'inherit',
env,
})
process.exit(result.status ?? 1)