From e614610d479439bbd58ca058aa2010e9e1a45ba6 Mon Sep 17 00:00:00 2001 From: Grayson Adams <51373669+GraysonCAdams@users.noreply.github.com> Date: Sun, 1 Mar 2026 01:27:03 -0600 Subject: [PATCH 1/2] fix: auto-trigger CI, CodeQL, and Docker publish for release-please PRs release-please uses GITHUB_TOKEN which doesn't trigger other workflows. This caused release PRs to have no status checks, blocking merges when branch protection requires them. Fix with three changes: - Add release-pr-checks.yml using pull_request_target (which IS triggered by GITHUB_TOKEN) to run CI and CodeQL on release-please PRs - Add workflow_run trigger to docker-publish.yml so Docker images are automatically published when release-please creates a release - Clean up stale TODO comments in release.yml --- .github/workflows/docker-publish.yml | 50 ++++++++++-- .github/workflows/release-pr-checks.yml | 102 ++++++++++++++++++++++++ .github/workflows/release.yml | 18 +---- 3 files changed, 150 insertions(+), 20 deletions(-) create mode 100644 .github/workflows/release-pr-checks.yml diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 1f6eeae..6ebeaed 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -1,13 +1,19 @@ name: Build & Publish Docker Image on: - # Automatic trigger when a release is published. - # NOTE: This only works when releases are created with a GitHub App token - # (not GITHUB_TOKEN). Until the App is configured, use workflow_dispatch. + # Automatic trigger when a release is published (works with GitHub App tokens). release: types: [published] - # Manual fallback for publishing when the release event doesn't fire. + # Automatic trigger after Release workflow completes (works with GITHUB_TOKEN). + # release-please creates releases using GITHUB_TOKEN which doesn't fire the + # release event above. workflow_run IS triggered by GITHUB_TOKEN completions. + workflow_run: + workflows: [Release] + types: [completed] + branches: [main] + + # Manual fallback. workflow_dispatch: inputs: version: @@ -31,6 +37,13 @@ jobs: build-and-push: runs-on: ubuntu-latest timeout-minutes: 20 + # For workflow_run: only run if the Release workflow succeeded AND a new + # release was actually created (check for a tag matching the latest release). + # For release/workflow_dispatch: always run. + if: | + github.event_name == 'release' || + github.event_name == 'workflow_dispatch' || + (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') steps: - name: Checkout @@ -38,24 +51,46 @@ jobs: - name: Determine version id: version + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then echo "version=${{ inputs.version }}" >> "$GITHUB_OUTPUT" - else - # Extract version from release tag (e.g. scrolly-v1.2.0 -> 1.2.0, v1.2.0 -> 1.2.0) + elif [ "${{ github.event_name }}" = "release" ]; then TAG="${{ github.event.release.tag_name }}" VERSION="${TAG#scrolly-v}" VERSION="${VERSION#v}" echo "version=$VERSION" >> "$GITHUB_OUTPUT" + else + # workflow_run trigger: look up the latest release + RELEASE=$(gh release view --json tagName --jq '.tagName' 2>/dev/null || echo "") + if [ -z "$RELEASE" ]; then + echo "No release found — skipping" + echo "skip=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + VERSION="${RELEASE#scrolly-v}" + VERSION="${VERSION#v}" + # Check if this version is already published + EXISTING=$(gh api "/orgs/312-dev/packages/container/scrolly/versions" --jq ".[].metadata.container.tags[]" 2>/dev/null | grep -x "$VERSION" || true) + if [ -n "$EXISTING" ]; then + echo "Version $VERSION already published — skipping" + echo "skip=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + echo "version=$VERSION" >> "$GITHUB_OUTPUT" fi - name: Set up QEMU + if: steps.version.outputs.skip != 'true' uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx + if: steps.version.outputs.skip != 'true' uses: docker/setup-buildx-action@v3 - name: Log in to GHCR + if: steps.version.outputs.skip != 'true' uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} @@ -63,6 +98,7 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Extract metadata + if: steps.version.outputs.skip != 'true' id: meta uses: docker/metadata-action@v5 with: @@ -72,6 +108,7 @@ jobs: type=raw,value=latest - name: Build and push + if: steps.version.outputs.skip != 'true' uses: docker/build-push-action@v6 with: context: . @@ -85,6 +122,7 @@ jobs: cache-to: type=gha,mode=max - name: Set package visibility to public + if: steps.version.outputs.skip != 'true' run: | curl -sf -X PATCH \ -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ diff --git a/.github/workflows/release-pr-checks.yml b/.github/workflows/release-pr-checks.yml new file mode 100644 index 0000000..dfa85ca --- /dev/null +++ b/.github/workflows/release-pr-checks.yml @@ -0,0 +1,102 @@ +# Runs CI and CodeQL on release-please PRs. +# +# Why this exists: release-please creates PRs using GITHUB_TOKEN, which does +# NOT trigger other workflows (pull_request events). pull_request_target IS +# triggered by GITHUB_TOKEN events because it runs in the base branch context. +# Without this, release PRs have no status checks and can't be merged when +# branch protection requires them. +# +# Security: restricted to release-please branches only. The checkout uses the +# PR's HEAD SHA, which is safe because release-please PRs come from within the +# same repository (not forks) and only modify version/changelog files. + +name: Release PR Checks + +on: + pull_request_target: + branches: [main] + types: [opened, synchronize, reopened] + +permissions: read-all + +jobs: + # Gate: only run for release-please PRs + should-run: + if: startsWith(github.head_ref, 'release-please--') + runs-on: ubuntu-latest + steps: + - run: echo "Running CI for release-please PR" + + lint-and-check: + needs: [should-run] + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout PR code + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 24 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Lint (ratcheted) + run: npm run lint:ci + + - name: Format check + run: npm run format:check + + - name: Type check + run: npm run type-check + + - name: Tests with coverage + run: npm run test:coverage + + - name: Production build + run: npm run build + + ci: + runs-on: ubuntu-latest + if: always() + needs: [lint-and-check] + steps: + - name: Check CI status + run: | + if [[ "${{ needs.lint-and-check.result }}" == "failure" || "${{ needs.lint-and-check.result }}" == "cancelled" ]]; then + echo "CI failed" + exit 1 + fi + echo "CI passed" + + codeql: + needs: [should-run] + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + security-events: write + steps: + - name: Checkout PR code + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: javascript-typescript + config-file: .github/codeql/codeql-config.yml + queries: security-extended + + - name: Autobuild + uses: github/codeql-action/autobuild@v3 + + - name: Perform CodeQL analysis + uses: github/codeql-action/analyze@v3 + with: + category: '/language:javascript-typescript' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 04fb3bc..d46746a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,20 +4,10 @@ on: push: branches: [main] -# TODO: Replace GITHUB_TOKEN with a GitHub App token to properly trigger -# CI/Security workflows on release-please PRs and docker-publish on releases. -# See: https://github.com/actions/create-github-app-token -# -# Once configured, add to this workflow: -# - name: Generate GitHub App token -# id: app-token -# uses: actions/create-github-app-token@v2 -# with: -# app-id: ${{ vars.RELEASE_APP_ID }} -# private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} -# -# Then pass `token: ${{ steps.app-token.outputs.token }}` to release-please. -# This eliminates all workarounds for GITHUB_TOKEN event limitations. +# Note: release-please uses GITHUB_TOKEN, which doesn't trigger other workflows. +# CI/CodeQL checks on release PRs are handled by release-pr-checks.yml using +# pull_request_target. Docker publishing is handled by docker-publish.yml using +# workflow_run on this workflow (to detect when a release is created). permissions: contents: write From b11bbe09c9aedb45424c8e5636696a8101f5a66c Mon Sep 17 00:00:00 2001 From: Grayson Adams <51373669+GraysonCAdams@users.noreply.github.com> Date: Sun, 1 Mar 2026 01:31:25 -0600 Subject: [PATCH 2/2] fix: always run CodeQL on PRs to satisfy branch protection Repository rules require CodeQL results for all PRs, even when only workflow YAML files are changed. Previously CodeQL was skipped when the paths filter found no security-relevant files, which blocked merging workflow-only PRs. --- .github/workflows/security.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 90e3eab..4c81d93 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -57,9 +57,12 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 15 needs: [changes] + # Always run on PRs (branch protection requires CodeQL results even for + # non-code changes like workflow YAML). Skip only on push/schedule when + # no security-relevant files changed. if: | !inputs.skip_codeql && - (needs.changes.outputs.security_relevant == 'true' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') + (github.event_name == 'pull_request' || needs.changes.outputs.security_relevant == 'true' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') permissions: security-events: write steps: