Skip to content

test: use available CPUs for validation (#44) #222

test: use available CPUs for validation (#44)

test: use available CPUs for validation (#44) #222

Workflow file for this run

name: CI
on:
push:
branches: [main]
tags: ['v*']
pull_request:
merge_group:
workflow_dispatch:
permissions:
contents: read
env:
GHCR_REGISTRY: ghcr.io
GHCR_IMAGE_NAME: tinyrack-net/issuary
DOCKERHUB_REGISTRY: docker.io
DOCKERHUB_USERNAME: tinyrack
DOCKERHUB_IMAGE_NAME: tinyrack/issuary
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
# Cancelling a merge-group run ejects the pull request from the queue.
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
changes:
name: Change scope
runs-on: ubuntu-24.04
timeout-minutes: 5
permissions:
contents: read
pull-requests: read
outputs:
docs_only: ${{ steps.scope.outputs.docs_only }}
steps:
- uses: actions/checkout@v6.0.3
- id: scope
name: Classify the change
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.pull_request.number }}
REPOSITORY: ${{ github.repository }}
run: |
set -euo pipefail
docs_only=false
if [ -n "${PR_NUMBER}" ]; then
files="$(gh api "repos/${REPOSITORY}/pulls/${PR_NUMBER}/files" --paginate --jq '.[].filename')" || {
echo "::warning::Could not read the PR file list; using full scope"
files=""
}
if [ -n "${files}" ]; then
docs_only=true
while IFS= read -r file; do
case "${file}" in
*.md|docs/*) ;;
*) docs_only=false; break ;;
esac
done <<< "${files}"
fi
fi
echo "docs_only=${docs_only}" >> "${GITHUB_OUTPUT}"
build:
name: Build & static checks
needs: changes
if: needs.changes.outputs.docs_only != 'true'
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- uses: actions/checkout@v6.0.3
- uses: pnpm/action-setup@v6.0.9
- uses: actions/setup-node@v6.4.0
with: { cache: pnpm, node-version: 24 }
- run: pnpm install --frozen-lockfile
- run: pnpm build
- run: pnpm biome check .
- run: pnpm check:ui
- name: Validate workflows
run: docker run --rm -v "$PWD:/repo" -w /repo rhysd/actionlint:1.7.7
- uses: actions/upload-artifact@v7.0.1
with:
name: dist-linux
path: |
packages/*/dist
packages/server/public
include-hidden-files: true
retention-days: 2
linux-tests:
name: Linux / ${{ matrix.name }}
needs: [changes, build]
if: needs.changes.outputs.docs_only != 'true'
runs-on: ubuntu-24.04
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
include:
- { name: Server, command: pnpm --filter @tinyrack/issuary-server test --run }
- { name: Tools, command: pnpm --filter @tinyrack/issuary-tools test --run }
- name: Frontend unit
command: pnpm --filter @tinyrack/issuary-frontend test:unit --run
browsers: true
- name: Standalone
command: pnpm --filter @tinyrack/issuary-standalone test:prepared --run && pnpm --filter @tinyrack/issuary-standalone test:dist:prepared
- { name: Example smoke, command: pnpm --filter @issuary-server-examples/node-hono-sqlite test:prepared }
steps:
- uses: actions/checkout@v6.0.3
- uses: pnpm/action-setup@v6.0.9
- uses: actions/setup-node@v6.4.0
with: { cache: pnpm, node-version: 24 }
- run: pnpm install --frozen-lockfile
- uses: actions/download-artifact@v8.0.1
with: { name: dist-linux, path: packages }
- if: matrix.browsers == true
run: >-
pnpm --filter @tinyrack/issuary-frontend exec playwright install --with-deps
${{ (github.event_name == 'merge_group' || startsWith(github.ref, 'refs/tags/v')) && 'chromium firefox' || 'chromium' }}
- env:
VITEST_BROWSER_SET: ${{ (github.event_name == 'pull_request' || github.ref == 'refs/heads/main') && 'chromium' || '' }}
run: ${{ matrix.command }}
frontend-smoke:
name: Linux / Frontend smoke (${{ matrix.shard }}/2)
needs: [changes, build]
if: needs.changes.outputs.docs_only != 'true'
runs-on: ubuntu-24.04
timeout-minutes: 15
strategy:
fail-fast: false
matrix: { shard: [1, 2] }
steps:
- uses: actions/checkout@v6.0.3
- uses: pnpm/action-setup@v6.0.9
- uses: actions/setup-node@v6.4.0
with: { cache: pnpm, node-version: 24 }
- run: pnpm install --frozen-lockfile
- uses: actions/download-artifact@v8.0.1
with: { name: dist-linux, path: packages }
- run: pnpm --filter @tinyrack/issuary-frontend exec playwright install --with-deps chromium
- env:
PLAYWRIGHT_BLOB_OUTPUT_FILE: blob-report/report-${{ matrix.shard }}.zip
PLAYWRIGHT_HTML_OUTPUT_DIR: playwright-report-${{ matrix.shard }}
run: >-
pnpm --filter @tinyrack/issuary-frontend test:e2e:smoke
--shard=${{ matrix.shard }}/2 --workers=100% --reporter=blob,html
- if: always()
uses: actions/upload-artifact@v7.0.1
with:
name: frontend-smoke-${{ matrix.shard }}
path: |
packages/frontend/blob-report
packages/frontend/playwright-report-${{ matrix.shard }}
if-no-files-found: warn
retention-days: 14
homepage:
name: Homepage
needs: changes
if: needs.changes.outputs.docs_only != 'true'
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@v6.0.3
- uses: pnpm/action-setup@v6.0.9
- uses: actions/setup-node@v6.4.0
with: { cache: pnpm, node-version: 24 }
- run: pnpm install --frozen-lockfile
- run: pnpm --filter @tinyrack/issuary-homepage exec playwright install --with-deps chromium
- run: pnpm --filter @tinyrack/issuary-homepage build
- run: pnpm --filter @tinyrack/issuary-homepage test:audit
- run: pnpm --filter @tinyrack/issuary-homepage deploy:dry-run
performance-smoke:
name: Performance smoke
needs: changes
if: >-
needs.changes.outputs.docs_only != 'true' &&
(github.event_name == 'pull_request' || github.ref == 'refs/heads/main')
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@v6.0.3
- uses: pnpm/action-setup@v6.0.9
- uses: actions/setup-node@v6.4.0
with: { cache: pnpm, node-version: 24 }
- run: pnpm install --frozen-lockfile
- run: pnpm --filter @tinyrack/issuary-server test:perf:smoke
full-e2e:
name: Full E2E (${{ matrix.shard }}/4)
needs: [changes, build]
if: >-
needs.changes.outputs.docs_only != 'true' &&
(github.event_name == 'merge_group' || startsWith(github.ref, 'refs/tags/v'))
runs-on: ubuntu-24.04
timeout-minutes: 25
strategy:
fail-fast: false
matrix: { shard: [1, 2, 3, 4] }
steps:
- uses: actions/checkout@v6.0.3
- uses: pnpm/action-setup@v6.0.9
- uses: actions/setup-node@v6.4.0
with: { cache: pnpm, node-version: 24 }
- run: pnpm install --frozen-lockfile
- uses: actions/download-artifact@v8.0.1
with: { name: dist-linux, path: packages }
- run: pnpm --filter @tinyrack/issuary-frontend exec playwright install --with-deps chromium firefox
- env:
ISSUARY_E2E_EXCLUDE_SCREEN_LAB: '1'
PLAYWRIGHT_BLOB_OUTPUT_FILE: blob-report/report-${{ matrix.shard }}.zip
PLAYWRIGHT_HTML_OUTPUT_DIR: playwright-report-${{ matrix.shard }}
run: >-
pnpm --filter @tinyrack/issuary-frontend test:e2e
--shard=${{ matrix.shard }}/4 --workers=100% --reporter=blob,html
- if: always()
uses: actions/upload-artifact@v7.0.1
with:
name: full-e2e-${{ matrix.shard }}
path: |
packages/frontend/blob-report
packages/frontend/playwright-report-${{ matrix.shard }}
if-no-files-found: warn
retention-days: 14
coverage:
name: Coverage / ${{ matrix.name }}
needs: changes
if: >-
needs.changes.outputs.docs_only != 'true' &&
(github.event_name == 'merge_group' || startsWith(github.ref, 'refs/tags/v'))
runs-on: ubuntu-24.04
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
include:
- { name: Server, package: server, command: pnpm --filter @tinyrack/issuary-server test:coverage }
- name: Frontend
package: frontend
command: pnpm --filter @tinyrack/issuary-frontend test:coverage
browsers: true
steps:
- uses: actions/checkout@v6.0.3
- uses: pnpm/action-setup@v6.0.9
- uses: actions/setup-node@v6.4.0
with: { cache: pnpm, node-version: 24 }
- run: pnpm install --frozen-lockfile
- if: matrix.browsers == true
run: pnpm --filter @tinyrack/issuary-frontend exec playwright install --with-deps chromium
- run: ${{ matrix.command }}
- if: always()
uses: actions/upload-artifact@v7.0.1
with:
name: coverage-${{ matrix.package }}
path: packages/${{ matrix.package }}/coverage
if-no-files-found: warn
retention-days: 14
performance:
name: Performance (${{ matrix.shard }}/4)
needs: changes
if: >-
needs.changes.outputs.docs_only != 'true' &&
(github.event_name == 'merge_group' || startsWith(github.ref, 'refs/tags/v'))
runs-on: ubuntu-24.04
timeout-minutes: 15
strategy:
fail-fast: false
matrix: { shard: [1, 2, 3, 4] }
steps:
- uses: actions/checkout@v6.0.3
- uses: pnpm/action-setup@v6.0.9
- uses: actions/setup-node@v6.4.0
with: { cache: pnpm, node-version: 24 }
- run: pnpm install --frozen-lockfile
- env:
ISSUARY_PERF_REPORT_PATH: performance-results/shard-${{ matrix.shard }}-of-4.json
run: pnpm --filter @tinyrack/issuary-server test:perf --shard=${{ matrix.shard }}/4
- if: always()
uses: actions/upload-artifact@v7.0.1
with:
name: performance-${{ matrix.shard }}
path: packages/server/performance-results/shard-${{ matrix.shard }}-of-4.json
if-no-files-found: warn
retention-days: 14
windows-build:
name: Windows / Build
needs: changes
if: >-
needs.changes.outputs.docs_only != 'true' &&
(github.event_name == 'merge_group' || startsWith(github.ref, 'refs/tags/v'))
runs-on: windows-2025
timeout-minutes: 20
steps:
- run: git config --global core.autocrlf false
- uses: actions/checkout@v6.0.3
- uses: pnpm/action-setup@v6.0.9
- uses: actions/setup-node@v6.4.0
with: { cache: pnpm, node-version: 24 }
- run: pnpm install --frozen-lockfile
- run: pnpm build
- run: pnpm biome check .
- uses: actions/upload-artifact@v7.0.1
with:
name: dist-windows
path: |
packages/*/dist
packages/server/public
include-hidden-files: true
retention-days: 2
windows-tests:
name: Windows / ${{ matrix.name }}
needs: [changes, windows-build]
if: >-
needs.changes.outputs.docs_only != 'true' &&
(github.event_name == 'merge_group' || startsWith(github.ref, 'refs/tags/v'))
runs-on: windows-2025
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
include:
- { name: Server, command: pnpm --filter @tinyrack/issuary-server test --run }
- { name: Tools, command: pnpm --filter @tinyrack/issuary-tools test --run }
- { name: Standalone, command: pnpm --filter @tinyrack/issuary-standalone test:prepared --run && pnpm --filter @tinyrack/issuary-standalone test:dist:prepared }
- name: Frontend unit
command: pnpm --filter @tinyrack/issuary-frontend test:unit --run
browsers: true
steps:
- run: git config --global core.autocrlf false
- uses: actions/checkout@v6.0.3
- uses: pnpm/action-setup@v6.0.9
- uses: actions/setup-node@v6.4.0
with: { cache: pnpm, node-version: 24 }
- run: pnpm install --frozen-lockfile
- uses: actions/download-artifact@v8.0.1
with: { name: dist-windows, path: packages }
- if: matrix.browsers == true
run: pnpm --filter @tinyrack/issuary-frontend exec playwright install chromium firefox
- run: ${{ matrix.command }}
windows-frontend:
name: Windows / ${{ matrix.suite }} (${{ matrix.shard }}/2)
needs: [changes, windows-build]
if: >-
needs.changes.outputs.docs_only != 'true' &&
(github.event_name == 'merge_group' || startsWith(github.ref, 'refs/tags/v'))
runs-on: windows-2025
timeout-minutes: 20
strategy:
fail-fast: false
matrix: { suite: [smoke, screen], shard: [1, 2] }
steps:
- run: git config --global core.autocrlf false
- uses: actions/checkout@v6.0.3
- uses: pnpm/action-setup@v6.0.9
- uses: actions/setup-node@v6.4.0
with: { cache: pnpm, node-version: 24 }
- run: pnpm install --frozen-lockfile
- uses: actions/download-artifact@v8.0.1
with: { name: dist-windows, path: packages }
- run: pnpm --filter @tinyrack/issuary-frontend exec playwright install chromium
- env:
PLAYWRIGHT_BLOB_OUTPUT_FILE: blob-report/report-${{ matrix.suite }}-${{ matrix.shard }}.zip
PLAYWRIGHT_HTML_OUTPUT_DIR: playwright-report-${{ matrix.suite }}-${{ matrix.shard }}
run: >-
pnpm --filter @tinyrack/issuary-frontend test:e2e:${{ matrix.suite }}
--shard=${{ matrix.shard }}/2 --workers=100% --reporter=blob,html
- if: always()
uses: actions/upload-artifact@v7.0.1
with:
name: windows-${{ matrix.suite }}-${{ matrix.shard }}
path: |
packages/frontend/blob-report
packages/frontend/playwright-report-${{ matrix.suite }}-${{ matrix.shard }}
if-no-files-found: warn
retention-days: 14
docker-build:
name: Docker build
needs: changes
if: >-
needs.changes.outputs.docs_only != 'true' &&
(github.event_name == 'merge_group' || startsWith(github.ref, 'refs/tags/v'))
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- uses: actions/checkout@v6.0.3
- uses: docker/setup-buildx-action@v4.1.0
- uses: docker/build-push-action@v7.2.0
with:
context: .
file: ./packages/standalone/Dockerfile
push: false
load: true
platforms: linux/amd64
cache-from: type=gha
cache-to: type=gha,mode=max
quality-gate:
name: Quality Gate
if: always()
needs: [changes, build, linux-tests, frontend-smoke, homepage, performance-smoke, full-e2e, coverage, performance, windows-build, windows-tests, windows-frontend, docker-build]
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- env:
QUALITY_RESULTS: ${{ toJSON(needs) }}
run: >-
jq -e 'all(.[]; .result == "success" or .result == "skipped")
and (.changes.result == "success")' <<< "$QUALITY_RESULTS"
docker-push:
name: Docker push
needs: [quality-gate, docker-build]
if: startsWith(github.ref, 'refs/tags/v') && needs.quality-gate.result == 'success'
runs-on: ubuntu-24.04
permissions: { contents: read, packages: write }
steps:
- uses: actions/checkout@v6.0.3
- uses: docker/setup-qemu-action@v4.1.0
- uses: docker/setup-buildx-action@v4.1.0
- env: { DOCKERHUB_TOKEN: '${{ secrets.DOCKERHUB_TOKEN }}' }
run: test -n "$DOCKERHUB_TOKEN"
- uses: docker/login-action@v4.2.0
with: { registry: '${{ env.GHCR_REGISTRY }}', username: '${{ github.actor }}', password: '${{ secrets.GITHUB_TOKEN }}' }
- uses: docker/login-action@v4.2.0
with: { registry: '${{ env.DOCKERHUB_REGISTRY }}', username: '${{ env.DOCKERHUB_USERNAME }}', password: '${{ secrets.DOCKERHUB_TOKEN }}' }
- id: metadata
uses: docker/metadata-action@v6.1.0
with:
images: |
${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}
${{ env.DOCKERHUB_REGISTRY }}/${{ env.DOCKERHUB_IMAGE_NAME }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}},enable=${{ !contains(github.ref_name, '-') }}
type=semver,pattern={{major}},enable=${{ !contains(github.ref_name, '-') }}
type=sha
- uses: docker/build-push-action@v7.2.0
with:
context: .
file: ./packages/standalone/Dockerfile
push: true
platforms: linux/amd64,linux/arm64
tags: ${{ steps.metadata.outputs.tags }}
labels: ${{ steps.metadata.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
npm-publish:
name: Publish to npm
needs: quality-gate
if: startsWith(github.ref, 'refs/tags/v') && needs.quality-gate.result == 'success'
runs-on: ubuntu-24.04
permissions: { contents: read, id-token: write }
steps:
- uses: actions/checkout@v6.0.3
- uses: pnpm/action-setup@v6.0.9
- uses: actions/setup-node@v6.4.0
with: { cache: pnpm, node-version: 24, registry-url: 'https://registry.npmjs.org' }
- run: pnpm install --frozen-lockfile
- run: pnpm build
- run: cp readme.md packages/server/readme.md
- run: pnpm publish --filter @tinyrack/issuary-server --access public --no-git-checks