Skip to content

v1.4.0

v1.4.0 #3

# GitHub Actions workflow: CI on PRs; Publish Docker image and Release on main
name: CI and Publish (Docker + Release)
on:
push:
branches: [main] # Run CI and publish when commits land on main
pull_request:
branches: [main] # Run CI for PRs targeting main
workflow_dispatch: # Allow manual runs from the Actions tab
# Least-privilege permissions required
permissions:
contents: write # needed to create GitHub releases (read is sufficient for checkout)
packages: write # push images to GitHub Container Registry
id-token: write # enable provenance/SLSA attestation by build-push-action
# Shared values
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }} # e.g. owner/repo (may be mixed-case)
jobs:
ci:
name: CI (format, lint, check, gen:licenses, build)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Read tool versions from package.json
id: tool-versions
shell: bash
run: |
echo "::group::Read Node and pnpm versions from package.json"
NODE_ENGINE=$(jq -r '.engines.node // ""' package.json)
if [[ -z "$NODE_ENGINE" || "$NODE_ENGINE" == "null" ]]; then
NODE_VERSION="22"
else
NODE_VERSION=$(echo "$NODE_ENGINE" | grep -oE '[0-9]+' | head -1)
if [[ -z "$NODE_VERSION" ]]; then NODE_VERSION="22"; fi
fi
PM=$(jq -r '.packageManager // ""' package.json)
if [[ -z "$PM" || "$PM" == "null" ]]; then
PNPM_VERSION="10.15.0"
else
PNPM_VERSION=$(echo "$PM" | sed -n 's/^pnpm@\([0-9][^ ]*\).*$/\1/p')
if [[ -z "$PNPM_VERSION" ]]; then PNPM_VERSION="10.15.0"; fi
fi
echo "Node engines: $NODE_ENGINE -> using $NODE_VERSION"
echo "packageManager: $PM -> pnpm $PNPM_VERSION"
echo "node=$NODE_VERSION" >> $GITHUB_OUTPUT
echo "pnpm=$PNPM_VERSION" >> $GITHUB_OUTPUT
echo "::endgroup::"
- name: Enable Corepack and pnpm
shell: bash
run: |
echo "::group::Enable Corepack and prepare pnpm"
corepack enable
corepack prepare pnpm@${{ steps.tool-versions.outputs.pnpm }} --activate
pnpm --version
echo "::endgroup::"
- name: Configure pnpm store for actions cache
shell: bash
run: |
echo "::group::Configure pnpm store path (~/.pnpm-store)"
PNPM_STORE_DIR="$HOME/.pnpm-store"
mkdir -p "$PNPM_STORE_DIR"
pnpm config set store-dir "$PNPM_STORE_DIR"
echo "pnpm store-dir => $(pnpm config get store-dir)"
echo "::endgroup::"
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ steps.tool-versions.outputs.node }}
cache: "pnpm"
cache-dependency-path: pnpm-lock.yaml
- name: Set up pnpm
uses: pnpm/action-setup@v4
with:
version: ${{ steps.tool-versions.outputs.pnpm }}
run_install: false
- name: Install dependencies
shell: bash
run: |
echo "::group::pnpm install"
pnpm --version
pnpm install --frozen-lockfile
echo "::endgroup::"
- name: Run format
shell: bash
run: |
echo "::group::pnpm format"
pnpm format
echo "::endgroup::"
- name: Run lint
shell: bash
run: |
echo "::group::pnpm lint"
pnpm lint
echo "::endgroup::"
- name: Run type check
shell: bash
run: |
echo "::group::pnpm check"
pnpm check
echo "::endgroup::"
- name: Generate licenses
shell: bash
run: |
echo "::group::pnpm gen:licenses"
pnpm run gen:licenses
echo "::endgroup::"
- name: Build app
shell: bash
run: |
echo "::group::pnpm build"
pnpm build
echo "::endgroup::"
publish:
name: Publish Docker image and GitHub Release
needs: ci
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up QEMU (for multi-arch builds)
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} # auto-provided token with package:write
- name: Extract version from package.json
id: version
shell: bash
run: |
echo "::group::Read version from package.json"
VERSION=$(jq -r .version package.json)
echo "Detected version: ${VERSION}"
if [[ -z "$VERSION" || "$VERSION" == "null" ]]; then
echo "Could not read version from package.json" >&2
echo "::endgroup::"
exit 1
fi
IFS='.' read -r MAJOR MINOR PATCH <<< "$VERSION"
echo "Parsed parts => MAJOR=${MAJOR}, MINOR=${MINOR}, PATCH=${PATCH}"
echo "::endgroup::"
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "major=$MAJOR" >> $GITHUB_OUTPUT
echo "minor=$MINOR" >> $GITHUB_OUTPUT
echo "patch=$PATCH" >> $GITHUB_OUTPUT
- name: Compute tags
id: meta
shell: bash
run: |
echo "::group::Compute image reference and tags"
IMAGE_NAME_LOWER=$(echo "${{ env.IMAGE_NAME }}" | tr '[:upper:]' '[:lower:]')
IMAGE_REF=${{ env.REGISTRY }}/${IMAGE_NAME_LOWER}
MAJOR=${{ steps.version.outputs.major }}
MINOR=${{ steps.version.outputs.minor }}
PATCH=${{ steps.version.outputs.patch }}
VERSION=${{ steps.version.outputs.version }}
echo "Original IMAGE_NAME: ${{ env.IMAGE_NAME }}"
echo "Normalized (lowercase) IMAGE_NAME: ${IMAGE_NAME_LOWER}"
echo "Image reference: ${IMAGE_REF}"
echo "Using version parts => MAJOR=${MAJOR}, MINOR=${MINOR}, PATCH=${PATCH}"
TAGS=(
"${IMAGE_REF}:latest"
"${IMAGE_REF}:${MAJOR}"
"${IMAGE_REF}:${MAJOR}.${MINOR}"
"${IMAGE_REF}:${VERSION}"
)
echo "Planned tags:"
for t in "${TAGS[@]}"; do echo " - ${t}"; done
TAGS_CSV=$(IFS=, ; echo "${TAGS[*]}")
echo "tags=$TAGS_CSV" >> $GITHUB_OUTPUT
echo "image_name_lower=${IMAGE_NAME_LOWER}" >> $GITHUB_OUTPUT
echo "image_ref=${IMAGE_REF}" >> $GITHUB_OUTPUT
echo "Computed tags CSV: ${TAGS_CSV}"
echo "::endgroup::"
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
push: true
provenance: true # generate provenance (SLSA) metadata
sbom: true # generate SBOM and attach to the image
tags: ${{ steps.meta.outputs.tags }}
labels: |
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
org.opencontainers.image.revision=${{ github.sha }}
org.opencontainers.image.version=${{ steps.version.outputs.version }}
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ steps.meta.outputs.image_name_lower }}:buildcache
cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ steps.meta.outputs.image_name_lower }}:buildcache,mode=max
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ steps.version.outputs.version }}
name: "${{ steps.version.outputs.version }} version"
generate_release_notes: true
- name: Summarize publish details
shell: bash
run: |
echo "## Docker Image Build Summary" >> "$GITHUB_STEP_SUMMARY"
echo "Repository: ${{ env.REGISTRY }}/${{ steps.meta.outputs.image_name_lower }}" >> "$GITHUB_STEP_SUMMARY"
echo "Version: ${{ steps.version.outputs.version }}" >> "$GITHUB_STEP_SUMMARY"
echo "Action: Pushed the following tags:" >> "$GITHUB_STEP_SUMMARY"
IFS=',' read -ra TAGS_ARR <<< "${{ steps.meta.outputs.tags }}"
for t in "${TAGS_ARR[@]}"; do echo "- $t" >> "$GITHUB_STEP_SUMMARY"; done