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
50 changes: 44 additions & 6 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -31,38 +37,68 @@ 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
uses: actions/checkout@v4

- 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 }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata
if: steps.version.outputs.skip != 'true'
id: meta
uses: docker/metadata-action@v5
with:
Expand All @@ -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: .
Expand All @@ -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 }}" \
Expand Down
102 changes: 102 additions & 0 deletions .github/workflows/release-pr-checks.yml
Original file line number Diff line number Diff line change
@@ -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'
18 changes: 4 additions & 14 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion .github/workflows/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Loading