diff --git a/.github/workflows/notify-umbrella.yml b/.github/workflows/notify-umbrella.yml index b4a7f54..6458b14 100644 --- a/.github/workflows/notify-umbrella.yml +++ b/.github/workflows/notify-umbrella.yml @@ -1,14 +1,16 @@ -# notify-umbrella — tell the umbrella a repo's docs changed, so it rebuilds within minutes -# instead of waiting for its daily cron (MIP-0070 §5.5). Modelled on h0ffmann/nix-config's -# profile-ping.yml reusable workflow — a plain repository_dispatch, no checkout needed on either -# side. +# notify-umbrella — tell the umbrella every push to main (`submodule-updated`), so +# pointer-sync.yml can move pointers on event instead of its daily cron, and that a repo's docs +# changed too (`submodule-docs-updated`) when the push touches README.md or docs/**, so +# docs.yml rebuilds within minutes (MIP-0070 §5.5, MIP-0076 §5.5 step 1). Modelled on +# h0ffmann/nix-config's profile-ping.yml reusable workflow — a plain repository_dispatch, no +# checkout needed on either side: the docs check reads the compare API instead of git. # -# A consumer adds about three lines: +# A consumer adds about three lines, no `paths:` filter — this workflow decides for itself which +# event types a given push earns: # # on: # push: # branches: [main] -# paths: [README.md, docs/**] # jobs: # notify: # uses: marola-dev/marola-devkit/.github/workflows/notify-umbrella.yml@v0.5.0 @@ -28,9 +30,9 @@ on: type: string default: marola-dev/marola event-type: - description: "repository_dispatch event_type the umbrella's docs workflow listens for." + description: "Deprecated, ignored (MIP-0076): every call now sends submodule-updated, plus submodule-docs-updated on a docs change. Kept declared so a caller still passing it doesn't fail GitHub's reusable-workflow input check." type: string - default: submodule-docs-updated + default: "" runner: description: "Runner label for the dispatch job — a reusable workflow's runs-on resolves in the caller's repository." type: string @@ -47,26 +49,62 @@ jobs: runs-on: ${{ inputs.runner }} timeout-minutes: 2 steps: - - name: repository_dispatch ${{ inputs.event-type }} -> ${{ inputs.umbrella }} + # A reusable workflow can only keep or lower the caller's permissions, and every caller sets + # permissions: {} — so this reads the compare API instead of checking the repo out. Even a + # token with no declared scope can read a public repo's API. + - name: Did this push touch README.md or docs/**? + id: docs + env: + TOKEN: ${{ github.token }} + BEFORE: ${{ github.event.before }} + SHA: ${{ github.sha }} + REPO: ${{ github.repository }} + run: | + set -euo pipefail + changed=false + if [ -z "${BEFORE:-}" ] || [ "$BEFORE" = "0000000000000000000000000000000000000000" ]; then + changed=true # first push on the branch: no prior ref to compare against + else + resp="$RUNNER_TEMP/notify-umbrella-compare.json" + code="$(curl -sS -o "$resp" -w '%{http_code}' \ + -H "Authorization: Bearer $TOKEN" -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" \ + "https://api.github.com/repos/$REPO/compare/$BEFORE...$SHA")" + if [ "$code" != 200 ]; then + changed=true # before unknown to the API (e.g. a force-push): take the conservative path + elif [ "$(jq '(.files // []) | length' "$resp")" -ge 300 ]; then + changed=true # the compare API caps its file list at 300: the list may be truncated + elif jq -e '(.files // []) | any(.[]; .filename == "README.md" or (.filename | startswith("docs/")))' "$resp" >/dev/null; then + changed=true + fi + fi + echo "changed=$changed" >>"$GITHUB_OUTPUT" + + - name: repository_dispatch -> ${{ inputs.umbrella }} env: TOKEN: ${{ secrets.token }} UMBRELLA: ${{ inputs.umbrella }} - EVENT_TYPE: ${{ inputs.event-type }} REPO: ${{ github.repository }} SHA: ${{ github.sha }} + DOCS_CHANGED: ${{ steps.docs.outputs.changed }} run: | set -euo pipefail if [ -z "$TOKEN" ]; then echo "::notice::no umbrella-dispatch token set in $REPO — skipping; the umbrella's daily cron still picks this up" exit 0 fi - body="$(jq -nc --arg repo "$REPO" --arg sha "$SHA" --arg event "$EVENT_TYPE" \ - '{event_type: $event, client_payload: {repo: $repo, sha: $sha}}')" - code="$(curl -sS -o "$RUNNER_TEMP/notify-umbrella-response.txt" -w '%{http_code}' -X POST \ - -H "Authorization: Bearer $TOKEN" -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" \ - "https://api.github.com/repos/$UMBRELLA/dispatches" -d "$body")" - if [ "$code" != 204 ]; then - echo "::warning::dispatch to $UMBRELLA answered HTTP $code: $(head -c 300 "$RUNNER_TEMP/notify-umbrella-response.txt")" - exit 0 + events=(submodule-updated) + if [ "$DOCS_CHANGED" = true ]; then + events+=(submodule-docs-updated) fi - echo "dispatched $EVENT_TYPE for $REPO@$SHA to $UMBRELLA" + for event in "${events[@]}"; do + body="$(jq -nc --arg repo "$REPO" --arg sha "$SHA" --arg event "$event" \ + '{event_type: $event, client_payload: {repo: $repo, sha: $sha}}')" + code="$(curl -sS -o "$RUNNER_TEMP/notify-umbrella-response.txt" -w '%{http_code}' -X POST \ + -H "Authorization: Bearer $TOKEN" -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" \ + "https://api.github.com/repos/$UMBRELLA/dispatches" -d "$body")" + if [ "$code" != 204 ]; then + echo "::warning::dispatch of $event to $UMBRELLA answered HTTP $code: $(head -c 300 "$RUNNER_TEMP/notify-umbrella-response.txt")" + else + echo "dispatched $event for $REPO@$SHA to $UMBRELLA" + fi + done diff --git a/docs/4-reference_workflows.md b/docs/4-reference_workflows.md index a58dc8f..37a6a40 100644 --- a/docs/4-reference_workflows.md +++ b/docs/4-reference_workflows.md @@ -114,16 +114,20 @@ No secrets. ## notify-umbrella -Tells the umbrella a repo's docs changed via `repository_dispatch`, so it rebuilds within minutes -instead of at its next daily cron (MIP-0070 §5.5). Modelled on h0ffmann/nix-config's -`profile-ping.yml` — no checkout on either side, ~3 lines to call. +Tells the umbrella every push to `main` via `repository_dispatch` (`submodule-updated`), so +pointer-sync.yml can move pointers on the event instead of waiting for its daily cron, and that a +repo's docs changed too (`submodule-docs-updated`) when the push touched `README.md` or `docs/**`, +so docs.yml rebuilds within minutes (MIP-0070 §5.5, MIP-0076 §5.5 step 1). Modelled on +h0ffmann/nix-config's `profile-ping.yml` — no checkout on either side. The caller needs no +`paths:` filter: this workflow reads the compare API itself to decide which event types a given +push earns (every caller sets `permissions: {}`, which a reusable workflow can only keep or +lower, so it can't check its own repo out). ```yaml name: notify umbrella on: push: branches: [main] - paths: [README.md, docs/**] jobs: notify: uses: marola-dev/marola-devkit/.github/workflows/notify-umbrella.yml@v0.5.0 @@ -134,7 +138,7 @@ jobs: | Input | Default | Notes | |---|---|---| | `umbrella` | `marola-dev/marola` | MAROLA_UMBRELLA, MIP-0070 §5.6 | -| `event-type` | `submodule-docs-updated` | what the umbrella's docs workflow listens for | +| `event-type` | `""` | deprecated, ignored (MIP-0076) — kept so a caller still passing it doesn't fail GitHub's input check | | `runner` | `ubuntu-latest` | resolved in the *caller's* repo | **Secret** `token` (optional): every repo passes the org secret `MAROLA_CROSS_REPO_PAT`, a @@ -142,6 +146,10 @@ fine-grained PAT with Contents: read & write on the umbrella (`repository_dispat `GITHUB_TOKEN` cannot reach another repo). Unset is a notice, not a failure — the umbrella's daily cron still catches the change. +A zero `github.event.before` (a branch's first push), a compare API call that doesn't answer 200 +(e.g. `before` unknown to it after a force-push), or a truncated compare (the API caps its file +list at 300) all count as a docs change too, so both events go rather than silently dropping one. + ## labels-sync Reconciles the caller repo's labels against `scripts/issues.sh labels sync`'s own default manifest