CI #233
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |