O `pnpm knip` imprimia `ERROR: Error loading src/payload.config.ts (...server-only...)` e saía 0 — gate verde com o ERROR silencioso, no CI (`ci-pr.yml`/`deploy.yml`) e local. O plugin embutido do Payload carregava `src/payload.config.ts` via jiti sob as condições Node padrão e o import transitivo de `server-only` lançava (só o `react-server` traz o `empty.js`); o `paths.server-only` do `knip.json` alimenta apenas o resolver estático, não o carregamento jiti (e o `tsconfig` alias, que funcionaria no load, foi rejeitado por re-apontar a guarda server/client — classe OPS69). Fix: `"payload": false` e as contribuições do plugin explicitadas no `knip.json` (`!src/payload-types.ts` load-bearing; `!src/migrations/**` paridade), removendo o bloco `paths` inerte. Provado: `pnpm knip` sai 0 sem o ERROR, um export morto plantado é detectado, e a corrida de controle com `server-only` neutralizado dá findings idênticos. Plano: docs/plans/ops-knip-importmap-artifact-impl.md
9.6 KiB
Impl: OPS: knip carrega o payload.config.ts com erro (importMap commitado) e roda com análise degradada
Status: aprovado Atualizado em: 2026-09-16 Issue: #1094 Intenção: docs/plans/ops-knip-importmap-artifact.md Appetite restante: herdado — ~30 min eng; sem schema, sem deploy, sem UI (Impeccable A)
Leitura da intenção
- Outcome:
pnpm knipdeixa de imprimirERROR: Error loading src/payload.config.ts (This module cannot be imported from a Client Component module...)na stderr e a análise continua completa — um export morto plantado segue sendo detectado. Hoje o erro aparece e o comando sai 0, gate verde com grafo degradado. - O que NÃO negociar: a cobertura de análise (nenhum finding novo/ausente por causa do fix), a suíte de gates inalterada em ordem e comportamento, e nenhum risco à classe OPS69 (admin em branco) —
importMap.jspermanece commitado e oentryexplícito permanece. - O que reavaliar: a hipótese da intenção de que
paths.server-only"não está sendo aplicado" está correta, mas a correção de rumo é outra:pathsalimenta o resolver estático do knip, não o carregamento jiti dopayload.config.ts; e destrackear oimportMap.js(opção b da intenção) não remove o ERROR. Ambas caem.
Abordagem recomendada
flowchart TD
A["pnpm knip"] --> B{"payload plugin enabled?"}
B -- sim --> C["jiti carrega src/payload.config.ts<br/>(condições Node default)"]
C --> D["transitive import de server-only<br/>exports[.].default = index.js"]
D --> E["throw: cannot be imported from a Client Component"]
E --> F["ERROR na stderr + exit 0 (grafo sem as entradas derivadas do config)"]
B -- "não (payload:false)" --> G["config globs e resolveConfig<br/>deixam de ser injetados"]
G --> H["importMap já é entry explícito<br/>+ negations project explícitas"]
H --> I["pnpm knip: sem ERROR, grafo completo"]
I --> J["export morto plantado é detectado<br/>(Unused exports 1)"]
Opções consideradas: A (desabilitar o plugin Payload e explicitar o que ele contribuía) | B (manter o plugin com globs neutralizados) | C (tornar o config carregável via alias/condição) | D (destrackear importMap.js)
Recomendação: A — "payload": false em knip.json, promover a contribuição útil do plugin para o próprio knip.json (o entry do importMap já existe; adicionar "!src/migrations/**" e "!src/payload-types.ts" ao project) e remover o bloco paths inerte. O plugin só contribui com (a) os globs de config, (b) um resolveConfig que devolve o importMap como entry diferido — redundante, pois o importMap já é entry explícito — e (c) as exclusões de projeto. Ou seja, desligá-lo e escrever (a)+(c) à mão preserva a análise sem manter um plugin que nunca consegue carregar o config. Prova empírica (config temporário): exit 0 sem ERROR e sem findings; sonda export const __knipDeadProbe = 1 em src/lib/campaignTime.ts detectada (Unused exports (1)); corrida de controle neutralizando node_modules/server-only/index.js para permitir o carregamento pelo plugin produziu findings idênticos (exit 0, nenhum) — no estado atual, zero perda de findings. Das duas exclusões, só !src/payload-types.ts é load-bearing: sem ela, payload-types.ts gera 74 "unused exported types" espúrios; !src/migrations/** reproduz a exclusão de história congelada do plugin e é redundante hoje (as migrations já são entry), mantida por paridade de escopo.
Rejeitadas:
- B — obscuro, mantém um plugin que nunca carregará o config e ainda exige as negações escritas à mão; não há ganho sobre A.
- C — o alias de
server-onlyfuncionaria no carregamento: o jiti do knip (node_modules/knip/dist/util/jiti.js) usatsconfigPaths: true, entãocompilerOptions.pathsnotsconfig.jsonresolveria para o stub e o config carregaria (verificado). Rejeitada mesmo assim porque re-apontaserver-onlypara um no-op em toda a toolchain (tsc/bundler), esvaziando a própria guarda de fronteira server/client e arriscando a classe OPS69 (admin em branco);NODE_OPTIONS=--conditions=react-servere opathsdoknip.jsonnão atingem o load (testados). O alias existe hoje só escopado ao vitest — mantê-lo escopado é a diferença. - D — não remove o ERROR (causa independente: ele também ocorre sem o arquivo); sem o importMap o knip sai 1 com
Unresolved imports (2) ../importMap+Unused files (2)dos componentes admin; e reabre o risco OPS69 que motivou commitá-lo (fea6c645/b03cec7c).
Componentes / mudanças
knip.json(blocopaths, removido nesta mudança): o mapeamentoserver-onlynão afeta o carregamento jiti — era a tentativa equivocada de correção; remover deixa o config honesto.knip.json: adicionar"payload": false(desabilita o plugin embutido, que hoje importasrc/payload.config.tsvia jiti).knip.json(project): adicionar"!src/migrations/**"e"!src/payload-types.ts"— reproduzem as exclusões que o plugin injetava. Só a segunda é load-bearing (sem ela, 74 findings espúrios empayload-types.ts); a primeira espelha o escopo de história congelada do plugin.knip.json(entry): inalterado — o importMap já é umentryexplícito, o que torna oresolveConfigdo plugin redundante.docs/TECH-DEBT.md:10: fechar a linha do ledger (impacto/status →closed 2026-09-16 (OPS124)).docs/changelog/2026-09-16-ops124.md: entrada nova exigida pelos docs-guards.docs/plans/ops-knip-importmap-artifact.md: atualizar status da intenção.- Demais docs com a P3 obsoleta (
AGENTS-infra.md:24,.agents/rules/engineering-standards.mdc:15,.agents/skills/engineering-audit/SKILL.md:97): corrigir a referência para não manter a dívida "open".
Dados → forma (se aplicável)
Não se aplica — nenhuma mudança de schema, coleção, global ou migração. A mudança é apenas de configuração de tooling (knip.json) e de documentação do ledger.
Fases verificáveis
- knip.json + prova local — aplicar A (
payload: false, negações noproject, removerpaths); rodarpnpm knipe confirmar exit 0 semError loading src/payload.config.ts; plantarexport const __knipDeadProbe = 1emsrc/lib/campaignTime.tse confirmar que ele é reportado; remover a sonda. - Fechamento de ledger + changelog + gate — fechar
docs/TECH-DEBT.md:10, ajustar as referências P3 obsoletas, criardocs/changelog/2026-09-16-ops124.md; rodarpnpm gate:faste depoispnpm push.
Rabbit holes / Não escopo (engenharia)
- Ordem do CI — knip antes do build já é o caso; não alterar pipelines.
knip:production(package.json) — não referenciado no CI; já sai 1 com 32 unused-dependencies pré-existentes sob a config atual E a corrigida. Fora de escopo; apenas registrar o estado.- Outros pontos do
knip.json(ignore,ignoreBinaries,ignoreDependencies,rules) — não mexer. - Status de commit do
importMap.js— permanece commitado (decisão OPS69/OPS99 em sentido inverso); não destrackear. server-only/ vitest alias — o alias de teste (vitest.config.mts,vitest.unit.config.mts) fica como está; não é o caminho do fix.
Riscos e mitigação
- Desligar o plugin esconde futuras entries derivadas do config — mitigado mantendo o importMap como
entryexplícito e documentando a decisão; o plugin hoje não adiciona collections/globals/components, então não há perda atual. - As exclusões do
projectpassam a ser nossas — atualizações futuras do plugin Payload que mexam noprojectdefault (node_modules/knip/dist/plugins/payload/index.js) deixam de se aplicar em silêncio. Não há como manterplugin.projectcom o plugin configurado por objeto (normalizePluginConfig/getPluginProjectFilePatternsdescartam o default quando um config é dado), então a paridade é manual por natureza; o gatilho de revisão é o upgrade do knip. - Classificação do CI —
scripts/lib/test-affected-core.mjslistaknip.jsonemCODE_CONFIG_EXACT, então o diff forçacode_mode=codee o knip É exercitado noci-pr.ymle nodeploy.yml; nada a mudar. - Impacto em runtime/produção — nenhum: knip é dev-only; o build e o admin não são tocados.
- Regressão de cobertura de análise — validada pela sonda de export morto + corrida de controle com o plugin forçado a carregar (findings idênticos).
Aceite de engenharia
pnpm knipsai 0 sem imprimirError loading src/payload.config.ts.- Um export morto plantado em
src/lib/campaignTime.tsé detectado (Unused exports), provando análise completa. payload-types.tsnão gera findings espúrios (!src/payload-types.tspresente, a exclusão load-bearing).- Bloco
paths.server-onlyremovido;importMap.jssegue comoentryexplícito e commitado. docs/TECH-DEBT.md:10fechado, referências P3 obsoletas corrigidas, changelogdocs/changelog/2026-09-16-ops124.mdcriado.pnpm gate:fastepnpm pushverdes (gate:fast verde; push no fechamento).
Self-score (decisão)
5/5 — (1) decisões caras com rejeição explícita (B/C/D com motivo e prova); (2) cabe no appetite de ~30 min sem tocar schema/deploy/CI; (3) rabbit holes nomeados (CI order, knip:production, demais pontos do knip.json, status do importMap); (4) profundidade reusa o existente — corrige o knip.json dono da decisão e reaproveita as contribuições do plugin em vez de criar caminho paralelo; (5) o outcome da intenção é satisfeito com prova objetiva (ERROR some + export morto detectado + controle de zero perda).