* chore: move supply-chain settings to a protected config file Use the `include` setting from pnpm 12.15 to move the settings that decide what gets installed and what may run into .config/pnpm/security.yaml: allowBuilds, auditConfig, the minimumReleaseAge and trustPolicy settings, overrides, packageExtensions, patchedDependencies, patchesDir, and registries. pnpm-workspace.yaml delegates exactly that list, so no other file can define those settings. Patch paths are relative to the included file. pnpm 12.14 ignores `include` and would drop every moved setting, so the packageManager pin moves to 12.15.0. CODEOWNERS now names both workspace files, the pnpmfile, and the patches explicitly, and the CI path filters watch the new file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: move routine settings to a file without a code owner The catalog, the update settings, and the task-runner settings (pipelines, tasks, concurrencyGroups) move to .config/pnpm/routine.yaml, which CODEOWNERS leaves unowned. Because pnpm-workspace.yaml delegates only that list to the file, any other setting defined there fails with ERR_PNPM_SETTING_NOT_DELEGATED, so the file cannot be used to change the protected settings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: move the catalogs to their own file .config/pnpm/catalog.yaml owns `catalogs`, which covers the default catalog and any named catalog added later. Like routine.yaml, it has no code owner. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: name the protected file policies.yaml and give tasks their own file security.yaml becomes policies.yaml. The task-runner settings (pipelines, tasks, concurrencyGroups) move to tasks.yaml, and `update` joins the catalogs in catalog.yaml, since its ignoreDeps list holds back catalog entries. routine.yaml is gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: move catalogMode and catalogPrune to catalog.yaml Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: keep the patch list next to the patches patchedDependencies and patchesDir move to __patches__/patches.yaml, so the entries name the patch files without a directory prefix and patchesDir is `.`. CODEOWNERS already covers __patches__/. The CI path filters that did not watch __patches__/ now watch patches.yaml, since they watched the patch list when it lived in pnpm-workspace.yaml. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: move the patches to .config/pnpm/patches Every pnpm configuration file now lives under .config/pnpm, so each CI path filter watches `.config/pnpm/**` instead of listing the files. CODEOWNERS keeps the patches protected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
234 lines
9.5 KiB
YAML
234 lines
9.5 KiB
YAML
name: TS CI
|
|
|
|
on:
|
|
# Run push CI only on the default branch. A branch that lives in this repo
|
|
# fires both a `push` and a `pull_request` event for the same commit, and
|
|
# both runs report the `TS CI / Success` context to that commit. Branch
|
|
# protection then sees two results for the required check, so a cancellation
|
|
# or flake on the push side blocks the PR even when the pull_request run passed.
|
|
# Restricting push to `main` means PRs carry `TS CI / Success` only via
|
|
# their `pull_request` run (and the merge queue via `merge_group`); `main`
|
|
# is validated pre-merge by `merge_group` and re-checked post-merge here.
|
|
push:
|
|
branches:
|
|
- main
|
|
pull_request:
|
|
merge_group:
|
|
|
|
permissions:
|
|
contents: read # to fetch code (actions/checkout)
|
|
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.run_id }}
|
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
|
|
|
jobs:
|
|
changes:
|
|
name: TS CI / Detect Changes
|
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
|
permissions:
|
|
contents: read
|
|
pull-requests: read
|
|
outputs:
|
|
ts: ${{ steps.force.outputs.ts || steps.filter.outputs.ts }}
|
|
changesets: ${{ steps.filter.outputs.changesets }}
|
|
steps:
|
|
# In the merge queue the full suite must run: `TS CI / Compile & Lint`
|
|
# is itself a required check, and a skipped job never reports its
|
|
# context, which would leave the queue waiting forever. Forcing
|
|
# detection true also tests the merged result, which is the point of
|
|
# the queue.
|
|
- name: Force TS CI for merge queue
|
|
if: github.event_name == 'merge_group'
|
|
id: force
|
|
run: echo "ts=true" >> "$GITHUB_OUTPUT"
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
if: github.event_name != 'merge_group' && github.event_name != 'pull_request'
|
|
with:
|
|
persist-credentials: false
|
|
- uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3
|
|
if: github.event_name != 'merge_group'
|
|
id: filter
|
|
with:
|
|
# The TypeScript pnpm stack lives under pnpm11/, plus the
|
|
# workspace-root tooling that drives its build/lint/test.
|
|
filters: |
|
|
ts:
|
|
- 'pnpm11/**'
|
|
- '.meta-updater/**'
|
|
- '.config/pnpm/**'
|
|
- 'package.json'
|
|
- 'pnpm-workspace.yaml'
|
|
- 'pnpm-lock.yaml'
|
|
- '.pnpmfile.cjs'
|
|
- 'eslint.config.mjs'
|
|
- 'tsconfig.lint.json'
|
|
- 'cspell.json'
|
|
- '.github/workflows/ci.yml'
|
|
- '.github/workflows/test.yml'
|
|
- '.github/workflows/build-pnpr.yml'
|
|
- '.github/scripts/**'
|
|
# Skill scripts under .agents/ ship their own *.test.sh, run by the
|
|
# Compile & Lint job next to the publication scripts above.
|
|
- '.agents/skills/**/scripts/**'
|
|
# A pending change intent is validated by `pn lint`, which runs in
|
|
# Compile & Lint. Nothing under .changeset/ can affect a test, so
|
|
# it gates that job alone.
|
|
changesets:
|
|
- '.changeset/**'
|
|
|
|
compile-and-lint:
|
|
needs: changes
|
|
# Run when TypeScript-relevant files changed, or when the PR only touches
|
|
# .changeset/ — `pn lint` validates the pending change intents and their
|
|
# spelling, and a changeset-only PR skips every other job below. Every PR
|
|
# (same-repo or fork) is covered by its pull_request run, and push only
|
|
# fires on main, so there are no duplicate build jobs to guard against here.
|
|
if: ${{ !cancelled() && (needs.changes.outputs.ts == 'true' || needs.changes.outputs.changesets == 'true') }}
|
|
|
|
name: TS CI / Compile & Lint
|
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
|
|
|
steps:
|
|
- name: Checkout Commit
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
persist-credentials: false
|
|
- name: Initialize Rust before dependency installation
|
|
uses: $/.github/actions/rustup
|
|
with:
|
|
restore-cache: false
|
|
- name: Install pnpm
|
|
uses: pnpm/setup@a0881977cd8ea73b1ed212236c99ca10dcc80cf4 # v3.1.1
|
|
with:
|
|
install: false
|
|
- name: Test CI and release publication scripts
|
|
run: |
|
|
.github/scripts/npm-package-publication-state.test.sh
|
|
.github/scripts/npm-staged-publication.test.sh
|
|
.github/scripts/issue-fix-intent.test.sh
|
|
.github/scripts/docker-image-tags.test.sh
|
|
.agents/skills/pull-requests/scripts/resolve-pr-conflicts.test.sh
|
|
node --test .github/scripts/ts-test-scope.test.mjs .github/scripts/ts-test-chunks.test.mjs .github/scripts/assert-windows-crt-static.test.mjs .github/scripts/wait-for-pnpr.test.mjs
|
|
- name: Compile and lint TypeScript
|
|
run: pn pipeline ts-check --include-workspace-root --full --no-cache
|
|
- name: Package compiled artifacts
|
|
shell: bash
|
|
run: |
|
|
mapfile -d '' -t lib_dirs < <(find . -type d -name lib -not -path '*/node_modules/*' -print0)
|
|
mapfile -d '' -t tsbuildinfo_files < <(find . -name 'tsconfig.tsbuildinfo' -not -path '*/node_modules/*' -print0)
|
|
tar -czf compiled.tar.gz --exclude='node_modules' "${lib_dirs[@]}" "${tsbuildinfo_files[@]}" pnpm11/pnpm/dist
|
|
- name: Upload compiled artifacts
|
|
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
|
with:
|
|
name: compiled-packages
|
|
path: compiled.tar.gz
|
|
retention-days: 1
|
|
|
|
# Build the pnpr binaries once per OS via a per-OS reusable workflow (not a
|
|
# single matrix job) so each platform's test jobs gate only on their own
|
|
# build. `needs` can only target a whole job, never an individual matrix
|
|
# leg, so a matrix build would make the ubuntu tests wait for the slower
|
|
# windows build and vice versa.
|
|
# Every push to main builds them, including Rust-only pushes that skip the
|
|
# tests, so pull requests merged with the latest main find them cached.
|
|
build-pnpr-linux:
|
|
needs: changes
|
|
if: ${{ !cancelled() && (needs.changes.outputs.ts == 'true' || github.event_name == 'push') }}
|
|
name: TS CI / Build pnpr
|
|
uses: $/.github/workflows/build-pnpr.yml
|
|
with:
|
|
os: blacksmith-16vcpu-ubuntu-2404
|
|
|
|
build-pnpr-windows:
|
|
needs: changes
|
|
if: ${{ !cancelled() && (needs.changes.outputs.ts == 'true' || github.event_name == 'push') }}
|
|
name: TS CI / Build pnpr
|
|
uses: $/.github/workflows/build-pnpr.yml
|
|
with:
|
|
os: blacksmith-16vcpu-windows-2025
|
|
|
|
# Like the Windows chunks, the Linux chunks start while the Linux pnpr
|
|
# build runs and wait for it inside the job (wait_for_pnpr).
|
|
test:
|
|
name: TS CI / Test / ubuntu
|
|
needs: [changes, compile-and-lint]
|
|
if: ${{ !cancelled() && needs.changes.outputs.ts == 'true' && needs.compile-and-lint.result == 'success' }}
|
|
permissions:
|
|
actions: read
|
|
contents: read
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
node: ['22.13.0', '24.0.0', '26.12.1']
|
|
test_chunk: ['1', '2', '3']
|
|
include:
|
|
- node: '22.13.0'
|
|
node_major: '22'
|
|
- node: '24.0.0'
|
|
node_major: '24'
|
|
# Garnet's post step costs about 100 s, so it runs on one chunk:
|
|
# the one whose tests finish first.
|
|
- node: '24.0.0'
|
|
test_chunk: '2'
|
|
garnet: true
|
|
- node: '26.12.1'
|
|
node_major: '26'
|
|
uses: $/.github/workflows/test.yml
|
|
with:
|
|
node: ${{ matrix.node }}
|
|
node_major: ${{ matrix.node_major }}
|
|
platform: blacksmith-8vcpu-ubuntu-2404
|
|
test_chunk: ${{ matrix.test_chunk }}
|
|
test_chunk_total: '3'
|
|
garnet: ${{ matrix.garnet == true }}
|
|
wait_for_pnpr: true
|
|
secrets:
|
|
GARNET_API_TOKEN: ${{ secrets.GARNET_API_TOKEN }}
|
|
|
|
# Windows chunks start while the Windows pnpr build runs and wait for it
|
|
# inside the job (wait_for_pnpr), so runner startup and installs overlap it.
|
|
test-windows:
|
|
name: TS CI / Test / windows ${{ matrix.test_chunk }}/5
|
|
needs: [changes, compile-and-lint]
|
|
if: ${{ !cancelled() && needs.changes.outputs.ts == 'true' && needs.compile-and-lint.result == 'success' }}
|
|
permissions:
|
|
actions: read
|
|
contents: read
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
test_chunk: ['1', '2', '3', '4', '5']
|
|
uses: $/.github/workflows/test.yml
|
|
with:
|
|
node: '22.13.0'
|
|
node_major: '22'
|
|
platform: blacksmith-8vcpu-windows-2025
|
|
test_chunk: ${{ matrix.test_chunk }}
|
|
test_chunk_total: '5'
|
|
wait_for_pnpr: true
|
|
|
|
# Single aggregate gate — the only TS CI context branch protection needs
|
|
# to require. Listing the individual jobs as required checks does not
|
|
# work: they skip on non-TypeScript PRs, and a matrix job skipped at the
|
|
# job level never expands its `${{ matrix.* }}` name, so the per-platform
|
|
# contexts it would report never appear and the PR blocks forever waiting
|
|
# for them. This job always runs (it reports under a static name in every
|
|
# state) and fails only if a dependency actually failed or was cancelled —
|
|
# skipped dependencies count as a pass.
|
|
success:
|
|
name: TS CI / Success
|
|
if: ${{ always() }}
|
|
needs:
|
|
- changes
|
|
- compile-and-lint
|
|
- build-pnpr-linux
|
|
- build-pnpr-windows
|
|
- test
|
|
- test-windows
|
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
|
steps:
|
|
- name: Fail if any dependency failed or was cancelled
|
|
if: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}
|
|
run: exit 1
|