Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 97 additions & 13 deletions .github/workflows/autofix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ name: autofix
on:
pull_request_target:
types: [opened, reopened, synchronize, ready_for_review]
# A MAIN ANDANDO é o que desatualiza PR, não o PR se mexendo. Foram 9 `chore(release)`
# em 20 horas, e cada um reescreve README/STATUS/ARCH/docs — reabrindo conflito em TODO
# PR aberto ao mesmo tempo. Sem este gatilho o autofix só acorda quando o autor empurra,
# ou seja, nunca para quem está parado esperando revisão, que é justamente quem precisa.
push:
branches: [main]
workflow_dispatch:
inputs:
pr_number:
Expand All @@ -31,12 +37,41 @@ permissions:
pull-requests: write

concurrency:
group: autofix-${{ github.event.pull_request.number || inputs.pr_number }}
group: autofix-${{ github.event.pull_request.number || inputs.pr_number || github.sha }}
cancel-in-progress: true

jobs:
# Varredura pós-release: descobre quem ficou para trás e dispara o autofix de cada um
# pelo `workflow_dispatch`, que é o caminho que enxerga secret. Barato — quem já está
# em dia sai no primeiro passo do autofix, sem clonar nada pesado.
varredura:
if: github.event_name == 'push'
runs-on: ubuntu-latest
timeout-minutes: 10
env:
GH_TOKEN: ${{ secrets.CSBRASIL_BOT_TOKEN }}
REPO: ${{ github.repository }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
- name: Dispara o autofix de quem ficou para trás
run: |
if [ -z "$GH_TOKEN" ]; then
echo "::warning::CSBRASIL_BOT_TOKEN ausente — varredura não roda, e não reprova ninguém."
exit 0
fi
gh pr list --repo "$REPO" --state open --base main \
--json number,mergeable,isDraft \
--jq '.[] | select(.isDraft | not) | select(.mergeable != "MERGEABLE") | .number' > /tmp/atrasados.txt
echo "PRs desatualizados: $(wc -l < /tmp/atrasados.txt)"
while read -r pr; do
[ -n "$pr" ] || continue
echo " → #$pr"
gh workflow run autofix.yml --repo "$REPO" -f pr_number="$pr" \
|| echo "::warning::não deu para disparar o autofix do #$pr"
done < /tmp/atrasados.txt

autofix:
if: github.event.pull_request.draft != true
if: github.event_name != 'push' && github.event.pull_request.draft != true
runs-on: ubuntu-latest
timeout-minutes: 15
env:
Expand Down Expand Up @@ -80,30 +115,77 @@ jobs:
if: env.pular != '1'
run: |
git remote add base "https://github.com/$REPO.git" 2>/dev/null || true
git fetch --depth=1 base "${{ github.event.pull_request.base.ref || 'main' }}"
git checkout FETCH_HEAD -- tools/ scripts/ package.json
git fetch base "${{ github.event.pull_request.base.ref || 'main' }}"
# O lock vem JUNTO com o package.json: `npm ci` exige que os dois batam, e
# restaurar só um deles quebra a instalação (medido em 22/08, 2 de 10 runs).
git checkout FETCH_HEAD -- tools/ scripts/ package.json package-lock.json

- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
if: env.pular != '1'
with: { node-version: 22 }
- name: Instala dependências
if: env.pular != '1'
run: npm ci --ignore-scripts
# `npm ci` morre se lock e manifesto divergirem; aqui a instalação é meio para
# rodar o gerador, não o alvo da medição, então cair para `install` é aceitável.
run: npm ci --ignore-scripts || npm install --ignore-scripts --no-audit --no-fund

- name: Conserta o mecânico
# RESOLVE CONFLITO DE ARQUIVO GERADO. Todo `chore(release)` na main reescreve
# README, STATUS, ARCH e docs/, e reabre conflito em TODO PR aberto ao mesmo
# tempo — medido em 21/08: o alpha.173 sozinho pôs quatro PRs em CONFLICTING, e
# os nove arquivos do #400 eram gerados, nenhum de código. Resolver isso à mão é
# trabalho de Sísifo: o release seguinte desfaz.
#
# A trava é a MESMA do resto do autofix. Sobrou UM conflito fora da lista, o bot
# aborta o merge e devolve para gente — conflito de código é julgamento, e
# julgamento não é dele.
- name: Traz a base e resolve conflito de arquivo gerado
if: env.pular != '1'
id: merge
run: |
git config user.name "csbrasil-bot"
git config user.email "csbrasil-bot@users.noreply.github.com"
BASE="${{ github.event.pull_request.base.ref || 'main' }}"
git fetch base "$BASE"
if git merge --no-edit FETCH_HEAD; then
git diff --quiet HEAD@{1} HEAD || echo "mesclou=1" >> "$GITHUB_OUTPUT"
echo "PR já está em cima da base (ou mesclou limpo)."
exit 0
fi
git diff --name-only --diff-filter=U > /tmp/conflitos.txt
echo "conflitos:"; cat /tmp/conflitos.txt
if ! python3 scripts/ci/autofix_allowlist.py --caminhos < /tmp/conflitos.txt; then
git merge --abort
echo "humano=1" >> "$GITHUB_OUTPUT"
exit 0
fi
# Só gerado: fica o da BASE, e os geradores re-derivam logo abaixo.
xargs -a /tmp/conflitos.txt -r git checkout --theirs --
xargs -a /tmp/conflitos.txt -r git add --
git commit --no-edit -m "Merge da base (conflito só em arquivo gerado, resolvido pelo autofix)" \
-m "Agent: csbrasil-bot (autofix)" \
-m "Signed-off-by: csbrasil-bot <csbrasil-bot@users.noreply.github.com>"
echo "mesclou=1" >> "$GITHUB_OUTPUT"

- name: Comenta quando o conflito é de gente
if: env.pular != '1' && steps.merge.outputs.humano == '1'
run: |
gh pr comment "$PR_NUMBER" --repo "$REPO" --body \
"🤖 **autofix**: a base andou e o conflito encostou em arquivo de código — não é meu para resolver. Os arquivos em conflito estão no log do run; rode \`git merge origin/${{ github.event.pull_request.base.ref || 'main' }}\` e decida."

- name: Conserta o mecânico
if: env.pular != '1' && steps.merge.outputs.humano != '1'
run: |
node tools/gen-docs.mjs || echo "::warning::gen-docs falhou"
node tools/gen-arch.mjs || echo "::warning::gen-arch falhou"

# As ferramentas foram restauradas da base só para rodar; o que volta ao PR é
# apenas o resultado delas.
- name: Devolve as ferramentas ao estado do PR
if: env.pular != '1'
run: git checkout HEAD -- tools/ scripts/ package.json
if: env.pular != '1' && steps.merge.outputs.humano != '1'
run: git checkout HEAD -- tools/ scripts/ package.json package-lock.json

- name: A trava — só arquivo gerado pode virar commit
if: env.pular != '1'
if: env.pular != '1' && steps.merge.outputs.humano != '1'
id: trava
run: |
git status --porcelain > /tmp/mexidos.txt
Expand All @@ -126,12 +208,14 @@ jobs:
"🤖 **autofix**: o conserto encostou em arquivo fora da lista de permissão, então **não commitei nada**. Rode \`npm run docs && node tools/gen-arch.mjs\` e confira o que mudou."

- name: Empurra o conserto
if: env.pular != '1' && steps.trava.outputs.commitar == '1' && env.PODE_EDITAR == 'true'
if: env.pular != '1' && (steps.trava.outputs.commitar == '1' || steps.merge.outputs.mesclou == '1') && env.PODE_EDITAR == 'true'
run: |
git config user.name "csbrasil-bot"
git config user.email "csbrasil-bot@users.noreply.github.com"
xargs -a /tmp/permitidos.txt -r git add --
git commit -m "chore(docs): regenera bloco derivado (autofix)" \
if [ -s /tmp/permitidos.txt ]; then
xargs -a /tmp/permitidos.txt -r git add --
fi
git diff --cached --quiet || git commit -m "chore(docs): regenera bloco derivado (autofix)" \
-m "Rodado pelo autofix: só arquivo gerado, conferido pela lista de permissão." \
-m "Signed-off-by: csbrasil-bot <csbrasil-bot@users.noreply.github.com>"
git push origin "HEAD:$HEAD_REF"
Expand All @@ -141,7 +225,7 @@ jobs:
# Fork com "allow edits by maintainers" desligado: o bot não tem como empurrar.
# Comentar o comando é melhor que um vermelho que o autor não entende.
- name: Sem permissão de push — comenta o comando
if: env.pular != '1' && steps.trava.outputs.commitar == '1' && env.PODE_EDITAR != 'true'
if: env.pular != '1' && (steps.trava.outputs.commitar == '1' || steps.merge.outputs.mesclou == '1') && env.PODE_EDITAR != 'true'
run: |
gh pr comment "$PR_NUMBER" --repo "$REPO" --body \
"🤖 **autofix**: dá para consertar sozinho, mas este PR está com *allow edits by maintainers* desligado. Rode \`npm run docs && node tools/gen-arch.mjs\` e commite — ou ligue a opção e eu faço."
53 changes: 6 additions & 47 deletions .github/workflows/preview-bot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,50 +51,9 @@ jobs:
fi
gh pr comment "$PR" --repo "$REPO" --body "🤖 **cs-brasil-ai-bot**: $TEXTO \`preview-autorizado\`."

preview:
if: >-
github.event.pull_request.head.repo.full_name != github.repository &&
github.event.action == 'labeled' &&
github.event.label.name == 'preview-autorizado'
runs-on: ubuntu-latest
timeout-minutes: 20
environment: preview-forks
steps:
- name: confirma mantenedor, label e SHA aprovado
env:
GH_TOKEN: ${{ github.token }}
ACTOR: ${{ github.actor }}
REPO: ${{ github.repository }}
PR: ${{ github.event.pull_request.number }}
EVENT_SHA: ${{ github.event.pull_request.head.sha }}
run: |
PERMISSION=$(gh api "repos/$REPO/collaborators/$ACTOR/permission" --jq .permission 2>/dev/null || echo none)
case "$PERMISSION" in admin|maintain|write) ;; *) echo "ator sem permissão de mantenedor"; exit 1 ;; esac
API_SHA=$(gh api "repos/$REPO/pulls/$PR" --jq .head.sha)
[ "$API_SHA" = "$EVENT_SHA" ]
gh api "repos/$REPO/issues/$PR/labels" --jq 'any(.[]; .name == "preview-autorizado")' | grep -qx true

- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
with:
ref: ${{ github.event.pull_request.head.sha }}
persist-credentials: false
allow-unsafe-pr-checkout: true
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with: { node-version: 22 }
- run: npm i -g vercel@58.9.0
- name: publica preview
id: deploy
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
run: |
vercel pull --yes --environment=preview --token "$VERCEL_TOKEN"
vercel build --token "$VERCEL_TOKEN"
URL=$(vercel deploy --prebuilt --token "$VERCEL_TOKEN")
echo "url=$URL" >> "$GITHUB_OUTPUT"
- name: comenta URL
env:
GH_TOKEN: ${{ github.token }}
run: |
gh pr comment "${{ github.event.pull_request.number }}" --repo "${{ github.repository }}" --body "🤖 **cs-brasil-ai-bot**: preview no ar → ${{ steps.deploy.outputs.url }}"
# O JOB `preview` SAIU DAQUI (22/08). Ele publicava com `vercel build`, ou seja,
# executava o build do fork com o VERCEL_TOKEN no ambiente — e era por isso que
# exigia um mantenedor aprovar cada push. O preview agora é feito em duas metades
# que não precisam de aprovação nenhuma: `preview-build.yml` compila o código do
# fork SEM segredo, e `preview-deploy.yml` publica COM segredo sem executar nada
# do PR. Este arquivo ficou só com a classificação do diff, que continua útil.
81 changes: 81 additions & 0 deletions .github/workflows/preview-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# preview-build — COMPILA o PR sem ter nada para roubar.
#
# Metade 1 de 2 do preview de fork. A regra que faz isso funcionar sem aprovação
# humana: este job roda o código do FORK e por isso NÃO recebe segredo nenhum.
# `pull_request` (e não `pull_request_target`) garante isso — num PR de fork o
# GitHub roda com token só-leitura e sem acesso a `secrets`.
#
# O que sai daqui é BYTE ESTÁTICO: o `npm run build` do Astro com adapter da Vercel
# já produz `.vercel/output` inteiro, que é exatamente o que `vercel deploy
# --prebuilt` consome. Quem publica é o `preview-deploy.yml`, que tem o segredo e
# NÃO executa nada do fork.
#
# Antes disso o preview de fork dependia de um mantenedor aprovar cada push, porque
# o desenho antigo rodava `vercel build` (código do fork) com o VERCEL_TOKEN no
# ambiente. Separar as duas metades resolve os dois lados de uma vez: preview
# automático e token que nunca encosta em código de terceiro.
name: preview-build

on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]

# Só leitura. Este job não comenta, não rotula e não publica.
permissions:
contents: read

concurrency:
group: preview-build-${{ github.event.pull_request.number }}
cancel-in-progress: true

jobs:
build:
if: github.event.pull_request.draft != true
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
persist-credentials: false

- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with: { node-version: 22, cache: npm }

- run: npm ci

# Os pacotes de asset vêm de release público — sem segredo, e com retry para
# soluço de rede não virar PR vermelho (é a DG2).
- name: Assets
run: |
bash scripts/fetch-audio.sh
bash scripts/fetch-decals.sh
npm run strip:decalbg
npm run assert:assets

- name: Build
run: npm run build

# O número do PR viaja junto: o job de deploy roda em `workflow_run`, onde o
# contexto não sabe de qual PR veio.
- name: Anota o PR
run: |
mkdir -p .vercel/output
echo "${{ github.event.pull_request.number }}" > /tmp/pr-numero.txt
echo "${{ github.event.pull_request.head.sha }}" > /tmp/pr-sha.txt

- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: preview-${{ github.event.pull_request.number }}
path: |
.vercel/output
retention-days: 3
if-no-files-found: error

- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: preview-meta-${{ github.event.pull_request.number }}
path: |
/tmp/pr-numero.txt
/tmp/pr-sha.txt
retention-days: 3
if-no-files-found: error
Loading
Loading