Skip to content
Open
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
93 changes: 93 additions & 0 deletions .github/workflows/reconcile-project-items.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
name: Reconcile Project items (nfe/5)

on:
schedule:
- cron: '0 * * * *'
workflow_dispatch:
inputs:
dry_run:
description: 'Dry run (nao adiciona, so reporta)'
type: boolean
default: false

permissions:
contents: read

jobs:
reconcile:
runs-on: ubuntu-latest
steps:
- name: Add orphan issues to Project #5
env:
GH_TOKEN: ${{ secrets.WORK_PROJECT_TOKEN }}
PROJECT_ID: PVT_kwDOAID6As4BUp17
DRY_RUN: ${{ inputs.dry_run || 'false' }}
run: |
set -euo pipefail

in_project=$(mktemp)
gh api graphql --paginate \
-f query='
query($projectId: ID!, $endCursor: String) {
node(id: $projectId) {
... on ProjectV2 {
items(first: 100, after: $endCursor) {
nodes { content { ... on Issue { id } } }
pageInfo { endCursor hasNextPage }
}
}
}
}' \
-F projectId="$PROJECT_ID" \
--jq '.data.node.items.nodes[].content.id // empty' > "$in_project"
sort -u -o "$in_project" "$in_project"
echo "Issues ja no Project: $(wc -l < "$in_project")"

all_open=$(mktemp)
mapfile -t repos < <(gh api 'orgs/nfe/repos?per_page=100&type=all' --paginate --jq '.[] | select(.archived == false) | .name')
echo "Repos nao-arquivados: ${#repos[@]}"
for repo in "${repos[@]}"; do
gh api --paginate "repos/nfe/$repo/issues?state=open&per_page=100" \
--jq '.[] | select(.pull_request == null) | .node_id' >> "$all_open" 2>/dev/null || true
done
sort -u -o "$all_open" "$all_open"
echo "Issues abertas na org: $(wc -l < "$all_open")"

orphans=$(mktemp)
comm -23 "$all_open" "$in_project" > "$orphans" || true
orphan_count=$(wc -l < "$orphans")
echo "Orphas a adicionar: $orphan_count"

if [ "$orphan_count" -eq 0 ]; then
echo "Nada a fazer."
exit 0
fi

if [ "$DRY_RUN" = "true" ]; then
echo "[dry-run] orphans:"
cat "$orphans"
exit 0
fi

added=0; failed=0
while IFS= read -r issue_id; do
[ -z "$issue_id" ] && continue
if gh api graphql \
-f query='
mutation($project: ID!, $content: ID!) {
addProjectV2ItemById(input: {projectId: $project, contentId: $content}) {
item { id }
}
}' \
-F project="$PROJECT_ID" \
-F content="$issue_id" >/dev/null 2>&1; then
added=$((added+1))
else
failed=$((failed+1))
echo "::warning::falha ao adicionar $issue_id"
fi
done < "$orphans"

echo "=== Resumo ==="
echo "Adicionadas: $added"
echo "Falhas: $failed"
153 changes: 153 additions & 0 deletions .github/workflows/sync-auto-add-workflow.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
name: Sync auto-add-to-project workflow

on:
schedule:
- cron: '0 6 * * *'
workflow_dispatch:
inputs:
dry_run:
description: 'Dry run (lista repos sem abrir PR)'
type: boolean
default: false
limit:
description: 'Max de repos a processar (0 = todos)'
type: string
default: '0'

permissions:
contents: read

jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Sync template to all non-archived repos
env:
GH_TOKEN: ${{ secrets.WORK_PROJECT_TOKEN }}
TEMPLATE_PATH: templates/auto-add-to-project.yml
TARGET_PATH: .github/workflows/auto-add-to-project.yml
SYNC_BRANCH: chore/auto-add-to-project-sync
DRY_RUN: ${{ inputs.dry_run || 'false' }}
LIMIT: ${{ inputs.limit || '0' }}
run: |
set -euo pipefail

if [ ! -f "$TEMPLATE_PATH" ]; then
echo "::error::Template nao encontrado em $TEMPLATE_PATH"
exit 1
fi

TEMPLATE_CONTENT=$(cat "$TEMPLATE_PATH")
TEMPLATE_B64=$(base64 -w0 "$TEMPLATE_PATH")

mapfile -t repos < <(gh api 'orgs/nfe/repos?per_page=100&type=all' --paginate --jq '.[] | select(.archived == false) | .name')
total=${#repos[@]}
echo "Encontrados $total repos nao-arquivados na org nfe"

if [ "$LIMIT" != "0" ] && [ "$LIMIT" -gt 0 ] 2>/dev/null; then
repos=("${repos[@]:0:$LIMIT}")
echo "Limitando a ${#repos[@]} repos (input limit=$LIMIT)"
fi

opened=0; skipped=0; failed=0

for repo in "${repos[@]}"; do
echo "::group::$repo"

existing_sha=""
content_matches=false
if resp=$(gh api "repos/nfe/$repo/contents/$TARGET_PATH" 2>/dev/null); then
existing_sha=$(echo "$resp" | jq -r '.sha')
existing_content=$(echo "$resp" | jq -r '.content' | base64 -d 2>/dev/null || echo "")
if [ "$existing_content" = "$TEMPLATE_CONTENT" ]; then
content_matches=true
fi
fi

if [ "$content_matches" = "true" ]; then
echo "ja sincronizado"
skipped=$((skipped+1))
echo "::endgroup::"
continue
fi

if pr_num=$(gh pr list -R "nfe/$repo" --head "$SYNC_BRANCH" --state open --json number --jq '.[0].number' 2>/dev/null) && [ -n "$pr_num" ]; then
echo "PR #$pr_num ja aberto"
skipped=$((skipped+1))
echo "::endgroup::"
continue
fi

if [ "$DRY_RUN" = "true" ]; then
echo "[dry-run] abriria PR em nfe/$repo"
opened=$((opened+1))
echo "::endgroup::"
continue
fi

default_branch=$(gh api "repos/nfe/$repo" --jq '.default_branch')
base_sha=$(gh api "repos/nfe/$repo/git/ref/heads/$default_branch" --jq '.object.sha')

if gh api "repos/nfe/$repo/git/ref/heads/$SYNC_BRANCH" >/dev/null 2>&1; then
gh api -X PATCH "repos/nfe/$repo/git/refs/heads/$SYNC_BRANCH" \
-f sha="$base_sha" -F force=true >/dev/null
else
gh api -X POST "repos/nfe/$repo/git/refs" \
-f ref="refs/heads/$SYNC_BRANCH" \
-f sha="$base_sha" >/dev/null
fi

branch_sha=""
if resp=$(gh api "repos/nfe/$repo/contents/$TARGET_PATH?ref=$SYNC_BRANCH" 2>/dev/null); then
branch_sha=$(echo "$resp" | jq -r '.sha')
fi

commit_msg="chore: add auto-add-to-project workflow"
[ -n "$existing_sha" ] && commit_msg="chore: update auto-add-to-project workflow"

if [ -n "$branch_sha" ]; then
gh api -X PUT "repos/nfe/$repo/contents/$TARGET_PATH" \
-f message="$commit_msg" \
-f content="$TEMPLATE_B64" \
-f branch="$SYNC_BRANCH" \
-f sha="$branch_sha" >/dev/null
else
gh api -X PUT "repos/nfe/$repo/contents/$TARGET_PATH" \
-f message="$commit_msg" \
-f content="$TEMPLATE_B64" \
-f branch="$SYNC_BRANCH" >/dev/null
fi

pr_body='Sincroniza o workflow `auto-add-to-project.yml` a partir de [nfe/.github](https://github.com/nfe/.github/blob/main/templates/auto-add-to-project.yml).

Ao merge, toda nova issue aberta neste repositorio sera automaticamente adicionada ao Project [Work - Product & Engineering (nfe/5)](https://github.com/orgs/nfe/projects/5).

**Requisitos:**
- Secret organization-level `WORK_PROJECT_TOKEN` (PAT com scope `project`) visivel para este repositorio

---
_PR aberto automaticamente pelo workflow `sync-auto-add-workflow` em nfe/.github._'

if pr_url=$(gh pr create -R "nfe/$repo" \
--base "$default_branch" \
--head "$SYNC_BRANCH" \
--title "chore: sync auto-add-to-project workflow" \
--body "$pr_body" 2>&1); then
echo "aberto: $pr_url"
opened=$((opened+1))
else
echo "::warning::falha ao abrir PR em $repo: $pr_url"
failed=$((failed+1))
fi

echo "::endgroup::"
done

echo ""
echo "=== Resumo ==="
echo "PRs abertos: $opened"
echo "Pulados: $skipped"
echo "Falhas: $failed"
echo "Total processados: ${#repos[@]}"
18 changes: 18 additions & 0 deletions templates/auto-add-to-project.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: Auto-add issues to Project

on:
issues:
types: [opened, reopened, transferred]

permissions:
contents: read

jobs:
add-to-project:
runs-on: ubuntu-latest
steps:
- name: Add issue to Work - Product & Engineering (nfe/5)
uses: actions/add-to-project@v1
with:
project-url: https://github.com/orgs/nfe/projects/5
github-token: ${{ secrets.WORK_PROJECT_TOKEN }}
Loading