mirror of
https://github.com/bookorbit/bookorbit.git
synced 2026-11-03 08:21:39 -06:00
chore: enhance ci pipeline
This commit is contained in:
@@ -22,6 +22,10 @@ inputs:
|
||||
description: Path(s) used to generate the cache key.
|
||||
required: false
|
||||
default: "pnpm-lock.yaml"
|
||||
prefetch:
|
||||
description: Whether to run pnpm fetch before installation.
|
||||
required: false
|
||||
default: "false"
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
@@ -54,6 +58,11 @@ runs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-${{ inputs.pnpm-version }}-store-
|
||||
|
||||
- name: Prefetch dependencies
|
||||
if: ${{ inputs.prefetch == 'true' }}
|
||||
shell: bash
|
||||
run: pnpm fetch --frozen-lockfile
|
||||
|
||||
- name: Install dependencies
|
||||
if: ${{ inputs.install == 'true' }}
|
||||
shell: bash
|
||||
|
||||
+211
-243
@@ -1,9 +1,11 @@
|
||||
name: CI
|
||||
run-name: >-
|
||||
${{ (github.event_name == 'push' && format('CI - {0} ({1})', github.event.head_commit.message, github.ref_name)) || (github.event_name == 'pull_request' && format('CI - PR #{0}: {1}', github.event.pull_request.number, github.event.pull_request.title)) || 'CI - manual' }}
|
||||
${{ (github.event_name == 'push' && format('CI - {0} ({1})', github.event.head_commit.message, github.ref_name)) || (github.event_name == 'pull_request' && format('CI - PR #{0}: {1}', github.event.pull_request.number, github.event.pull_request.title)) || (github.event_name == 'schedule' && 'CI - nightly') || 'CI - manual' }}
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "17 4 * * *"
|
||||
pull_request:
|
||||
branches: [main]
|
||||
push:
|
||||
@@ -48,9 +50,9 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Manual dispatch runs all scopes
|
||||
- name: Manual and nightly runs include all scopes
|
||||
id: manual
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
if: github.event_name == 'workflow_dispatch' || github.event_name == 'schedule'
|
||||
shell: bash
|
||||
run: |
|
||||
echo "server=true" >> "$GITHUB_OUTPUT"
|
||||
@@ -61,7 +63,7 @@ jobs:
|
||||
# Force-push edge cases (before SHA no longer reachable) may mark all files changed too.
|
||||
- name: Compute changed scopes
|
||||
id: changes
|
||||
if: github.event_name != 'workflow_dispatch'
|
||||
if: github.event_name != 'workflow_dispatch' && github.event_name != 'schedule'
|
||||
uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4
|
||||
with:
|
||||
filters: |
|
||||
@@ -88,27 +90,159 @@ jobs:
|
||||
echo "server=${{ steps.manual.outputs.server || steps.changes.outputs.server }}"
|
||||
echo "client=${{ steps.manual.outputs.client || steps.changes.outputs.client }}"
|
||||
|
||||
checks:
|
||||
name: ${{ matrix.check }} (${{ matrix.package }})
|
||||
lint:
|
||||
name: Lint (${{ matrix.package_label }})
|
||||
runs-on: ubuntu-latest
|
||||
needs: [dependency-review, detect-changes]
|
||||
timeout-minutes: 20
|
||||
# matrix context is not available in job-level if; per-package change filtering
|
||||
# is done via the has-changes gate step below.
|
||||
timeout-minutes: 10
|
||||
if: >
|
||||
always() &&
|
||||
(needs.dependency-review.result == 'success' || needs.dependency-review.result == 'skipped') &&
|
||||
needs.detect-changes.result == 'success'
|
||||
strategy:
|
||||
# All matrix cells run independently so all failures are visible in one pass.
|
||||
fail-fast: false
|
||||
matrix:
|
||||
package: [server, client]
|
||||
check: [lint, typecheck, test]
|
||||
include:
|
||||
- package: server
|
||||
package_label: Server
|
||||
- package: client
|
||||
package_label: Client
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Check for package changes
|
||||
id: has-changes
|
||||
shell: bash
|
||||
env:
|
||||
PACKAGE: ${{ matrix.package }}
|
||||
SERVER_CHANGED: ${{ needs.detect-changes.outputs.server }}
|
||||
CLIENT_CHANGED: ${{ needs.detect-changes.outputs.client }}
|
||||
run: |
|
||||
if [[ "$PACKAGE" == "server" && "$SERVER_CHANGED" == "true" ]] || \
|
||||
[[ "$PACKAGE" == "client" && "$CLIENT_CHANGED" == "true" ]]; then
|
||||
echo "run=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "run=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Checkout
|
||||
if: steps.has-changes.outputs.run == 'true'
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Set up Node.js and pnpm
|
||||
if: steps.has-changes.outputs.run == 'true'
|
||||
uses: ./.github/actions/setup-node-pnpm
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
prefetch: "true"
|
||||
|
||||
- name: Run lint checks
|
||||
if: steps.has-changes.outputs.run == 'true'
|
||||
shell: bash
|
||||
env:
|
||||
PACKAGE: ${{ matrix.package }}
|
||||
run: |
|
||||
if [[ "$PACKAGE" == "server" ]]; then
|
||||
pnpm --filter server lint:check
|
||||
else
|
||||
pnpm --filter client lint:check
|
||||
fi
|
||||
|
||||
- name: Run format checks
|
||||
if: steps.has-changes.outputs.run == 'true'
|
||||
shell: bash
|
||||
env:
|
||||
PACKAGE: ${{ matrix.package }}
|
||||
run: |
|
||||
if [[ "$PACKAGE" == "server" ]]; then
|
||||
pnpm --filter server format:check
|
||||
else
|
||||
pnpm --filter client format:check
|
||||
fi
|
||||
|
||||
typecheck:
|
||||
name: Type Check (${{ matrix.package_label }})
|
||||
runs-on: ubuntu-latest
|
||||
needs: [dependency-review, detect-changes]
|
||||
timeout-minutes: 10
|
||||
if: >
|
||||
always() &&
|
||||
(needs.dependency-review.result == 'success' || needs.dependency-review.result == 'skipped') &&
|
||||
needs.detect-changes.result == 'success'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
package: [server, client]
|
||||
include:
|
||||
- package: server
|
||||
package_label: Server
|
||||
- package: client
|
||||
package_label: Client
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Check for package changes
|
||||
id: has-changes
|
||||
shell: bash
|
||||
env:
|
||||
PACKAGE: ${{ matrix.package }}
|
||||
SERVER_CHANGED: ${{ needs.detect-changes.outputs.server }}
|
||||
CLIENT_CHANGED: ${{ needs.detect-changes.outputs.client }}
|
||||
run: |
|
||||
if [[ "$PACKAGE" == "server" && "$SERVER_CHANGED" == "true" ]] || \
|
||||
[[ "$PACKAGE" == "client" && "$CLIENT_CHANGED" == "true" ]]; then
|
||||
echo "run=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "run=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Checkout
|
||||
if: steps.has-changes.outputs.run == 'true'
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Set up Node.js and pnpm
|
||||
if: steps.has-changes.outputs.run == 'true'
|
||||
uses: ./.github/actions/setup-node-pnpm
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
prefetch: "true"
|
||||
|
||||
- name: Run typecheck
|
||||
if: steps.has-changes.outputs.run == 'true'
|
||||
shell: bash
|
||||
env:
|
||||
PACKAGE: ${{ matrix.package }}
|
||||
run: |
|
||||
if [[ "$PACKAGE" == "server" ]]; then
|
||||
pnpm --filter server type-check
|
||||
else
|
||||
pnpm --filter @projectx/types build
|
||||
pnpm --filter client type-check
|
||||
fi
|
||||
|
||||
tests:
|
||||
name: Tests (${{ matrix.package_label }})
|
||||
runs-on: ubuntu-latest
|
||||
needs: [dependency-review, detect-changes]
|
||||
timeout-minutes: 20
|
||||
if: >
|
||||
always() &&
|
||||
(needs.dependency-review.result == 'success' || needs.dependency-review.result == 'skipped') &&
|
||||
needs.detect-changes.result == 'success'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
package: [server, client]
|
||||
include:
|
||||
- package: server
|
||||
package_label: Server
|
||||
- package: client
|
||||
package_label: Client
|
||||
permissions:
|
||||
contents: read
|
||||
# checks: write is only used by publish-unit-test-result-action in test matrix cells.
|
||||
# Lint and typecheck cells receive this permission too due to the shared job definition.
|
||||
checks: write
|
||||
steps:
|
||||
- name: Check for package changes
|
||||
@@ -136,32 +270,15 @@ jobs:
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
|
||||
- name: Run lint
|
||||
if: steps.has-changes.outputs.run == 'true' && matrix.check == 'lint'
|
||||
shell: bash
|
||||
run: pnpm --filter "${{ matrix.package }}" lint:check
|
||||
|
||||
- name: Run typecheck
|
||||
if: steps.has-changes.outputs.run == 'true' && matrix.check == 'typecheck'
|
||||
shell: bash
|
||||
env:
|
||||
PACKAGE: ${{ matrix.package }}
|
||||
run: |
|
||||
if [[ "$PACKAGE" == "server" ]]; then
|
||||
pnpm --filter server type-check
|
||||
else
|
||||
pnpm --filter @projectx/types build
|
||||
pnpm --filter client type-check
|
||||
fi
|
||||
prefetch: "true"
|
||||
|
||||
- name: Prepare test report directory
|
||||
if: steps.has-changes.outputs.run == 'true' && matrix.check == 'test'
|
||||
if: steps.has-changes.outputs.run == 'true'
|
||||
shell: bash
|
||||
run: mkdir -p "$GITHUB_WORKSPACE/test-results/${{ matrix.package }}"
|
||||
|
||||
- name: Run tests
|
||||
if: steps.has-changes.outputs.run == 'true' && matrix.check == 'test'
|
||||
if: steps.has-changes.outputs.run == 'true'
|
||||
shell: bash
|
||||
env:
|
||||
PACKAGE: ${{ matrix.package }}
|
||||
@@ -177,7 +294,7 @@ jobs:
|
||||
if: >
|
||||
always() &&
|
||||
steps.has-changes.outputs.run == 'true' &&
|
||||
matrix.check == 'test' &&
|
||||
hashFiles(format('test-results/{0}/junit.xml', matrix.package)) != '' &&
|
||||
(github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)
|
||||
uses: EnricoMi/publish-unit-test-result-action@c950f6fb443cb5af20a377fd0dfaa78838901040 # v2
|
||||
with:
|
||||
@@ -190,7 +307,7 @@ jobs:
|
||||
if: >
|
||||
always() &&
|
||||
steps.has-changes.outputs.run == 'true' &&
|
||||
matrix.check == 'test' &&
|
||||
hashFiles(format('test-results/{0}/junit.xml', matrix.package)) != '' &&
|
||||
(github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
with:
|
||||
@@ -201,7 +318,7 @@ jobs:
|
||||
quality-gate:
|
||||
name: Quality Gate
|
||||
runs-on: ubuntu-latest
|
||||
needs: [dependency-review, detect-changes, checks]
|
||||
needs: [dependency-review, detect-changes, lint, typecheck, tests]
|
||||
timeout-minutes: 5
|
||||
if: always()
|
||||
steps:
|
||||
@@ -215,14 +332,16 @@ jobs:
|
||||
run: |
|
||||
echo "dependency-review=${{ needs.dependency-review.result }}"
|
||||
echo "detect-changes=${{ needs.detect-changes.result }}"
|
||||
echo "checks=${{ needs.checks.result }}"
|
||||
echo "lint=${{ needs.lint.result }}"
|
||||
echo "typecheck=${{ needs.typecheck.result }}"
|
||||
echo "tests=${{ needs.tests.result }}"
|
||||
|
||||
e2e-setup:
|
||||
name: Detect E2E Suites
|
||||
runs-on: ubuntu-latest
|
||||
needs: [quality-gate]
|
||||
if: needs.quality-gate.result == 'success'
|
||||
timeout-minutes: 5
|
||||
timeout-minutes: 10
|
||||
outputs:
|
||||
matrix: ${{ steps.build-matrix.outputs.matrix }}
|
||||
steps:
|
||||
@@ -231,195 +350,6 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Detect path changes per suite
|
||||
id: changes
|
||||
if: github.event_name != 'workflow_dispatch'
|
||||
uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4
|
||||
with:
|
||||
filters: |
|
||||
workflow-changed:
|
||||
- '.github/workflows/ci.yml'
|
||||
- '.github/workflows/e2e.yml'
|
||||
- '.github/workflows/e2e-runner.yml'
|
||||
- 'Dockerfile'
|
||||
- 'docker-compose*.yml'
|
||||
- 'docker/**'
|
||||
app-smoke:
|
||||
- 'server/src/**'
|
||||
- 'server/test/app-smoke.e2e-spec.ts'
|
||||
- 'server/src/db/schema/**'
|
||||
- 'packages/types/src/**'
|
||||
- 'scripts/test/**'
|
||||
- 'scripts/db/**'
|
||||
- '.github/workflows/e2e-runner.yml'
|
||||
auth-session-security:
|
||||
- 'server/src/modules/auth/**'
|
||||
- 'server/src/modules/user/**'
|
||||
- 'server/src/modules/role/**'
|
||||
- 'server/src/common/guards/**'
|
||||
- 'server/src/common/decorators/**'
|
||||
- 'server/src/common/types/**'
|
||||
- 'server/src/db/schema/**'
|
||||
- 'server/test/auth-session-security.e2e-spec.ts'
|
||||
- 'server/test/e2e/app-harness.ts'
|
||||
- 'packages/types/src/auth.ts'
|
||||
- 'packages/types/src/permissions.ts'
|
||||
- 'scripts/test/**'
|
||||
- 'scripts/db/**'
|
||||
- '.github/workflows/e2e-runner.yml'
|
||||
auth-recovery-oidc-logout:
|
||||
- 'server/src/modules/auth/**'
|
||||
- 'server/src/modules/user/**'
|
||||
- 'server/src/modules/email/**'
|
||||
- 'server/src/modules/metadata/**'
|
||||
- 'server/src/db/schema/**'
|
||||
- 'server/test/auth-recovery-oidc-logout.e2e-spec.ts'
|
||||
- 'server/test/e2e/app-harness.ts'
|
||||
- 'packages/types/src/auth.ts'
|
||||
- 'scripts/test/**'
|
||||
- 'scripts/db/**'
|
||||
- '.github/workflows/e2e-runner.yml'
|
||||
authorization-matrix:
|
||||
- 'server/src/**'
|
||||
- 'server/test/authorization-matrix.e2e-spec.ts'
|
||||
- 'server/test/e2e/authorization-matrix/**'
|
||||
- 'packages/types/src/**'
|
||||
- 'scripts/test/**'
|
||||
- 'scripts/db/**'
|
||||
- '.github/workflows/e2e-runner.yml'
|
||||
email-lifecycle:
|
||||
- 'server/src/modules/email/**'
|
||||
- 'server/src/modules/book/**'
|
||||
- 'server/src/modules/library/**'
|
||||
- 'server/src/modules/auth/**'
|
||||
- 'server/src/modules/cover/**'
|
||||
- 'server/src/modules/scanner/**'
|
||||
- 'server/src/modules/seed/**'
|
||||
- 'server/src/db/schema/**'
|
||||
- 'packages/types/src/**'
|
||||
- 'server/test/email-lifecycle.e2e-spec.ts'
|
||||
- 'server/test/e2e/email/**'
|
||||
- 'server/test/e2e/**'
|
||||
- 'scripts/test/**'
|
||||
- 'scripts/db/**'
|
||||
- 'client/src/features/email/**'
|
||||
- 'client/src/router/index.ts'
|
||||
- 'client/src/features/settings/SettingsHeader.vue'
|
||||
- 'client/src/views/HomeView.vue'
|
||||
- 'client/src/views/CollectionView.vue'
|
||||
- 'client/src/views/LensView.vue'
|
||||
- 'client/src/features/book/components/BookCoverCard.vue'
|
||||
- 'client/src/features/book/components/BookListRow.vue'
|
||||
metadata-write:
|
||||
- 'server/src/modules/book/**'
|
||||
- 'server/src/modules/library/**'
|
||||
- 'server/src/modules/file-write/**'
|
||||
- 'server/src/modules/metadata/**'
|
||||
- 'server/src/modules/app-settings/**'
|
||||
- 'server/test/metadata-write.e2e-spec.ts'
|
||||
- 'server/test/e2e/metadata-write/**'
|
||||
- 'server/src/db/schema/**'
|
||||
- 'packages/types/src/file-write.ts'
|
||||
- 'scripts/test/**'
|
||||
- 'scripts/db/**'
|
||||
metadata-lock:
|
||||
- 'server/src/modules/book/**'
|
||||
- 'server/src/modules/book-metadata-lock/**'
|
||||
- 'server/src/modules/book-metadata-fetch/**'
|
||||
- 'server/src/modules/metadata-fetch/**'
|
||||
- 'server/src/modules/metadata-preferences/**'
|
||||
- 'server/src/modules/cover/**'
|
||||
- 'server/src/modules/scanner/**'
|
||||
- 'server/src/modules/library/**'
|
||||
- 'server/src/modules/auth/**'
|
||||
- 'server/src/modules/app-settings/**'
|
||||
- 'server/test/metadata-lock.e2e-spec.ts'
|
||||
- 'server/test/e2e/metadata-lock/**'
|
||||
- 'server/src/db/schema/**'
|
||||
- 'packages/types/src/**'
|
||||
- 'scripts/test/**'
|
||||
- 'scripts/db/**'
|
||||
- '.github/workflows/e2e-runner.yml'
|
||||
migration-booklore:
|
||||
- 'server/src/modules/migration/**'
|
||||
- 'server/test/migration-booklore.e2e-spec.ts'
|
||||
- 'server/test/e2e/migration-booklore/**'
|
||||
- 'server/src/db/schema/**'
|
||||
- 'scripts/test/**'
|
||||
- 'scripts/db/**'
|
||||
- '.github/workflows/e2e-runner.yml'
|
||||
opds-auth-catalog:
|
||||
- 'server/src/modules/opds/**'
|
||||
- 'server/src/modules/book/**'
|
||||
- 'server/src/modules/library/**'
|
||||
- 'server/src/modules/collection/**'
|
||||
- 'server/src/modules/lens/**'
|
||||
- 'server/src/modules/user/**'
|
||||
- 'server/src/modules/auth/**'
|
||||
- 'server/src/modules/app-settings/**'
|
||||
- 'server/test/opds-auth-catalog.e2e-spec.ts'
|
||||
- 'server/test/e2e/opds/**'
|
||||
- 'server/test/e2e/**'
|
||||
- 'server/src/db/schema/**'
|
||||
- 'packages/types/src/**'
|
||||
- 'scripts/test/**'
|
||||
- 'scripts/db/**'
|
||||
reader-state-isolation:
|
||||
- 'server/src/modules/bookmark/**'
|
||||
- 'server/src/modules/annotation/**'
|
||||
- 'server/src/modules/reader-preferences/**'
|
||||
- 'server/src/modules/reading-session/**'
|
||||
- 'server/src/modules/book/**'
|
||||
- 'server/src/modules/library/**'
|
||||
- 'server/src/modules/scanner/**'
|
||||
- 'server/src/modules/user-book-status/**'
|
||||
- 'server/src/scripts/db-prepare-e2e.ts'
|
||||
- 'server/test/reader-state-isolation.e2e-spec.ts'
|
||||
- 'server/test/e2e/reader-state-isolation/**'
|
||||
- 'server/src/db/schema/**'
|
||||
- 'packages/types/src/reader-settings.ts'
|
||||
- 'scripts/test/**'
|
||||
- 'scripts/db/**'
|
||||
scanner-file-operations:
|
||||
- 'server/src/modules/scanner/**'
|
||||
- 'server/test/scanner-file-operations.e2e-spec.ts'
|
||||
- 'server/test/e2e/scanner/**'
|
||||
- 'server/src/db/schema/**'
|
||||
- 'scripts/test/**'
|
||||
- 'scripts/db/**'
|
||||
- '.github/workflows/e2e-runner.yml'
|
||||
scanner-scenarios:
|
||||
- 'server/src/modules/scanner/**'
|
||||
- 'server/test/scanner-scenarios.e2e-spec.ts'
|
||||
- 'server/test/e2e/scanner/**'
|
||||
- 'server/src/db/schema/**'
|
||||
- 'scripts/test/**'
|
||||
- 'scripts/db/**'
|
||||
- '.github/workflows/e2e-runner.yml'
|
||||
book-bucket-ingest-finalize:
|
||||
- 'server/src/modules/book-bucket/**'
|
||||
- 'server/src/modules/upload/**'
|
||||
- 'server/src/modules/metadata/**'
|
||||
- 'server/test/book-bucket-ingest-finalize.e2e-spec.ts'
|
||||
- 'server/test/e2e/book-bucket/**'
|
||||
- 'server/src/db/schema/**'
|
||||
- 'scripts/test/**'
|
||||
- 'scripts/db/**'
|
||||
users-admin-lifecycle:
|
||||
- 'server/src/modules/user/**'
|
||||
- 'server/src/modules/auth/**'
|
||||
- 'server/src/common/guards/**'
|
||||
- 'server/src/common/decorators/**'
|
||||
- 'server/src/common/types/**'
|
||||
- 'server/src/db/schema/**'
|
||||
- 'server/test/users-admin-lifecycle.e2e-spec.ts'
|
||||
- 'server/test/e2e/users-admin-lifecycle/**'
|
||||
- 'packages/types/src/auth.ts'
|
||||
- 'packages/types/src/permissions.ts'
|
||||
- 'scripts/test/**'
|
||||
- 'scripts/db/**'
|
||||
- '.github/workflows/e2e-runner.yml'
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
with:
|
||||
@@ -428,24 +358,14 @@ jobs:
|
||||
- name: Build suite matrix
|
||||
id: build-matrix
|
||||
shell: bash
|
||||
env:
|
||||
GITHUB_EVENT_NAME: ${{ github.event_name }}
|
||||
GITHUB_BASE_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }}
|
||||
GITHUB_HEAD_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
|
||||
run: |
|
||||
SUITES_DEF=$(node scripts/test/list-suites-json.mjs)
|
||||
|
||||
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
|
||||
MATRIX="$SUITES_DEF"
|
||||
elif [ "${{ steps.changes.outputs.workflow-changed }}" == "true" ]; then
|
||||
MATRIX="$SUITES_DEF"
|
||||
else
|
||||
CHANGED='${{ steps.changes.outputs.changes }}'
|
||||
if [ -z "$CHANGED" ] || [ "$CHANGED" == "[]" ]; then
|
||||
MATRIX="[]"
|
||||
else
|
||||
MATRIX=$(echo "$SUITES_DEF" | jq --argjson changed "$CHANGED" \
|
||||
'[.[] | select(.id as $id | $changed | index($id) != null)]')
|
||||
fi
|
||||
fi
|
||||
|
||||
MATRIX=$(node scripts/test/select-e2e-suites-json.mjs)
|
||||
echo "matrix=$(echo "$MATRIX" | jq -c .)" >> "$GITHUB_OUTPUT"
|
||||
echo "selected_suites=$(echo "$MATRIX" | jq -c .)"
|
||||
|
||||
e2e:
|
||||
needs: [e2e-setup]
|
||||
@@ -482,3 +402,51 @@ jobs:
|
||||
echo "quality-gate=${{ needs.quality-gate.result }}"
|
||||
echo "e2e-setup=${{ needs.e2e-setup.result }}"
|
||||
echo "e2e=${{ needs.e2e.result }}"
|
||||
|
||||
ci-performance:
|
||||
name: CI Performance
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ci-gate]
|
||||
if: always()
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
steps:
|
||||
- name: Compare duration with previous successful run
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
RUN_ID: ${{ github.run_id }}
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
CURRENT_JSON=$(gh api "repos/${GH_REPO}/actions/runs/${RUN_ID}")
|
||||
CURRENT_STARTED=$(echo "$CURRENT_JSON" | jq -r '.run_started_at // .created_at')
|
||||
CURRENT_UPDATED=$(echo "$CURRENT_JSON" | jq -r '.updated_at')
|
||||
CURRENT_DURATION=$(node -e 'const [start, end] = process.argv.slice(1); console.log(Math.max(0, Math.round((Date.parse(end) - Date.parse(start)) / 1000)));' "$CURRENT_STARTED" "$CURRENT_UPDATED")
|
||||
|
||||
PREVIOUS_JSON=$(gh api "repos/${GH_REPO}/actions/workflows/ci.yml/runs?per_page=30&event=${EVENT_NAME}&status=completed")
|
||||
PREVIOUS_STARTED=$(echo "$PREVIOUS_JSON" | jq -r --arg run_id "${RUN_ID}" '.workflow_runs[] | select((.id | tostring) != $run_id and .conclusion == "success") | (.run_started_at // .created_at)' | head -n1)
|
||||
PREVIOUS_UPDATED=$(echo "$PREVIOUS_JSON" | jq -r --arg run_id "${RUN_ID}" '.workflow_runs[] | select((.id | tostring) != $run_id and .conclusion == "success") | .updated_at' | head -n1)
|
||||
|
||||
{
|
||||
echo "## CI Runtime"
|
||||
echo ""
|
||||
echo "- Event: \`${EVENT_NAME}\`"
|
||||
echo "- Current run duration: \`${CURRENT_DURATION}s\`"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
if [[ -z "$PREVIOUS_STARTED" || -z "$PREVIOUS_UPDATED" ]]; then
|
||||
echo "- Baseline: unavailable (no previous successful run for this event)" >> "$GITHUB_STEP_SUMMARY"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
PREVIOUS_DURATION=$(node -e 'const [start, end] = process.argv.slice(1); console.log(Math.max(0, Math.round((Date.parse(end) - Date.parse(start)) / 1000)));' "$PREVIOUS_STARTED" "$PREVIOUS_UPDATED")
|
||||
RATIO=$(node -e 'const [current, previous] = process.argv.slice(1).map(Number); if (previous === 0) { console.log("N/A"); process.exit(0); } console.log((current / previous).toFixed(2));' "$CURRENT_DURATION" "$PREVIOUS_DURATION")
|
||||
echo "- Previous successful run: \`${PREVIOUS_DURATION}s\`" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "- Ratio (current/previous): \`${RATIO}\`" >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
if [[ "$RATIO" != "N/A" ]] && node -e 'process.exit(Number(process.argv[1]) > 1.2 ? 0 : 1)' "$RATIO"; then
|
||||
echo "::warning::CI duration regression detected: current=${CURRENT_DURATION}s previous=${PREVIOUS_DURATION}s ratio=${RATIO}"
|
||||
fi
|
||||
|
||||
@@ -29,7 +29,9 @@ env:
|
||||
jobs:
|
||||
image:
|
||||
name: Build and Push Image
|
||||
if: github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success'
|
||||
if: >
|
||||
github.event_name != 'workflow_run' ||
|
||||
(github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push')
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
permissions:
|
||||
|
||||
@@ -67,6 +67,7 @@ jobs:
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
prefetch: "true"
|
||||
|
||||
- name: Run e2e suite
|
||||
shell: bash
|
||||
|
||||
@@ -1,185 +1,427 @@
|
||||
const TEST_RESULTS_DIR = "../test-results/server";
|
||||
|
||||
export const E2E_GLOBAL_CHANGE_PATHS = Object.freeze([
|
||||
".github/workflows/ci.yml",
|
||||
".github/workflows/e2e.yml",
|
||||
".github/workflows/e2e-runner.yml",
|
||||
"Dockerfile",
|
||||
"docker-compose*.yml",
|
||||
"docker/**",
|
||||
]);
|
||||
|
||||
const SHARED_DB_AND_HELPER_PATHS = Object.freeze(["server/src/db/schema/**", "scripts/test/**", "scripts/db/**"]);
|
||||
|
||||
const SHARED_AUTH_TYPE_PATHS = Object.freeze(["packages/types/src/auth.ts", "packages/types/src/permissions.ts"]);
|
||||
|
||||
const SHARED_TYPES_PATHS = Object.freeze(["packages/types/src/**"]);
|
||||
|
||||
export const E2E_SUITES = Object.freeze({
|
||||
"guard-mechanics": {
|
||||
id: "guard-mechanics",
|
||||
name: "Guard Mechanics",
|
||||
timeout: 30,
|
||||
lane: "smoke",
|
||||
description: "Guard mechanics suite - verifies @Public/@RequirePermission wiring without a real DB",
|
||||
vitestTarget: "test/guard-mechanics.e2e-spec.ts",
|
||||
junitOutput: `${TEST_RESULTS_DIR}/guard-mechanics-e2e-junit.xml`,
|
||||
prepareDedicatedDatabase: false,
|
||||
useDedicatedDatabase: false,
|
||||
changedPaths: [
|
||||
"server/src/common/guards/**",
|
||||
"server/src/common/decorators/**",
|
||||
"server/src/common/types/**",
|
||||
"server/src/modules/auth/**",
|
||||
"server/src/modules/user/**",
|
||||
"server/src/modules/role/**",
|
||||
"server/src/**",
|
||||
"server/test/guard-mechanics.e2e-spec.ts",
|
||||
"server/test/e2e/**",
|
||||
...SHARED_AUTH_TYPE_PATHS,
|
||||
...SHARED_DB_AND_HELPER_PATHS,
|
||||
],
|
||||
},
|
||||
"scanner-scenarios": {
|
||||
id: "scanner-scenarios",
|
||||
name: "Scanner Scenarios",
|
||||
timeout: 45,
|
||||
lane: "full",
|
||||
description: "Scanner scenario matrix",
|
||||
vitestTarget: "test/scanner-scenarios.e2e-spec.ts",
|
||||
junitOutput: `${TEST_RESULTS_DIR}/scanner-scenarios-e2e-junit.xml`,
|
||||
prepareDedicatedDatabase: true,
|
||||
useDedicatedDatabase: true,
|
||||
changedPaths: [
|
||||
"server/src/modules/scanner/**",
|
||||
"server/test/scanner-scenarios.e2e-spec.ts",
|
||||
"server/test/e2e/scanner/**",
|
||||
".github/workflows/e2e-runner.yml",
|
||||
...SHARED_DB_AND_HELPER_PATHS,
|
||||
],
|
||||
},
|
||||
"scanner-file-operations": {
|
||||
id: "scanner-file-operations",
|
||||
name: "Scanner File Operations",
|
||||
timeout: 60,
|
||||
lane: "full",
|
||||
description: "Scanner file operation matrix",
|
||||
vitestTarget: "test/scanner-file-operations.e2e-spec.ts",
|
||||
junitOutput: `${TEST_RESULTS_DIR}/scanner-file-operations-e2e-junit.xml`,
|
||||
prepareDedicatedDatabase: true,
|
||||
useDedicatedDatabase: true,
|
||||
changedPaths: [
|
||||
"server/src/modules/scanner/**",
|
||||
"server/test/scanner-file-operations.e2e-spec.ts",
|
||||
"server/test/e2e/scanner/**",
|
||||
".github/workflows/e2e-runner.yml",
|
||||
...SHARED_DB_AND_HELPER_PATHS,
|
||||
],
|
||||
},
|
||||
"auth-session-security": {
|
||||
id: "auth-session-security",
|
||||
name: "Auth Session Security",
|
||||
timeout: 50,
|
||||
lane: "full",
|
||||
description: "Auth session security suite",
|
||||
vitestTarget: "test/auth-session-security.e2e-spec.ts",
|
||||
junitOutput: `${TEST_RESULTS_DIR}/auth-session-security-e2e-junit.xml`,
|
||||
prepareDedicatedDatabase: true,
|
||||
useDedicatedDatabase: true,
|
||||
changedPaths: [
|
||||
"server/src/modules/auth/**",
|
||||
"server/src/modules/user/**",
|
||||
"server/src/modules/role/**",
|
||||
"server/src/common/guards/**",
|
||||
"server/src/common/decorators/**",
|
||||
"server/src/common/types/**",
|
||||
"server/test/auth-session-security.e2e-spec.ts",
|
||||
"server/test/e2e/app-harness.ts",
|
||||
".github/workflows/e2e-runner.yml",
|
||||
...SHARED_AUTH_TYPE_PATHS,
|
||||
...SHARED_DB_AND_HELPER_PATHS,
|
||||
],
|
||||
},
|
||||
"auth-recovery-oidc-logout": {
|
||||
id: "auth-recovery-oidc-logout",
|
||||
name: "Auth Recovery OIDC Logout",
|
||||
timeout: 50,
|
||||
lane: "full",
|
||||
description: "Auth recovery and OIDC logout suite",
|
||||
vitestTarget: "test/auth-recovery-oidc-logout.e2e-spec.ts",
|
||||
junitOutput: `${TEST_RESULTS_DIR}/auth-recovery-oidc-logout-e2e-junit.xml`,
|
||||
prepareDedicatedDatabase: true,
|
||||
useDedicatedDatabase: true,
|
||||
changedPaths: [
|
||||
"server/src/modules/auth/**",
|
||||
"server/src/modules/user/**",
|
||||
"server/src/modules/email/**",
|
||||
"server/src/modules/metadata/**",
|
||||
"server/test/auth-recovery-oidc-logout.e2e-spec.ts",
|
||||
"server/test/e2e/app-harness.ts",
|
||||
".github/workflows/e2e-runner.yml",
|
||||
"packages/types/src/auth.ts",
|
||||
...SHARED_DB_AND_HELPER_PATHS,
|
||||
],
|
||||
},
|
||||
"book-bucket-ingest-finalize": {
|
||||
id: "book-bucket-ingest-finalize",
|
||||
name: "Book Bucket Ingest Finalize",
|
||||
timeout: 45,
|
||||
lane: "full",
|
||||
description: "Book Bucket ingest and finalize suite",
|
||||
vitestTarget: "test/book-bucket-ingest-finalize.e2e-spec.ts",
|
||||
junitOutput: `${TEST_RESULTS_DIR}/book-bucket-ingest-finalize-e2e-junit.xml`,
|
||||
prepareDedicatedDatabase: true,
|
||||
useDedicatedDatabase: true,
|
||||
changedPaths: [
|
||||
"server/src/modules/book-bucket/**",
|
||||
"server/src/modules/upload/**",
|
||||
"server/src/modules/metadata/**",
|
||||
"server/test/book-bucket-ingest-finalize.e2e-spec.ts",
|
||||
"server/test/e2e/book-bucket/**",
|
||||
...SHARED_DB_AND_HELPER_PATHS,
|
||||
],
|
||||
},
|
||||
"metadata-write": {
|
||||
id: "metadata-write",
|
||||
name: "Metadata Write",
|
||||
timeout: 60,
|
||||
lane: "full",
|
||||
description: "Metadata write operations suite",
|
||||
vitestTarget: "test/metadata-write.e2e-spec.ts",
|
||||
junitOutput: `${TEST_RESULTS_DIR}/metadata-write-e2e-junit.xml`,
|
||||
prepareDedicatedDatabase: true,
|
||||
useDedicatedDatabase: true,
|
||||
changedPaths: [
|
||||
"server/src/modules/book/**",
|
||||
"server/src/modules/library/**",
|
||||
"server/src/modules/file-write/**",
|
||||
"server/src/modules/metadata/**",
|
||||
"server/src/modules/app-settings/**",
|
||||
"server/test/metadata-write.e2e-spec.ts",
|
||||
"server/test/e2e/metadata-write/**",
|
||||
"packages/types/src/file-write.ts",
|
||||
...SHARED_DB_AND_HELPER_PATHS,
|
||||
],
|
||||
},
|
||||
"metadata-lock": {
|
||||
id: "metadata-lock",
|
||||
name: "Metadata Lock",
|
||||
timeout: 60,
|
||||
lane: "full",
|
||||
description: "Metadata lock end-to-end suite",
|
||||
vitestTarget: "test/metadata-lock.e2e-spec.ts",
|
||||
junitOutput: `${TEST_RESULTS_DIR}/metadata-lock-e2e-junit.xml`,
|
||||
prepareDedicatedDatabase: true,
|
||||
useDedicatedDatabase: true,
|
||||
changedPaths: [
|
||||
"server/src/modules/book/**",
|
||||
"server/src/modules/book-metadata-lock/**",
|
||||
"server/src/modules/book-metadata-fetch/**",
|
||||
"server/src/modules/metadata-fetch/**",
|
||||
"server/src/modules/metadata-preferences/**",
|
||||
"server/src/modules/cover/**",
|
||||
"server/src/modules/scanner/**",
|
||||
"server/src/modules/library/**",
|
||||
"server/src/modules/auth/**",
|
||||
"server/src/modules/app-settings/**",
|
||||
"server/test/metadata-lock.e2e-spec.ts",
|
||||
"server/test/e2e/metadata-lock/**",
|
||||
".github/workflows/e2e-runner.yml",
|
||||
...SHARED_TYPES_PATHS,
|
||||
...SHARED_DB_AND_HELPER_PATHS,
|
||||
],
|
||||
},
|
||||
"migration-booklore": {
|
||||
id: "migration-booklore",
|
||||
name: "Migration Booklore",
|
||||
timeout: 70,
|
||||
lane: "full",
|
||||
description: "Booklore migration end-to-end suite",
|
||||
vitestTarget: "test/migration-booklore.e2e-spec.ts",
|
||||
junitOutput: `${TEST_RESULTS_DIR}/migration-booklore-e2e-junit.xml`,
|
||||
prepareDedicatedDatabase: true,
|
||||
useDedicatedDatabase: true,
|
||||
changedPaths: [
|
||||
"server/src/modules/migration/**",
|
||||
"server/test/migration-booklore.e2e-spec.ts",
|
||||
"server/test/e2e/migration-booklore/**",
|
||||
".github/workflows/e2e-runner.yml",
|
||||
...SHARED_DB_AND_HELPER_PATHS,
|
||||
],
|
||||
},
|
||||
"authorization-matrix": {
|
||||
id: "authorization-matrix",
|
||||
name: "Authorization Matrix",
|
||||
timeout: 60,
|
||||
lane: "full",
|
||||
description: "Authorization matrix suite",
|
||||
vitestTarget: "test/authorization-matrix.e2e-spec.ts",
|
||||
junitOutput: `${TEST_RESULTS_DIR}/authorization-matrix-e2e-junit.xml`,
|
||||
prepareDedicatedDatabase: true,
|
||||
useDedicatedDatabase: true,
|
||||
changedPaths: [
|
||||
"server/src/**",
|
||||
"server/test/authorization-matrix.e2e-spec.ts",
|
||||
"server/test/e2e/authorization-matrix/**",
|
||||
".github/workflows/e2e-runner.yml",
|
||||
...SHARED_TYPES_PATHS,
|
||||
...SHARED_DB_AND_HELPER_PATHS,
|
||||
],
|
||||
},
|
||||
"book-api-contract": {
|
||||
id: "book-api-contract",
|
||||
name: "Book API Contract",
|
||||
timeout: 60,
|
||||
lane: "full",
|
||||
description: "Book API contract suite",
|
||||
vitestTarget: "test/book-api-contract.e2e-spec.ts",
|
||||
junitOutput: `${TEST_RESULTS_DIR}/book-api-contract-e2e-junit.xml`,
|
||||
prepareDedicatedDatabase: true,
|
||||
useDedicatedDatabase: true,
|
||||
changedPaths: [
|
||||
"server/src/modules/book/**",
|
||||
"server/src/modules/library/**",
|
||||
"server/src/modules/collection/**",
|
||||
"server/src/modules/auth/**",
|
||||
"server/src/modules/user/**",
|
||||
"server/test/book-api-contract.e2e-spec.ts",
|
||||
"server/test/e2e/book-api-contract/**",
|
||||
...SHARED_TYPES_PATHS,
|
||||
...SHARED_DB_AND_HELPER_PATHS,
|
||||
],
|
||||
},
|
||||
"app-settings-oidc-contract": {
|
||||
id: "app-settings-oidc-contract",
|
||||
name: "App Settings OIDC Contract",
|
||||
timeout: 60,
|
||||
lane: "full",
|
||||
description: "App settings OIDC contract suite",
|
||||
vitestTarget: "test/app-settings-oidc-contract.e2e-spec.ts",
|
||||
junitOutput: `${TEST_RESULTS_DIR}/app-settings-oidc-contract-e2e-junit.xml`,
|
||||
prepareDedicatedDatabase: true,
|
||||
useDedicatedDatabase: true,
|
||||
changedPaths: [
|
||||
"server/src/modules/app-settings/**",
|
||||
"server/src/modules/auth/**",
|
||||
"server/src/modules/user/**",
|
||||
"server/src/modules/metadata/**",
|
||||
"server/test/app-settings-oidc-contract.e2e-spec.ts",
|
||||
"server/test/e2e/app-settings/**",
|
||||
...SHARED_TYPES_PATHS,
|
||||
...SHARED_DB_AND_HELPER_PATHS,
|
||||
],
|
||||
},
|
||||
"library-admin-workflows": {
|
||||
id: "library-admin-workflows",
|
||||
name: "Library Admin Workflows",
|
||||
timeout: 60,
|
||||
lane: "full",
|
||||
description: "Library admin workflows suite",
|
||||
vitestTarget: "test/library-admin-workflows.e2e-spec.ts",
|
||||
junitOutput: `${TEST_RESULTS_DIR}/library-admin-workflows-e2e-junit.xml`,
|
||||
prepareDedicatedDatabase: true,
|
||||
useDedicatedDatabase: true,
|
||||
changedPaths: [
|
||||
"server/src/modules/library/**",
|
||||
"server/src/modules/auth/**",
|
||||
"server/src/modules/user/**",
|
||||
"server/src/modules/role/**",
|
||||
"server/test/library-admin-workflows.e2e-spec.ts",
|
||||
"server/test/e2e/library/**",
|
||||
...SHARED_TYPES_PATHS,
|
||||
...SHARED_DB_AND_HELPER_PATHS,
|
||||
],
|
||||
},
|
||||
"reader-format-delivery": {
|
||||
id: "reader-format-delivery",
|
||||
name: "Reader Format Delivery",
|
||||
timeout: 60,
|
||||
lane: "full",
|
||||
description: "Reader format delivery suite",
|
||||
vitestTarget: "test/reader-format-delivery.e2e-spec.ts",
|
||||
junitOutput: `${TEST_RESULTS_DIR}/reader-format-delivery-e2e-junit.xml`,
|
||||
prepareDedicatedDatabase: true,
|
||||
useDedicatedDatabase: true,
|
||||
changedPaths: [
|
||||
"server/src/modules/book/**",
|
||||
"server/src/modules/library/**",
|
||||
"server/src/modules/reader-preferences/**",
|
||||
"server/src/modules/annotation/**",
|
||||
"server/src/modules/bookmark/**",
|
||||
"server/test/reader-format-delivery.e2e-spec.ts",
|
||||
"server/test/e2e/reader-format-delivery/**",
|
||||
"packages/types/src/reader-settings.ts",
|
||||
...SHARED_DB_AND_HELPER_PATHS,
|
||||
],
|
||||
},
|
||||
"opds-auth-catalog": {
|
||||
id: "opds-auth-catalog",
|
||||
name: "OPDS Auth Catalog",
|
||||
timeout: 45,
|
||||
lane: "full",
|
||||
description: "OPDS auth and catalog suite",
|
||||
vitestTarget: "test/opds-auth-catalog.e2e-spec.ts",
|
||||
junitOutput: `${TEST_RESULTS_DIR}/opds-auth-catalog-e2e-junit.xml`,
|
||||
prepareDedicatedDatabase: true,
|
||||
useDedicatedDatabase: true,
|
||||
changedPaths: [
|
||||
"server/src/modules/opds/**",
|
||||
"server/src/modules/book/**",
|
||||
"server/src/modules/library/**",
|
||||
"server/src/modules/collection/**",
|
||||
"server/src/modules/lens/**",
|
||||
"server/src/modules/user/**",
|
||||
"server/src/modules/auth/**",
|
||||
"server/src/modules/app-settings/**",
|
||||
"server/test/opds-auth-catalog.e2e-spec.ts",
|
||||
"server/test/e2e/opds/**",
|
||||
"server/test/e2e/**",
|
||||
".github/workflows/e2e-runner.yml",
|
||||
...SHARED_TYPES_PATHS,
|
||||
...SHARED_DB_AND_HELPER_PATHS,
|
||||
],
|
||||
},
|
||||
"email-lifecycle": {
|
||||
id: "email-lifecycle",
|
||||
name: "Email Lifecycle",
|
||||
timeout: 45,
|
||||
lane: "full",
|
||||
description: "Email lifecycle suite",
|
||||
vitestTarget: "test/email-lifecycle.e2e-spec.ts",
|
||||
junitOutput: `${TEST_RESULTS_DIR}/email-lifecycle-e2e-junit.xml`,
|
||||
prepareDedicatedDatabase: true,
|
||||
useDedicatedDatabase: true,
|
||||
changedPaths: [
|
||||
"server/src/modules/email/**",
|
||||
"server/src/modules/book/**",
|
||||
"server/src/modules/library/**",
|
||||
"server/src/modules/auth/**",
|
||||
"server/src/modules/cover/**",
|
||||
"server/src/modules/scanner/**",
|
||||
"server/src/modules/seed/**",
|
||||
"server/test/email-lifecycle.e2e-spec.ts",
|
||||
"server/test/e2e/email/**",
|
||||
"server/test/e2e/**",
|
||||
"client/src/features/email/**",
|
||||
"client/src/router/index.ts",
|
||||
"client/src/features/settings/SettingsHeader.vue",
|
||||
"client/src/views/HomeView.vue",
|
||||
"client/src/views/CollectionView.vue",
|
||||
"client/src/views/LensView.vue",
|
||||
"client/src/features/book/components/BookCoverCard.vue",
|
||||
"client/src/features/book/components/BookListRow.vue",
|
||||
...SHARED_TYPES_PATHS,
|
||||
...SHARED_DB_AND_HELPER_PATHS,
|
||||
],
|
||||
},
|
||||
"reader-state-isolation": {
|
||||
id: "reader-state-isolation",
|
||||
name: "Reader State Isolation",
|
||||
timeout: 45,
|
||||
lane: "full",
|
||||
description: "Reader state isolation suite",
|
||||
vitestTarget: "test/reader-state-isolation.e2e-spec.ts",
|
||||
junitOutput: `${TEST_RESULTS_DIR}/reader-state-isolation-e2e-junit.xml`,
|
||||
prepareDedicatedDatabase: true,
|
||||
useDedicatedDatabase: true,
|
||||
changedPaths: [
|
||||
"server/src/modules/bookmark/**",
|
||||
"server/src/modules/annotation/**",
|
||||
"server/src/modules/reader-preferences/**",
|
||||
"server/src/modules/reading-session/**",
|
||||
"server/src/modules/book/**",
|
||||
"server/src/modules/library/**",
|
||||
"server/src/modules/scanner/**",
|
||||
"server/src/modules/user-book-status/**",
|
||||
"server/src/scripts/db-prepare-e2e.ts",
|
||||
"server/test/reader-state-isolation.e2e-spec.ts",
|
||||
"server/test/e2e/reader-state-isolation/**",
|
||||
"packages/types/src/reader-settings.ts",
|
||||
...SHARED_DB_AND_HELPER_PATHS,
|
||||
],
|
||||
},
|
||||
"users-admin-lifecycle": {
|
||||
id: "users-admin-lifecycle",
|
||||
name: "Users Admin Lifecycle",
|
||||
timeout: 50,
|
||||
lane: "full",
|
||||
description: "Users admin lifecycle suite",
|
||||
vitestTarget: "test/users-admin-lifecycle.e2e-spec.ts",
|
||||
junitOutput: `${TEST_RESULTS_DIR}/users-admin-lifecycle-e2e-junit.xml`,
|
||||
prepareDedicatedDatabase: true,
|
||||
useDedicatedDatabase: true,
|
||||
changedPaths: [
|
||||
"server/src/modules/user/**",
|
||||
"server/src/modules/auth/**",
|
||||
"server/src/common/guards/**",
|
||||
"server/src/common/decorators/**",
|
||||
"server/src/common/types/**",
|
||||
"server/test/users-admin-lifecycle.e2e-spec.ts",
|
||||
"server/test/e2e/users-admin-lifecycle/**",
|
||||
".github/workflows/e2e-runner.yml",
|
||||
...SHARED_AUTH_TYPE_PATHS,
|
||||
...SHARED_DB_AND_HELPER_PATHS,
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { listE2ESuites } from "./e2e-suites.mjs";
|
||||
|
||||
process.stdout.write(
|
||||
JSON.stringify(listE2ESuites().map(({ id, name, timeout }) => ({ id, name, timeout })))
|
||||
);
|
||||
process.stdout.write(JSON.stringify(listE2ESuites().map(({ id, name, timeout }) => ({ id, name, timeout }))));
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { matchesGlob } from "node:path";
|
||||
import process from "node:process";
|
||||
|
||||
import { E2E_GLOBAL_CHANGE_PATHS, listE2ESuites } from "./e2e-suites.mjs";
|
||||
|
||||
const ZERO_SHA_PATTERN = /^0+$/;
|
||||
|
||||
function git(args) {
|
||||
return execFileSync("git", args, { encoding: "utf8" }).trim();
|
||||
}
|
||||
|
||||
function hasCommit(sha) {
|
||||
if (!sha || ZERO_SHA_PATTERN.test(sha)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
git(["cat-file", "-e", `${sha}^{commit}`]);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function getChangedFiles(eventName, baseSha, headSha) {
|
||||
if (eventName !== "pull_request") {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!hasCommit(baseSha) || !hasCommit(headSha)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const diffRange = `${baseSha}...${headSha}`;
|
||||
const output = git(["diff", "--name-only", diffRange]);
|
||||
return output === "" ? [] : output.split("\n").filter(Boolean);
|
||||
}
|
||||
|
||||
function fileMatchesAnyPattern(filePath, patterns) {
|
||||
return patterns.some((pattern) => matchesGlob(filePath, pattern));
|
||||
}
|
||||
|
||||
function suiteMatchesChangedFiles(suite, changedFiles) {
|
||||
if (!Array.isArray(suite.changedPaths) || suite.changedPaths.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return changedFiles.some((filePath) => fileMatchesAnyPattern(filePath, suite.changedPaths));
|
||||
}
|
||||
|
||||
function toMatrixItems(suites) {
|
||||
return suites.map(({ id, name, timeout }) => ({ id, name, timeout }));
|
||||
}
|
||||
|
||||
function selectSuitesForEvent(eventName, changedFiles) {
|
||||
const suites = listE2ESuites();
|
||||
|
||||
if (eventName === "workflow_dispatch" || eventName === "push" || eventName === "schedule") {
|
||||
return toMatrixItems(suites);
|
||||
}
|
||||
|
||||
if (eventName !== "pull_request") {
|
||||
return toMatrixItems(suites);
|
||||
}
|
||||
|
||||
const smokeSuites = suites.filter((suite) => suite.lane === "smoke");
|
||||
if (!changedFiles || changedFiles.length === 0) {
|
||||
return changedFiles === null ? toMatrixItems(smokeSuites) : [];
|
||||
}
|
||||
|
||||
if (changedFiles.some((filePath) => fileMatchesAnyPattern(filePath, E2E_GLOBAL_CHANGE_PATHS))) {
|
||||
return toMatrixItems(smokeSuites);
|
||||
}
|
||||
|
||||
return toMatrixItems(smokeSuites.filter((suite) => suiteMatchesChangedFiles(suite, changedFiles)));
|
||||
}
|
||||
|
||||
const eventName = process.env.GITHUB_EVENT_NAME ?? "";
|
||||
const baseSha = process.env.GITHUB_BASE_SHA ?? "";
|
||||
const headSha = process.env.GITHUB_HEAD_SHA ?? "";
|
||||
const changedFiles = getChangedFiles(eventName, baseSha, headSha);
|
||||
const selectedSuites = selectSuitesForEvent(eventName, changedFiles);
|
||||
|
||||
process.stdout.write(JSON.stringify(selectedSuites));
|
||||
Reference in New Issue
Block a user