From ab59c233f379be58e1be36f2f12e49d533f3f667 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Canouil?= <8896044+mcanouil@users.noreply.github.com> Date: Sat, 17 Oct 2026 19:14:28 +0200 Subject: [PATCH 1/3] ci: release and update with release-actions and quarto-workflows --- .github/workflows/deploy.yml | 11 --- .../workflows/quarto-extensions-updater.yml | 64 +++----------- .github/workflows/release.yml | 85 +++++-------------- 3 files changed, 32 insertions(+), 128 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 433f74c..f952549 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -33,20 +33,9 @@ jobs: name: Build - Documentation website steps: - - name: Setup Git user - uses: mcanouil/quarto-workflows/.github/actions/setup-git-user@main - id: setup-git-user - with: - # Pull requests from forks cannot read the app key, so fall back to - # the workflow token there; nothing is pushed from this workflow. - gh-app-id: ${{ github.event_name != 'pull_request' && vars.APP_ID || '' }} - app-key: ${{ secrets.APP_KEY }} - gh-token: ${{ secrets.GITHUB_TOKEN }} - - name: Checkout repository uses: actions/checkout@v7 with: - token: ${{ steps.setup-git-user.outputs.token }} # Publish the default branch whatever the trigger: a release runs # against its tag, which predates the quarto check transcripts that # land on the default branch afterwards. Pull requests keep the diff --git a/.github/workflows/quarto-extensions-updater.yml b/.github/workflows/quarto-extensions-updater.yml index 6a63625..4eff844 100644 --- a/.github/workflows/quarto-extensions-updater.yml +++ b/.github/workflows/quarto-extensions-updater.yml @@ -9,60 +9,18 @@ on: schedule: - cron: 00 12 1 * * -concurrency: - group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref_name }} - cancel-in-progress: true +permissions: + contents: read jobs: - quarto-extensions-updates: - runs-on: ubuntu-latest + updates: + uses: mcanouil/quarto-workflows/.github/workflows/quarto-extensions-updates.yml@v2 + secrets: inherit permissions: - contents: read + contents: write pull-requests: write - steps: - - uses: actions/checkout@v7 - - - name: Create GitHub App token - uses: actions/create-github-app-token@v3 - id: app-token - with: - client-id: ${{ vars.APP_ID }} - private-key: ${{ secrets.APP_KEY }} - - - name: Get GitHub App User ID - id: get-user-id - if: ${{ steps.app-token.outputs.app-slug != '' }} - env: - GH_TOKEN: ${{ steps.app-token.outputs.token }} - APP_SLUG: ${{ steps.app-token.outputs.app-slug }} - run: | - echo "user-id=$(gh api "/users/${APP_SLUG}[bot]" --jq .id)" >> "${GITHUB_OUTPUT}" - - - name: Configure Environment for Git User - env: - APP_SLUG: ${{ steps.app-token.outputs.app-slug }} - USER_ID: ${{ steps.get-user-id.outputs.user-id }} - run: | - if [ -z "${APP_SLUG}" ]; then - USER_NAME="github-actions[bot]" - USER_EMAIL="41898282+github-actions[bot]@users.noreply.github.com" - else - USER_NAME="${APP_SLUG}[bot]" - USER_EMAIL="${USER_ID}+${APP_SLUG}[bot]@users.noreply.github.com" - fi - git config --global user.name "${USER_NAME}" - git config --global user.email "${USER_EMAIL}" - - - name: Setup Quarto - uses: quarto-dev/quarto-actions/setup@v2 - with: - version: "pre-release" - - - name: Update Quarto extensions - uses: mcanouil/quarto-extensions-updater@v2.2.1 - with: - github-token: ${{ steps.app-token.outputs.token }} - pr-labels: "Type: Dependencies :arrow_up:" - group-updates: true - scan-directories: | - docs + with: + gh-app-id: ${{ vars.APP_ID }} + quarto-version: pre-release + scan-directories: | + docs diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fc4bc22..cbdd297 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,44 +19,36 @@ jobs: timeout-minutes: 15 steps: - - name: Refuse to release from anything but the default branch - if: ${{ github.ref_name != github.event.repository.default_branch }} - env: - DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} - REF_NAME: ${{ github.ref_name }} - shell: bash - run: | - echo "::error::Releases are cut from ${DEFAULT_BRANCH}, not ${REF_NAME}." - exit 1 - # The App only, with no fallback token: the default token cannot pass the # pull request rule of the default branch, and a release it creates # triggers neither the image build nor the documentation deploy. - - name: Setup Git User - uses: mcanouil/quarto-workflows/.github/actions/setup-git-user@v2 - id: setup-git-user + - name: Get the App token + id: token + uses: mcanouil/release-actions/token@v1 with: - gh-app-id: ${{ vars.APP_ID }} + app-id: ${{ vars.APP_ID }} app-key: ${{ secrets.APP_KEY }} + require-app: true - name: Checkout repository uses: actions/checkout@v7 with: - token: ${{ steps.setup-git-user.outputs.token }} + token: ${{ steps.token.outputs.token }} # The whole history and every tag, since the same-day suffix is # resolved from the tags already cut. fetch-depth: 0 # The first release of a day is the bare date; every further one carries - # the count of releases cut that day, such as `2026.10.16-2`. An empty - # Unreleased section in CHANGELOG.md stops the release here. + # the count of releases cut that day, such as `2026.10.16-2`, and is + # titled with it, such as `2026-10-16 (2)`. An empty Unreleased section in + # CHANGELOG.md stops the release here. - name: Bump the version id: bump uses: mcanouil/release-actions/bump@v1 with: bump: calver version-source: tag - token: ${{ steps.setup-git-user.outputs.token }} + token: ${{ steps.token.outputs.token }} targets: | - file: CITATION.cff pattern: '^version: {version}$' @@ -66,20 +58,12 @@ jobs: # The App is exempt from the pull request rule of the default branch, and # [skip ci] keeps CI from running again on a tree it has checked. - name: Push the version - id: push - env: - VERSION: ${{ steps.bump.outputs.version }} - FILES: ${{ steps.bump.outputs.files }} - shell: bash - run: | - set -euo pipefail - printf '%s\n' "${FILES}" | git add --pathspec-from-file=- - git commit -m "ci: release ${VERSION} [skip ci]" - if ! git push origin "HEAD:refs/heads/${GITHUB_REF_NAME}"; then - echo "::error::The App could not push the bump to ${GITHUB_REF_NAME}. If the push output says the branch moved, dispatch this workflow again; otherwise add the App to the bypass list of the branch ruleset first." - exit 1 - fi - echo "sha=$(git rev-parse HEAD)" >> "${GITHUB_OUTPUT}" + id: land + uses: mcanouil/release-actions/land@v1 + with: + mode: push + files: ${{ steps.bump.outputs.files }} + version: ${{ steps.bump.outputs.version }} - name: Compose the release body id: notes @@ -87,25 +71,9 @@ jobs: with: notes-file: ${{ steps.bump.outputs.notes-file }} tag: ${{ steps.bump.outputs.version }} - target: ${{ steps.push.outputs.sha }} + target: ${{ steps.land.outputs.sha }} generate-notes: true - token: ${{ steps.setup-git-user.outputs.token }} - - # A further release of a day is titled with its count, such as - # `2026-10-16 (2)`. - - name: Resolve the release title - id: title - env: - VERSION: ${{ steps.bump.outputs.version }} - DATE: ${{ steps.bump.outputs.date }} - shell: bash - run: | - set -euo pipefail - TITLE="${DATE}" - if [[ "${VERSION}" == *-* ]]; then - TITLE="${DATE} (${VERSION##*-})" - fi - echo "title=${TITLE}" >> "${GITHUB_OUTPUT}" + token: ${{ steps.token.outputs.token }} # Created as the App, so that the published release starts the image # build and the documentation deploy. @@ -114,18 +82,7 @@ jobs: with: version: ${{ steps.bump.outputs.version }} body-file: ${{ steps.notes.outputs.body-file }} - title: ${{ steps.title.outputs.title }} - target: ${{ steps.push.outputs.sha }} + title: "{date}{count}" + target: ${{ steps.land.outputs.sha }} latest: true - token: ${{ steps.setup-git-user.outputs.token }} - - # A new dispatch would stop on the emptied Unreleased section, so the - # pushed bump has to go first. - - name: Explain the recovery - if: ${{ failure() && steps.push.outputs.sha != '' }} - env: - VERSION: ${{ steps.bump.outputs.version }} - SHA: ${{ steps.push.outputs.sha }} - shell: bash - run: | - echo "::error::${GITHUB_REF_NAME} holds the bump ${SHA} for ${VERSION}, but the release failed. Create release ${VERSION} on ${SHA} by hand, or revert that commit with a pull request and dispatch this workflow again." + token: ${{ steps.token.outputs.token }} From f2d7e04caa9ff6498c345bfc9b9d87ff46a36551 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Canouil?= <8896044+mcanouil@users.noreply.github.com> Date: Sat, 17 Oct 2026 19:23:26 +0200 Subject: [PATCH 2/3] ci: land the quarto check transcripts with release-actions --- .github/workflows/devcontainer.yml | 78 ++++++++++-------------------- 1 file changed, 26 insertions(+), 52 deletions(-) diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml index 8968bd1..ef7c40a 100644 --- a/.github/workflows/devcontainer.yml +++ b/.github/workflows/devcontainer.yml @@ -82,9 +82,9 @@ jobs: id: image_version run: | if [[ "${GITHUB_REF_TYPE}" == "tag" ]]; then - echo "VERSION=${GITHUB_REF_NAME}" >> ${GITHUB_OUTPUT} + echo "VERSION=${GITHUB_REF_NAME}" >> "${GITHUB_OUTPUT}" else - echo "VERSION=$(git rev-parse --short HEAD)" >> ${GITHUB_OUTPUT} + echo "VERSION=$(git rev-parse --short HEAD)" >> "${GITHUB_OUTPUT}" fi - name: Build and run Dev Container on release @@ -135,7 +135,7 @@ jobs: echo '```' echo "" echo "" - ) >>${GITHUB_STEP_SUMMARY} + ) >> "${GITHUB_STEP_SUMMARY}" - name: Save quarto check output env: @@ -235,35 +235,20 @@ jobs: TARGET_FILE: docs/reference/quarto-check.qmd steps: - - name: Checkout repository - uses: actions/checkout@v7 + - name: Get the App token + id: token + uses: mcanouil/release-actions/token@v1 with: - persist-credentials: true + app-id: ${{ vars.APP_ID }} + app-key: ${{ secrets.APP_KEY }} - - name: Update branch - run: | - git fetch --all - git checkout main - git pull origin main - - - name: Create GitHub App token - uses: actions/create-github-app-token@v3 - id: app-token + # A release runs on its tag, so check out the default branch, where the + # transcripts land. + - name: Checkout repository + uses: actions/checkout@v7 with: - client-id: ${{ vars.APP_ID }} - private-key: ${{ secrets.APP_KEY }} - - - name: Get GitHub App User ID - id: get-user-id - env: - GH_TOKEN: ${{ steps.app-token.outputs.token }} - run: | - echo "user-id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT" - - - name: Git Config - run: | - git config --global user.name '${{ steps.app-token.outputs.app-slug }}[bot]' - git config --global user.email '${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com' + ref: ${{ github.event.repository.default_branch }} + token: ${{ steps.token.outputs.token }} - name: Download quarto check outputs uses: actions/download-artifact@v8 @@ -353,28 +338,17 @@ jobs: update_docs_section "release" "/tmp/quarto-check-outputs/quarto-check-release-amd64/release.txt" "${BUILD_URL}" update_docs_section "prerelease" "/tmp/quarto-check-outputs/quarto-check-prerelease-amd64/prerelease.txt" "${BUILD_URL}" - - name: Commit and push changes - env: - GH_TOKEN: ${{ steps.app-token.outputs.token }} - BRANCH: "ci/update-quarto-check-docs" - COMMIT: "ci: update quarto check outputs in documentation" - run: | - if git show-ref --quiet refs/heads/${BRANCH}; then - echo "Branch ${BRANCH} already exists." - git branch -D "${BRANCH}" - git push origin --delete "${BRANCH}" - fi - - git checkout -b "${BRANCH}" - - git add "${TARGET_FILE}" || echo "No changes to add" - git commit -m "${COMMIT}" || echo "No changes to commit" - git push --force origin ${BRANCH} || echo "No changes to push" - - sleep 5 - gh pr create --fill-first --base "main" --head "${BRANCH}" --label "Type: CI/CD :robot:" - sleep 2 - gh pr merge --auto --squash --delete-branch + - name: Land the transcripts + uses: mcanouil/release-actions/land@v1 + with: + mode: pull-request + files: ${{ env.TARGET_FILE }} + base: ${{ github.event.repository.default_branch }} + message: "ci: update quarto check outputs in documentation" + branch: ci/update-quarto-check-docs + wait: false + allow-unchanged: true + token: ${{ steps.token.outputs.token }} list-versions: runs-on: ubuntu-latest @@ -462,7 +436,7 @@ jobs: ALL_TAGS="${BASE_TAGS},${TAGS_WITH_BUILD}" echo "All tags: ${ALL_TAGS}" echo "::endgroup::" - echo "IMAGE_TAG=${ALL_TAGS}" >> ${GITHUB_OUTPUT} + echo "IMAGE_TAG=${ALL_TAGS}" >> "${GITHUB_OUTPUT}" - name: Build and run Dev Container on release uses: devcontainers/ci@v0.3 From 1028e2766a33bafe4cdcc43030e986c73c910083 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Canouil?= <8896044+mcanouil@users.noreply.github.com> Date: Sat, 17 Oct 2026 19:28:40 +0200 Subject: [PATCH 3/3] ci: require the App for the transcript pull request --- .github/workflows/devcontainer.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/devcontainer.yml b/.github/workflows/devcontainer.yml index ef7c40a..6544738 100644 --- a/.github/workflows/devcontainer.yml +++ b/.github/workflows/devcontainer.yml @@ -241,6 +241,7 @@ jobs: with: app-id: ${{ vars.APP_ID }} app-key: ${{ secrets.APP_KEY }} + require-app: true # A release runs on its tag, so check out the default branch, where the # transcripts land.