Aviso não segura padrão. O que segura é a regra escrita como código que o build executa. O aviso precisa de uma pessoa que leia, lembre e se disponha a travar um merge por causa de um campo de metadado. O erro de build não precisa de ninguém. Neste site, uma peça sem data de publicação não gera comentário de revisão: ela derruba o build inteiro, e a mensagem diz o arquivo e o campo que faltou.
O ciclo de vida do aviso é conhecido. Na primeira vez, o revisor comenta "faltou a data" e o autor corrige. Na segunda, o comentário sai com menos energia. Quando chega a terceira, o revisor está de férias, o merge passa e o padrão deixou de ser padrão: virou preferência de uma pessoa, e preferência se negocia. Guia de estilo tem o mesmo destino em câmera lenta. Minha posição: padrão que só existe em documento já está morto, e ninguém percebeu.
Um job de CI que apenas imprime aviso amarelo não muda esse ciclo, porque aviso amarelo é um log mais caro. A diferença entre exit 0 e exit 1 é a diferença entre opinião e regra.
As peças deste site são arquivos MDX versionados em apps/web/content/blog/. Cada arquivo abre com um bloco YAML de metadados, o frontmatter. Quem lê tudo isso é o parser em apps/web/src/lib/content/files.ts, executado no build. A primeira decisão dele é o que acontece quando algo está errado:
ts
function fail(file: string, message: string): never {
throw new Error(`Content file ${file}: ${message}`);
}
O tipo de retorno é never: a função não devolve valor, ela interrompe. Modo aviso não existe nesse contrato.
Dali em diante, cada regra chama fail. Título é obrigatório e não pode ser espaço em branco. E a regra que interessa aqui: peça sem publishedAt só existe enquanto for rascunho.
ts
if (typeof data.title !== "string" || !data.title.trim()) fail(file, 'missing required frontmatter field "title"');
const draft = data.draft === true;
const publishedAt = asIsoDate(data.publishedAt, file, "publishedAt");
if (!draft && !publishedAt) fail(file, 'missing required frontmatter field "publishedAt" (required unless draft)');
O throw não fica esperando alguém visitar a página com problema. Como o sitemap.ts lê todas as coleções durante o build, um arquivo malformado estoura ali mesmo, antes de existir versão pública. O deploy para. Esse é o preço certo: quem escreveu a peça descobre o erro no build seguinte ao push, com o caminho do arquivo na tela, e produção nunca fica sabendo.
Repare que a checagem não é um lint separado, configurado no CI e esquecido. O parsePiece que valida é o mesmo que alimenta a listagem, a página da peça, o RSS e o sitemap. Não existe uma segunda implementação para divergir da primeira. O site só consegue ler conteúdo passando pela regra, porque a regra é o leitor.
A segunda metade dessa distinção é a que importa. Peça malformada que renderiza em silêncio como página vazia é erro fantasiado de vazio, o pior estado de um sistema: ninguém investiga o que parece estar funcionando. O comentário no topo de files.ts registra a decisão: frontmatter inválido lança erro com o caminho do arquivo e o campo, porque a alternativa é a falha silenciosa que este repositório proíbe em todos os outros lugares.
A prova está no texto que você lê agora. Escrevi esta peça com draft: true e sem publishedAt, exatamente o estado que o parser reprovaria numa peça publicada. Rascunho renderiza em dev e no preview, nunca em produção:
No dia de publicar, troco o draft: true por uma data. Se eu remover o rascunho e esquecer a data, o deploy morre com Content file content/blog/build-reprova-conteudo-fora-do-padrao.mdx: missing required frontmatter field "publishedAt" (required unless draft). Não preciso lembrar da regra, e revisor nenhum precisa: a máquina lembra, todas as vezes, no mesmo ponto do fluxo. Se esta página está no ar, a data existe. O texto passou pela regra que descreve.
O que mudou de lugar foi o padrão. Ele morava na cabeça de quem revisa e no "todo mundo sabe". Agora mora num parser que o build executa: checa e não deixa passar. Revisor de férias e sexta-feira às 18h não alteram o resultado, porque nenhum desses estados existe para o parser.
Aviso é pedido. Erro de build é regra. Todo padrão sustentado na base do pedido está em negociação permanente, e o primeiro prazo apertado vence a negociação. O padrão que sobrevive é o que alguém teve o trabalho de escrever como código que falha.