test(release): pin collapsed Safari guidance #1865
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] | |
| pull_request: | |
| branches: [main] | |
| permissions: | |
| contents: read | |
| # Cancel in-progress runs for the same PR / branch | |
| concurrency: | |
| group: ci-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| # ── Change detection ────────────────────────────────────────────────── | |
| # Routes jobs by changed paths so docs-only changes skip extension and | |
| # native builds. `core` is a catch-all: any file NOT classified as docs, | |
| # app, native, or repo prose (markdown/meta) triggers the full CI. | |
| changes: | |
| name: Changes | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| outputs: | |
| docs: ${{ steps.filter.outputs.docs }} | |
| app: ${{ steps.filter.outputs.app }} | |
| native: ${{ steps.filter.outputs.native }} | |
| core: ${{ steps.catchall.outputs.core }} | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| persist-credentials: false | |
| - id: filter | |
| uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 | |
| with: | |
| filters: | | |
| docs: | |
| - 'docs/**' | |
| - 'CNAME' | |
| - 'README.md' | |
| - '.github/README_*.md' | |
| - '.github/workflows/deploy-docs.yml' | |
| - '.github/workflows/sponsors.yml' | |
| - 'scripts/generate-sponsors.cjs' | |
| - 'scripts/generate-sponsors.test.js' | |
| - 'scripts/update-readme-badges.mjs' | |
| - 'scripts/update-readme-badges.test.js' | |
| - 'sponsorkit/**' | |
| app: | |
| - 'src/**' | |
| - 'public/**' | |
| # Read by src/pages/popup/__tests__/releaseArtifacts.test.ts | |
| - '.github/RELEASE_TEMPLATE.md' | |
| native: | |
| - 'Voyager/**' | |
| # Everything not matched below forces the full CI (build configs, | |
| # workflows, scripts, dependencies, and any unclassified new file). | |
| - id: catchall | |
| uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 | |
| with: | |
| predicate-quantifier: 'every' | |
| filters: | | |
| core: | |
| - '!docs/**' | |
| - '!CNAME' | |
| - '!src/**' | |
| - '!public/**' | |
| - '!Voyager/**' | |
| - '!.github/workflows/deploy-docs.yml' | |
| - '!.github/workflows/sponsors.yml' | |
| - '!scripts/generate-sponsors.cjs' | |
| - '!scripts/generate-sponsors.test.js' | |
| - '!scripts/update-readme-badges.mjs' | |
| - '!scripts/update-readme-badges.test.js' | |
| - '!sponsorkit/**' | |
| - '!**/*.md' | |
| - '!LICENSE' | |
| - '!.gitignore' | |
| - '!.gitattributes' | |
| # ── Prettier ─────────────────────────────────────────────────────────── | |
| format: | |
| name: Format | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| persist-credentials: false | |
| - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 | |
| with: | |
| bun-version: '1.3.12' | |
| - run: bun install --frozen-lockfile | |
| - run: bun run format:check | |
| # ── ESLint ───────────────────────────────────────────────────────────── | |
| lint: | |
| name: Lint | |
| needs: changes | |
| if: needs.changes.outputs.app == 'true' || needs.changes.outputs.core == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| persist-credentials: false | |
| - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 | |
| with: | |
| bun-version: '1.3.12' | |
| - run: bun install --frozen-lockfile | |
| - run: bun run lint:check | |
| # ── TypeScript ───────────────────────────────────────────────────────── | |
| typecheck: | |
| name: Typecheck | |
| needs: changes | |
| if: needs.changes.outputs.app == 'true' || needs.changes.outputs.core == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| persist-credentials: false | |
| - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 | |
| with: | |
| bun-version: '1.3.12' | |
| - run: bun install --frozen-lockfile | |
| - run: bun run typecheck | |
| # ── Tests ────────────────────────────────────────────────────────────── | |
| test: | |
| name: Test | |
| needs: changes | |
| if: needs.changes.outputs.app == 'true' || needs.changes.outputs.core == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| persist-credentials: false | |
| - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 | |
| with: | |
| bun-version: '1.3.12' | |
| - run: bun install --frozen-lockfile | |
| - run: bun run test | |
| # ── Native Swift Tests + Xcode Build ───────────────────────────────── | |
| # app-only (src/public) changes skip this: the Safari web bundle is already | |
| # packaging-verified by the Build (safari) matrix job on ubuntu; this job's | |
| # extra coverage (swift test + xcodebuild app assembly) only varies with | |
| # native code or build configs. | |
| native-test: | |
| name: Native Swift Test & Xcode Build | |
| needs: changes | |
| if: >- | |
| needs.changes.outputs.native == 'true' || | |
| needs.changes.outputs.core == 'true' | |
| runs-on: macos-latest | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| persist-credentials: false | |
| - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 | |
| with: | |
| bun-version: '1.3.12' | |
| - run: bun install --frozen-lockfile | |
| - run: bun run build:safari | |
| - run: swift test --package-path "Voyager" | |
| - run: >- | |
| xcodebuild | |
| -project "Voyager/Voyager.xcodeproj" | |
| -scheme "Voyager" | |
| -configuration Debug | |
| -destination "platform=macOS" | |
| -derivedDataPath .build/ci-xcode-derived | |
| CODE_SIGNING_ALLOWED=NO | |
| build | |
| # ── Build (all browsers) ────────────────────────────────────────────── | |
| build: | |
| name: Build | |
| needs: changes | |
| if: needs.changes.outputs.app == 'true' || needs.changes.outputs.core == 'true' | |
| runs-on: ubuntu-latest | |
| strategy: | |
| matrix: | |
| browser: [chrome, edge, firefox, safari] | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| persist-credentials: false | |
| - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 | |
| with: | |
| bun-version: '1.3.12' | |
| - run: bun install --frozen-lockfile | |
| - run: bun run build:${{ matrix.browser }} | |
| # ── Docs build ──────────────────────────────────────────────────────── | |
| docs-build: | |
| name: Docs Build | |
| needs: changes | |
| if: needs.changes.outputs.docs == 'true' || needs.changes.outputs.core == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| persist-credentials: false | |
| - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 | |
| with: | |
| bun-version: '1.3.12' | |
| - run: bun install --frozen-lockfile | |
| - name: Test docs tooling | |
| run: | | |
| node scripts/update-readme-badges.mjs --self-test | |
| bun run test scripts/generate-sponsors.test.js scripts/update-readme-badges.test.js | |
| - run: bun run docs:build | |
| # ── i18n consistency ────────────────────────────────────────────────── | |
| i18n: | |
| name: i18n | |
| needs: changes | |
| if: needs.changes.outputs.app == 'true' || needs.changes.outputs.core == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| persist-credentials: false | |
| - name: Check locale key consistency | |
| run: node scripts/check-locale-keys.mjs | |
| # ── PR size label ───────────────────────────────────────────────────── | |
| pr-size: | |
| name: PR Size | |
| # Skip on fork PRs — GITHUB_TOKEN lacks write permission for labels | |
| if: >- | |
| github.event_name == 'pull_request' && | |
| github.event.pull_request.head.repo.full_name == github.repository | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| steps: | |
| - name: Label PR by changed lines | |
| uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 | |
| with: | |
| script: | | |
| const { owner, repo } = context.repo; | |
| const pr = context.payload.pull_request.number; | |
| const { data: files } = await github.rest.pulls.listFiles({ owner, repo, pull_number: pr, per_page: 100 }); | |
| const changes = files.reduce((sum, f) => sum + f.changes, 0); | |
| let label; | |
| if (changes < 50) label = 'size/S'; | |
| else if (changes < 200) label = 'size/M'; | |
| else if (changes < 500) label = 'size/L'; | |
| else label = 'size/XL'; | |
| // Remove old size labels | |
| const { data: existing } = await github.rest.issues.listLabelsOnIssue({ owner, repo, issue_number: pr }); | |
| for (const l of existing) { | |
| if (l.name.startsWith('size/')) { | |
| await github.rest.issues.removeLabel({ owner, repo, issue_number: pr, name: l.name }); | |
| } | |
| } | |
| await github.rest.issues.addLabels({ owner, repo, issue_number: pr, labels: [label] }); | |
| core.info(`PR #${pr}: ${changes} changed lines → ${label}`); | |
| # ── Summary (required-check anchor) ─────────────────────────────────── | |
| # Always runs, so branch protection can require this single check even | |
| # when path filtering skips individual jobs. Skipped jobs pass; any | |
| # failed or cancelled job fails the summary. | |
| ci-summary: | |
| name: CI Summary | |
| if: always() | |
| needs: [changes, format, lint, typecheck, test, native-test, build, docs-build, i18n] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Verify job results | |
| env: | |
| NEEDS_JSON: ${{ toJSON(needs) }} | |
| run: | | |
| echo "$NEEDS_JSON" | |
| if echo "$NEEDS_JSON" | grep -Eq '"result": "(failure|cancelled)"'; then | |
| echo "One or more CI jobs failed or were cancelled." | |
| exit 1 | |
| fi | |
| echo "All CI jobs succeeded or were skipped by path filtering." |