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
3 changes: 3 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@ version: 2
updates:
- package-ecosystem: npm
directory: /
target-branch: develop
schedule:
interval: weekly
groups:
npm:
patterns: ["*"]
- package-ecosystem: github-actions
directory: /
target-branch: develop
schedule:
interval: weekly
- package-ecosystem: docker
directory: /
target-branch: develop
schedule:
interval: weekly
1 change: 1 addition & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: CI

on:
workflow_dispatch:
push:
branches: [develop, master]
pull_request:
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: CodeQL

on:
workflow_dispatch:
push:
branches: [develop, master]
pull_request:
Expand Down
31 changes: 7 additions & 24 deletions .github/workflows/dependabot-auto-merge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,51 +2,34 @@ name: Dependabot Auto Merge

on:
pull_request_target:
types: [opened, synchronize, reopened, ready_for_review, closed]
types: [opened, synchronize, reopened, ready_for_review]

permissions:
contents: write
pull-requests: write

jobs:
enable-automerge:
if: >
github.event.action != 'closed' &&
if: >-
github.event.pull_request.base.ref == 'develop' &&
github.event.pull_request.user.login == 'dependabot[bot]' &&
startsWith(github.event.pull_request.head.ref, 'dependabot/') &&
github.event.pull_request.head.repo.full_name == github.repository &&
github.event.pull_request.draft == false
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Fetch Dependabot metadata
- name: Fetch Dependabot Metadata
id: metadata
uses: dependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98 # v3.1.0
with:
github-token: ${{ secrets.GITHUB_TOKEN }}

- name: Enable auto-merge
if: >
- name: Enable Auto-Merge
if: >-
steps.metadata.outputs.update-type == 'version-update:semver-patch' ||
steps.metadata.outputs.update-type == 'version-update:semver-minor'
run: gh pr merge --auto --squash "$PR_URL"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_URL: ${{ github.event.pull_request.html_url }}

publish-after-merged-dependabot:
if: >
github.event.action == 'closed' &&
github.event.pull_request.merged == true &&
github.event.pull_request.base.ref == 'master' &&
github.event.pull_request.user.login == 'dependabot[bot]' &&
startsWith(github.event.pull_request.head.ref, 'dependabot/') &&
github.event.pull_request.head.repo.full_name == github.repository
permissions:
actions: write
contents: read
runs-on: ubuntu-latest
steps:
- name: Dispatch publication from the merged master revision
run: gh workflow run publish.yaml --repo "$GITHUB_REPOSITORY" --ref master
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
96 changes: 84 additions & 12 deletions .github/workflows/publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,93 @@ permissions:
contents: read

concurrency:
group: publish-${{ github.event.workflow_run.head_branch }}
group: publish-${{ github.event_name == 'workflow_dispatch' && github.ref_name || github.event.workflow_run.head_branch }}
cancel-in-progress: false

jobs:
container:
name: Publish container
if: >
verify:
name: Verify Release Checks
if: >-
github.event_name == 'workflow_dispatch' ||
(github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push')
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
actions: write
checks: read
contents: read
outputs:
sha: ${{ steps.release.outputs.sha }}
branch: ${{ steps.release.outputs.branch }}
steps:
- name: Verify Exact Release Commit
id: release
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
RELEASE_SHA: ${{ github.event_name == 'workflow_dispatch' && github.sha || github.event.workflow_run.head_sha }}
RELEASE_BRANCH: ${{ github.event_name == 'workflow_dispatch' && github.ref_name || github.event.workflow_run.head_branch }}
MANUAL_RELEASE: ${{ github.event_name == 'workflow_dispatch' }}
run: |
case "$RELEASE_BRANCH" in
develop|master) ;;
*) echo "Unsupported release branch: $RELEASE_BRANCH" >&2; exit 1 ;;
esac
current_sha="$(gh api "repos/$GH_REPO/commits/$RELEASE_BRANCH" --jq .sha)"
if [ "$current_sha" != "$RELEASE_SHA" ]; then
echo "Release commit is no longer the branch head." >&2
exit 1
fi

# Manual releases rerun all checks, including the current vulnerability data.
check_after="$(date -u -d '24 hours ago' +%Y-%m-%dT%H:%M:%SZ)"
if [ "$MANUAL_RELEASE" = true ]; then
check_after="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
for workflow in ci.yaml snyk.yml codeql.yml; do
gh workflow run "$workflow" --ref "$RELEASE_BRANCH"
done
fi

if [ "$RELEASE_BRANCH" = master ]; then
required='["Test (Node 26)","Lint and audit","Helm lint and render","Build container","Open Source and Container","Analyze JavaScript"]'
else
required='["Test (Node 22)","Test (Node 24)","Lint and audit","Helm lint and render","Build container","Open Source","Analyze JavaScript"]'
fi

for attempt in {1..60}; do
checks="$(gh api --paginate --slurp "repos/$GH_REPO/commits/$RELEASE_SHA/check-runs?per_page=100")"
missing="$(jq -r --argjson required "$required" --arg after "$check_after" '
[.[].check_runs[] | select(.app.slug == "github-actions")] as $runs
| $required[] as $name
| ([$runs[] | select(.name == $name)] | max_by(.id)) as $latest
| select($latest == null or $latest.status != "completed"
or $latest.conclusion != "success" or $latest.started_at < $after)
| $name
' <<< "$checks")"
if [ -z "$missing" ]; then
current_sha="$(gh api "repos/$GH_REPO/commits/$RELEASE_BRANCH" --jq .sha)"
if [ "$current_sha" != "$RELEASE_SHA" ]; then
echo "Branch advanced while checks were running; release blocked." >&2
exit 1
fi
echo "sha=$RELEASE_SHA" >> "$GITHUB_OUTPUT"
echo "branch=$RELEASE_BRANCH" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "Waiting for successful checks on $RELEASE_SHA: $missing"
sleep 10
done
echo "Release blocked by missing, stale, skipped, or failed checks: $missing" >&2
exit 1

container:
name: Publish container
needs: verify
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.event_name == 'workflow_dispatch' && github.sha || github.event.workflow_run.head_sha }}
ref: ${{ needs.verify.outputs.sha }}
- name: Set up QEMU
uses: docker/setup-qemu-action@99012661954931238ded8c8b007157a8430204e1 # v4
- name: Set up Docker Buildx
Expand All @@ -44,9 +117,9 @@ jobs:
images: jonfairbanks/docker-node-app
tags: |
type=sha
type=raw,value=develop,enable=${{ (github.event_name == 'workflow_dispatch' && github.ref_name == 'develop') || github.event.workflow_run.head_branch == 'develop' }}
type=raw,value=latest,enable=${{ (github.event_name == 'workflow_dispatch' && github.ref_name == 'master') || github.event.workflow_run.head_branch == 'master' }}
type=raw,value=${{ steps.package.outputs.version }},enable=${{ (github.event_name == 'workflow_dispatch' && github.ref_name == 'master') || github.event.workflow_run.head_branch == 'master' }}
type=raw,value=develop,enable=${{ needs.verify.outputs.branch == 'develop' }}
type=raw,value=latest,enable=${{ needs.verify.outputs.branch == 'master' }}
type=raw,value=${{ steps.package.outputs.version }},enable=${{ needs.verify.outputs.branch == 'master' }}
- name: Build and push
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7
with:
Expand All @@ -61,17 +134,16 @@ jobs:

helm:
name: Publish Helm chart
if: >
(github.event_name == 'workflow_dispatch' && github.ref_name == 'master') ||
(github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push' && github.event.workflow_run.head_branch == 'master')
needs: verify
if: needs.verify.outputs.branch == 'master'
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.event_name == 'workflow_dispatch' && github.sha || github.event.workflow_run.head_sha }}
ref: ${{ needs.verify.outputs.sha }}
fetch-depth: 2
- name: Set up Helm
uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5.0.1
Expand Down
20 changes: 7 additions & 13 deletions .github/workflows/snyk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,36 +19,32 @@ permissions:
jobs:
open-source:
name: Open Source and Container
if: >-
github.actor != 'dependabot[bot]' &&
(github.event_name != 'pull_request' ||
github.event.pull_request.head.repo.full_name == github.repository)
runs-on: ubuntu-latest
timeout-minutes: 15
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}

steps:
- name: Check Snyk configuration
if: env.SNYK_TOKEN == ''
run: echo "::warning::SNYK_TOKEN is not configured; skipping Snyk scan."
- name: Require Snyk Token
run: |
if [ -z "$SNYK_TOKEN" ]; then
echo "::error::SNYK_TOKEN is required. Configure it in Actions and Dependabot secrets."
exit 1
fi

- name: Checkout
if: env.SNYK_TOKEN != ''
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Use Node.js 24.x
if: env.SNYK_TOKEN != ''
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 24.x
cache: npm

- name: Install dependencies
if: env.SNYK_TOKEN != ''
run: npm ci

- name: Scan dependencies
if: env.SNYK_TOKEN != ''
uses: snyk/actions/node@9adf32b1121593767fc3c057af55b55db032dc04 # v1.0.0
with:
args: >-
Expand All @@ -57,11 +53,9 @@ jobs:
--severity-threshold=high

- name: Build production image
if: env.SNYK_TOKEN != ''
run: docker build --target production --tag docker-node-app:snyk .

- name: Scan production image
if: env.SNYK_TOKEN != ''
run: |
# snyk/actions/docker invokes the legacy `snyk test --docker` path.
# Use Snyk's current container command with the current immutable
Expand Down
Loading