refactor: consume shared rules from @exadev/eslint-config instead of … #54
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: | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| jobs: | |
| commitlint: | |
| name: Commitlint | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| - uses: wagoid/commitlint-github-action@v6 | |
| lint: | |
| name: Lint | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: pnpm/action-setup@v6 | |
| - uses: actions/setup-node@v7 | |
| with: | |
| node-version: '22' | |
| cache: pnpm | |
| - run: pnpm install --frozen-lockfile | |
| - run: pnpm lint | |
| typecheck: | |
| name: Typecheck | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: pnpm/action-setup@v6 | |
| - uses: actions/setup-node@v7 | |
| with: | |
| node-version: '22' | |
| cache: pnpm | |
| - run: pnpm install --frozen-lockfile | |
| - run: pnpm typecheck | |
| # attw (are-the-types-wrong) needs the built dist/, so build first. Mirrors the prepublishOnly check, now enforced on every push rather than only at publish. | |
| - run: pnpm build | |
| - run: pnpm exec attw --pack | |
| test: | |
| name: Test | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| permissions: | |
| contents: read | |
| code-quality: write # to upload the cobertura coverage report below | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: pnpm/action-setup@v6 | |
| - uses: actions/setup-node@v7 | |
| with: | |
| node-version: '22' | |
| cache: pnpm | |
| - run: pnpm install --frozen-lockfile | |
| - run: pnpm test:coverage | |
| # Cloudflare Workers (workerd) runtime test -- pnpm install above already built the workerd binary (allowBuilds in pnpm-workspace.yaml), enforcing zero Node-only API usage on the tested paths at runtime. | |
| - run: pnpm test:workers | |
| - name: Upload coverage report | |
| # Code Quality requires the org on GitHub Team/Enterprise Cloud, which ExaDev is not yet on, so the upload call itself will fail until that changes -- fail-on-error: false keeps that failure a log annotation instead of gating the release job below on a feature we can't turn on yet. Also guarded against fork PRs, which never hold the code-quality: write permission to upload. | |
| if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository | |
| uses: actions/upload-code-coverage@v1 | |
| with: | |
| file: coverage/cobertura-coverage.xml | |
| language: typescript | |
| label: unit | |
| fail-on-error: false | |
| test-smoke: | |
| name: Smoke test | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: pnpm/action-setup@v6 | |
| - uses: actions/setup-node@v7 | |
| with: | |
| node-version: '22' | |
| cache: pnpm | |
| - run: pnpm install --frozen-lockfile | |
| - run: pnpm test:smoke | |
| release: | |
| name: Release | |
| needs: [commitlint, lint, typecheck, test, test-smoke] | |
| if: github.ref == 'refs/heads/main' && github.event_name == 'push' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| permissions: | |
| contents: write # to push the release commit/tag and create the GitHub Release | |
| issues: write # to comment on released issues | |
| pull-requests: write # to comment on released pull requests | |
| id-token: write # OIDC identity for npm trusted publishing (no NPM_TOKEN) | |
| outputs: | |
| published: ${{ steps.before.outputs.version != steps.after.outputs.version }} | |
| version: ${{ steps.after.outputs.version }} | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| # semantic-release analyses the full commit history since the last release. | |
| fetch-depth: 0 | |
| - uses: pnpm/action-setup@v6 | |
| - uses: actions/setup-node@v7 | |
| with: | |
| node-version: '22' | |
| cache: pnpm | |
| # registry-url is deliberately absent. Setting it makes setup-node write an .npmrc containing an _authToken line, and that line wins over the OIDC token exchange -- so the setting that looks like it configures the registry is exactly the one that would stop trusted publishing working. | |
| - run: pnpm install --frozen-lockfile | |
| - name: Read pre-release version | |
| id: before | |
| run: echo "version=$(node -p "require('./package.json').version")" >> "$GITHUB_OUTPUT" | |
| - name: Upgrade npm for OIDC trusted publishing (needs npm CLI >=11.5.1) | |
| run: npm install -g npm@latest | |
| - name: Release | |
| # HUSKY=0 so the commit-msg hook never fires against the automated release commit. | |
| run: HUSKY=0 pnpm exec semantic-release | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # Blanked, not omitted -- an inherited NPM_TOKEN/NODE_AUTH_TOKEN from a workflow-level env block, reusable workflow, or composite action would otherwise be used in preference to the OIDC exchange. | |
| NPM_TOKEN: '' | |
| NODE_AUTH_TOKEN: '' | |
| - name: Read post-release version | |
| id: after | |
| run: echo "version=$(node -p "require('./package.json').version")" >> "$GITHUB_OUTPUT" | |
| notify-downstream: | |
| name: Notify downstream repositories | |
| needs: release | |
| if: needs.release.outputs.published == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Generate a token for cross-repo dispatch | |
| id: app-token | |
| uses: actions/create-github-app-token@v2 | |
| with: | |
| app-id: "4473709" | |
| private-key: ${{ secrets.AUTOMERGE_APP_PRIVATE_KEY }} | |
| repositories: | | |
| documents.js | |
| ooxml.js | |
| odf.js | |
| pdf-codec | |
| markdown-codec | |
| - name: Dispatch sibling-released event to downstream repos | |
| env: | |
| GH_TOKEN: ${{ steps.app-token.outputs.token }} | |
| run: | | |
| version="${{ needs.release.outputs.version }}" | |
| for repo in documents.js ooxml.js odf.js pdf-codec markdown-codec; do | |
| gh api "repos/ExaDev/$repo/dispatches" \ | |
| -f event_type=sibling-released \ | |
| -F "client_payload[package]=document-schema.js" \ | |
| -F "client_payload[version]=$version" | |
| done | |
| publish-aliases: | |
| name: Publish aliases (${{ matrix.name }}) | |
| needs: release | |
| if: needs.release.outputs.published == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| permissions: | |
| contents: read | |
| id-token: write # OIDC identity for npm trusted publishing -- every leg in this job targets npmjs.org. The GitHub Packages alias is a separate job below specifically so it never holds this permission: pnpm attempts an OIDC exchange whenever id-token: write is available regardless of target registry, and GitHub Packages has no such exchange to attempt -- confirmed the hard way when this leg used to share this job and failed with "401 Unauthorized - authentication token not provided" even with GITHUB_TOKEN correctly set, because the failed OIDC attempt never fell through to it. | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - { name: document-content-model, registry: 'https://registry.npmjs.org' } | |
| - { name: doc-model.js, registry: 'https://registry.npmjs.org' } | |
| - { name: doc-schema.js, registry: 'https://registry.npmjs.org' } | |
| - { name: document-schema, registry: 'https://registry.npmjs.org' } | |
| - { name: document-model.js, registry: 'https://registry.npmjs.org' } | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| ref: main # the release commit semantic-release just pushed | |
| - uses: pnpm/action-setup@v6 | |
| - uses: actions/setup-node@v7 | |
| with: | |
| node-version: '22' | |
| cache: pnpm | |
| # registry-url is deliberately absent. Setting it makes setup-node write an .npmrc containing an _authToken line, and that line wins over the OIDC exchange -- so the setting that looks like it configures the registry is exactly the one that would break trusted publishing. | |
| - run: pnpm install --frozen-lockfile | |
| - run: pnpm build | |
| - name: Rewrite package name for this alias | |
| # Rewriting the checked-out package.json rather than keeping a package.json per alias means no alias can drift away from the real package's metadata -- each matrix leg gets its own fresh runner and checkout, so there is no cross-contamination between legs. | |
| run: npm pkg set name="${{ matrix.name }}" | |
| - run: pnpm publish --access public --no-git-checks | |
| env: | |
| # Blanked, not omitted -- an inherited NODE_AUTH_TOKEN would otherwise be used in preference to the OIDC exchange, matching the release job's own convention above. | |
| NODE_AUTH_TOKEN: '' | |
| publish-github-packages: | |
| name: Publish alias to GitHub Packages | |
| needs: release | |
| if: needs.release.outputs.published == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| permissions: | |
| contents: read | |
| packages: write | |
| # Deliberately no id-token: write here -- see publish-aliases' own comment above for why this job is kept separate rather than folded into that matrix: GitHub Packages has no OIDC trusted-publishing exchange, and holding id-token: write makes pnpm attempt (and fail) that exchange anyway, which then breaks its fallback to the GITHUB_TOKEN this job actually authenticates with. | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| ref: main # the release commit semantic-release just pushed | |
| - uses: pnpm/action-setup@v6 | |
| - uses: actions/setup-node@v7 | |
| with: | |
| node-version: '22' | |
| cache: pnpm | |
| registry-url: 'https://npm.pkg.github.com' | |
| scope: '@exadev' | |
| - run: pnpm install --frozen-lockfile | |
| - run: pnpm build | |
| - name: Rewrite package name and registry for the GitHub Packages scope | |
| # publishConfig.registry has to be overridden too: it takes precedence over the .npmrc registry-url set by setup-node above, so without this the publish would silently target registry.npmjs.org instead. | |
| run: | | |
| npm pkg set name="@exadev/document-content-model" | |
| npm pkg set publishConfig.registry="https://npm.pkg.github.com" | |
| - run: pnpm publish --access public --no-git-checks | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| attest: | |
| name: Attest SBOM and build provenance | |
| needs: release | |
| if: needs.release.outputs.published == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| permissions: | |
| contents: read | |
| id-token: write | |
| attestations: write | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| ref: main # the release commit semantic-release just pushed | |
| - uses: pnpm/action-setup@v6 | |
| - uses: actions/setup-node@v7 | |
| with: | |
| node-version: '22' | |
| cache: pnpm | |
| - run: pnpm install --frozen-lockfile | |
| - run: pnpm build | |
| # Pack into a directory of its own, separate from dist/ (tsdown's raw build output). The attestation subject has to be the artefact that actually ships -- attesting dist/ itself would mix in files that never leave the repo, producing digests that match nothing a consumer can download. | |
| - run: pnpm pack --pack-destination release-artifact | |
| - run: pnpm sbom --sbom-format spdx --prod > release-artifact/sbom.spdx.json | |
| - name: Attest SBOM | |
| uses: actions/attest@v4 | |
| with: | |
| subject-path: release-artifact/*.tgz | |
| sbom-path: release-artifact/sbom.spdx.json | |
| - name: Attest build provenance | |
| uses: actions/attest@v4 | |
| with: | |
| subject-path: release-artifact/*.tgz |