chore: version packages (rc) #10076
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 | |
| # Superseded runs on the same PR/branch waste runners and delay feedback; | |
| # cancel them. Push runs to main group by commit ref as well, so an in-flight | |
| # main run is cancelled only by a newer main push. | |
| concurrency: | |
| group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| filter: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| outputs: | |
| docs: ${{ steps.changes.outputs.docs }} | |
| core: ${{ steps.changes.outputs.core }} | |
| generated: ${{ steps.changes.outputs.generated }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v7 | |
| - uses: dorny/paths-filter@v4 | |
| id: changes | |
| with: | |
| filters: | | |
| docs: | |
| - 'apps/docs/**' | |
| - 'content/**' | |
| - 'pnpm-lock.yaml' | |
| - '.github/workflows/ci.yml' | |
| # Inputs AND outputs of every generated artifact checked by the | |
| # `check-generated` job. A path missing here makes that gate dormant on | |
| # exactly the PRs that can break it, so keep the two in lockstep. | |
| generated: | |
| # check:skill-docs — catalog source is skills/*/SKILL.md; the derived | |
| # listings are skills/README.md + the reference page below. (That page | |
| # was content/docs/guides/skills.mdx until #2584 moved it; this filter | |
| # kept watching the old path, so hand-edits to the generated block went | |
| # unchecked from then until now.) | |
| - 'skills/**' | |
| - 'content/docs/ai/skills-reference.mdx' | |
| - 'packages/spec/scripts/build-skill-docs.ts' | |
| # check:spec-changes / check:upgrade-guide — derived from the ADR-0087 | |
| # registries and the protocol version, none of which the skills paths | |
| # above cover. Both gates were dormant on spec-only PRs: a protocol bump | |
| # or a new migration could not trigger the job that verifies them. | |
| - 'packages/spec/scripts/build-spec-changes.ts' | |
| - 'packages/spec/scripts/build-upgrade-guide.ts' | |
| - 'packages/spec/src/migrations/**' | |
| - 'packages/spec/src/conversions/**' | |
| - 'packages/spec/src/kernel/protocol-version.ts' | |
| - 'packages/spec/api-surface.json' | |
| - 'packages/spec/spec-changes.json' | |
| - 'docs/protocol-upgrade-guide.md' | |
| - '.github/workflows/ci.yml' | |
| core: | |
| - 'packages/**' | |
| - 'examples/**' | |
| - 'apps/!(docs)/**' | |
| - 'package.json' | |
| - 'pnpm-lock.yaml' | |
| - 'tsconfig.json' | |
| - '.github/workflows/ci.yml' | |
| test: | |
| name: Test Core | |
| needs: filter | |
| if: needs.filter.outputs.core == 'true' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v7 | |
| with: | |
| # Full history so `turbo --affected` can diff against the PR base. | |
| fetch-depth: 0 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: '20' | |
| - name: Enable Corepack | |
| run: corepack enable | |
| - name: Verify pnpm version | |
| run: pnpm --version | |
| - name: Get pnpm store directory | |
| shell: bash | |
| run: | | |
| echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV | |
| - name: Setup pnpm cache | |
| uses: actions/cache@v6 | |
| with: | |
| path: ${{ env.STORE_PATH }} | |
| key: ${{ runner.os }}-pnpm-store-v3-${{ hashFiles('**/pnpm-lock.yaml') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pnpm-store-v3- | |
| # Restore-only on PRs: PR-side saves (~5 turbo entries per push) churned | |
| # the repo's 10 GB Actions cache pool and evicted the main-branch seeds — | |
| # observed as sudden cold-cache spikes (Build Core 51s → 4m30s). PRs fall | |
| # back to main's entries via the prefix restore-keys; only main pushes | |
| # save (the "Save Turbo cache" step at the end of the job). | |
| - name: Restore Turbo cache | |
| uses: actions/cache/restore@v6 | |
| with: | |
| path: .turbo/cache | |
| key: ${{ runner.os }}-turbo-${{ github.job }}-${{ github.ref_name }}-${{ github.sha }} | |
| restore-keys: | | |
| ${{ runner.os }}-turbo-${{ github.job }}-${{ github.ref_name }}- | |
| ${{ runner.os }}-turbo-${{ github.job }}- | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| # PRs: only test packages affected by the diff against the PR base. | |
| # spec sits at the root of the dependency graph, so spec-touching PRs | |
| # still run (close to) everything — but the many PRs that don't touch | |
| # spec skip the bulk of the 75-package matrix. | |
| # --concurrency=4: turbo's default (10) oversubscribes the 4-vCPU | |
| # hosted runner; matching the core count bounds peak memory and the | |
| # job is CPU-bound anyway. | |
| # !@objectstack/dogfood: the ~7½-minute dogfood suite is the dedicated | |
| # Dogfood job's whole purpose, and both jobs run under the same `core` | |
| # filter — without the exclusion every core PR executed the suite twice | |
| # in parallel, and it dominated this job's critical path. The exclusion | |
| # subtracts from the affected set (verified: turbo unions inclusive | |
| # filters, then applies `!` negations to the result). | |
| - name: Run affected tests (PR) | |
| if: github.event_name == 'pull_request' | |
| env: | |
| TURBO_SCM_BASE: ${{ github.event.pull_request.base.sha }} | |
| run: pnpm turbo run test --affected --filter=!@objectstack/dogfood --concurrency=4 | |
| # Push to main: full run. Spec's suite runs here plain (uninstrumented); | |
| # the coverage-instrumented pass moved to the nightly Spec Coverage | |
| # workflow (coverage-nightly.yml) — instrumentation added minutes to | |
| # every main push for a trend artifact that is consulted occasionally at | |
| # best. Dogfood is excluded for the same reason as the PR step: the | |
| # Dogfood job runs it. | |
| - name: Run all tests (push) | |
| if: github.event_name == 'push' | |
| run: pnpm turbo run test --filter=!@objectstack/dogfood --concurrency=4 | |
| # Seed the shared Turbo cache from main only (see the restore step | |
| # above). always(): keep the seed fresh even when a test fails, matching | |
| # the old actions/cache post-step behavior. | |
| - name: Save Turbo cache (main only) | |
| if: always() && github.event_name == 'push' | |
| uses: actions/cache/save@v6 | |
| with: | |
| path: .turbo/cache | |
| key: ${{ runner.os }}-turbo-${{ github.job }}-${{ github.ref_name }}-${{ github.sha }} | |
| dogfood: | |
| # Sharded 2-way: the suite is ~60 independent test files, each booting its | |
| # own in-process app, and a single 4-vCPU runner needed ~7½ minutes for the | |
| # lot — the longest pole in the whole workflow. vitest partitions the file | |
| # list deterministically across shards; both shards must pass. If branch | |
| # protection lists "Dogfood Regression Gate" as a required check, it must be | |
| # updated to the two sharded check names. | |
| name: Dogfood Regression Gate (${{ matrix.shard }}/2) | |
| needs: filter | |
| if: needs.filter.outputs.core == 'true' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| shard: [1, 2] | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v7 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: '20' | |
| - name: Enable Corepack | |
| run: corepack enable | |
| - name: Verify pnpm version | |
| run: pnpm --version | |
| - name: Get pnpm store directory | |
| shell: bash | |
| run: | | |
| echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV | |
| - name: Setup pnpm cache | |
| uses: actions/cache@v6 | |
| with: | |
| path: ${{ env.STORE_PATH }} | |
| key: ${{ runner.os }}-pnpm-store-v3-${{ hashFiles('**/pnpm-lock.yaml') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pnpm-store-v3- | |
| # Shard-scoped key: the turbo test hash differs per shard (pass-through | |
| # args are part of the task hash). Restore-only on PRs — see the Restore | |
| # Turbo cache comment in the test job; the save step at the end of this | |
| # job seeds from main only. | |
| - name: Restore Turbo cache | |
| uses: actions/cache/restore@v6 | |
| with: | |
| path: .turbo/cache | |
| key: ${{ runner.os }}-turbo-${{ github.job }}-${{ matrix.shard }}-${{ github.ref_name }}-${{ github.sha }} | |
| restore-keys: | | |
| ${{ runner.os }}-turbo-${{ github.job }}-${{ matrix.shard }}-${{ github.ref_name }}- | |
| ${{ runner.os }}-turbo-${{ github.job }}-${{ matrix.shard }}- | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| # Boots real example apps in-process (in-memory SQLite) and exercises them | |
| # through the real HTTP + service stack — catches runtime regressions that | |
| # build / unit tests / spec-liveness pass over (e.g. the #2018 tz-bucketing | |
| # break, which was green on every static gate). The `--` args reach the | |
| # package's `vitest run` and are hashed into the turbo task, so each | |
| # shard caches independently. | |
| - name: Boot example apps and exercise real user flows | |
| run: pnpm turbo run test --filter=@objectstack/dogfood -- --shard=${{ matrix.shard }}/2 | |
| # Replaces the former auto-verify dogfood tests: runs the published | |
| # `objectstack verify` engine over each example app through the CLI — | |
| # auto-derived CRUD round-trip fidelity + the cross-owner RLS invariant. | |
| # Exits non-zero on a real runtime failure, so it gates like the tests did. | |
| # Not shard-dependent, so shard 1 alone runs it. | |
| - name: Verify example apps via the `objectstack verify` CLI | |
| if: matrix.shard == 1 | |
| run: | | |
| pnpm turbo run build --filter=@objectstack/cli | |
| for app in examples/app-crm examples/app-showcase; do | |
| echo "::group::objectstack verify $app --rls" | |
| OS_LOG_LEVEL=error node packages/cli/bin/run.js verify --app "$app/objectstack.config.ts" --rls | |
| echo "::endgroup::" | |
| done | |
| # Seed the shared Turbo cache from main only (see the restore step | |
| # above); shard-scoped key so the two matrix jobs don't collide. | |
| - name: Save Turbo cache (main only) | |
| if: always() && github.event_name == 'push' | |
| uses: actions/cache/save@v6 | |
| with: | |
| path: .turbo/cache | |
| key: ${{ runner.os }}-turbo-${{ github.job }}-${{ matrix.shard }}-${{ github.ref_name }}-${{ github.sha }} | |
| dogfood-gate: | |
| # Stable required-check name for a SHARDED job (#3622 follow-up). | |
| # | |
| # Branch protection requires the context "Dogfood Regression Gate". Once | |
| # the job became a 2-way matrix its checks publish as "Dogfood Regression | |
| # Gate (1/2)" / "(2/2)" — the bare context could never appear again, so | |
| # EVERY pull request in the repo sat permanently BLOCKED (mergeable, all | |
| # checks green, merge button dead). #3622's own comment called for updating | |
| # branch protection; keeping the contract HERE instead means a future | |
| # shard-count change cannot deadlock the repo a second time. | |
| # | |
| # `if: always()` + result inspection so a legitimately skipped matrix (the | |
| # `filter` job says no core paths changed) still satisfies the gate. | |
| name: Dogfood Regression Gate | |
| needs: dogfood | |
| if: always() | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Verify dogfood shard results | |
| run: | | |
| result="${{ needs.dogfood.result }}" | |
| echo "dogfood matrix aggregate result: $result" | |
| # cancelled is a run-lifecycle state, not a shard verdict (#3668): | |
| # with cancel-in-progress on, every superseded push cancelled the | |
| # in-flight dogfood matrix — the longest job, so almost always the | |
| # one still running — and the old `*)` fallthrough painted a false | |
| # red on the old SHA. Verified experimentally (run 30271824408, a | |
| # fail-fast matrix with one real failure + one cancelled sibling): | |
| # a real shard failure DOMINATES the aggregate — it reads "failure", | |
| # never "cancelled" — so "cancelled" here can only mean the whole | |
| # run was stopped from outside (supersession, or a manual cancel — | |
| # accepted trade-off), and passing it masks no regression. | |
| # Deliberately NOT `if: !cancelled()` on the job instead: a skipped | |
| # gate publishes no required-check context on the SHA, which is the | |
| # #3622 merge-deadlock all over again. | |
| case "$result" in | |
| success|skipped|cancelled) echo "Dogfood gate satisfied ($result)." ;; | |
| *) echo "::error::Dogfood shards did not pass (aggregate result: $result)"; exit 1 ;; | |
| esac | |
| build-core: | |
| name: Build Core | |
| needs: filter | |
| if: needs.filter.outputs.core == 'true' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v7 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: '20' | |
| - name: Enable Corepack | |
| run: corepack enable | |
| - name: Verify pnpm version | |
| run: pnpm --version | |
| - name: Get pnpm store directory | |
| shell: bash | |
| run: | | |
| echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV | |
| - name: Setup pnpm cache | |
| uses: actions/cache@v6 | |
| with: | |
| path: ${{ env.STORE_PATH }} | |
| key: ${{ runner.os }}-pnpm-store-v3-${{ hashFiles('**/pnpm-lock.yaml') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pnpm-store-v3- | |
| # Restore-only on PRs: PR-side saves (~5 turbo entries per push) churned | |
| # the repo's 10 GB Actions cache pool and evicted the main-branch seeds — | |
| # observed as sudden cold-cache spikes (Build Core 51s → 4m30s). PRs fall | |
| # back to main's entries via the prefix restore-keys; only main pushes | |
| # save (the "Save Turbo cache" step at the end of the job). | |
| - name: Restore Turbo cache | |
| uses: actions/cache/restore@v6 | |
| with: | |
| path: .turbo/cache | |
| key: ${{ runner.os }}-turbo-${{ github.job }}-${{ github.ref_name }}-${{ github.sha }} | |
| restore-keys: | | |
| ${{ runner.os }}-turbo-${{ github.job }}-${{ github.ref_name }}- | |
| ${{ runner.os }}-turbo-${{ github.job }}- | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Build packages (excluding docs) | |
| run: pnpm build | |
| - name: Verify build outputs | |
| run: | | |
| if [ ! -d "packages/spec/dist" ]; then | |
| echo "Error: packages/spec/dist directory not found" | |
| exit 1 | |
| fi | |
| if [ ! -d "packages/spec/json-schema" ]; then | |
| echo "Error: packages/spec/json-schema directory not found" | |
| exit 1 | |
| fi | |
| echo "Build outputs verified successfully" | |
| # Capability packages (services / triggers / plugins) are loaded by the | |
| # multi-tenant runtime via a DYNAMIC import of their published entry. If a | |
| # package ships a dts-only / half-built / 0-byte `dist` (an interrupted | |
| # build, or a package retired in source but still referenced), that import | |
| # resolves to nothing and the capability SILENTLY fails to load — e.g. | |
| # record-change automation never fires, with no user-visible signal. The | |
| # build config always emits JS, so this can only happen by accident; assert | |
| # every buildable capability package actually produced its declared runtime | |
| # entry. Self-maintaining: reads each package's own `main`, skips dirs with | |
| # no package.json (e.g. a retired service-feed/service-ai leftover). | |
| - name: Verify capability packages ship a runtime entry (no dts-only / half-built) | |
| run: | | |
| fail=0 | |
| for d in packages/triggers/* packages/services/* packages/plugins/*; do | |
| [ -f "$d/package.json" ] || continue | |
| has_build=$(node -p "Boolean((require('./$d/package.json').scripts||{}).build)" 2>/dev/null || echo false) | |
| [ "$has_build" = "true" ] || continue | |
| main=$(node -p "require('./$d/package.json').main || 'dist/index.js'" 2>/dev/null || echo dist/index.js) | |
| if [ ! -s "$d/$main" ]; then | |
| echo "✗ $d: missing/empty runtime entry '$main' — dts-only or unbuilt? dynamic import would silently fail" | |
| fail=1 | |
| fi | |
| done | |
| if [ "$fail" -ne 0 ]; then exit 1; fi | |
| echo "✓ all buildable capability packages ship a runtime JS entry" | |
| - name: Analyze bundle size | |
| run: pnpm --filter @objectstack/spec analyze | |
| - name: Upload build artifacts | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: build-output | |
| path: | | |
| packages/spec/dist/ | |
| packages/spec/json-schema/ | |
| retention-days: 30 | |
| # Seed the shared Turbo cache from main only (see the restore step above). | |
| - name: Save Turbo cache (main only) | |
| if: always() && github.event_name == 'push' | |
| uses: actions/cache/save@v6 | |
| with: | |
| path: .turbo/cache | |
| key: ${{ runner.os }}-turbo-${{ github.job }}-${{ github.ref_name }}-${{ github.sha }} | |
| build-docs: | |
| name: Build Docs | |
| needs: filter | |
| if: needs.filter.outputs.docs == 'true' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v7 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: '20' | |
| - name: Enable Corepack | |
| run: corepack enable | |
| - name: Verify pnpm version | |
| run: pnpm --version | |
| - name: Get pnpm store directory | |
| shell: bash | |
| run: | | |
| echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV | |
| - name: Setup pnpm cache | |
| uses: actions/cache@v6 | |
| with: | |
| path: ${{ env.STORE_PATH }} | |
| key: ${{ runner.os }}-pnpm-store-v3-${{ hashFiles('**/pnpm-lock.yaml') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pnpm-store-v3- | |
| - name: Setup Next.js cache | |
| uses: actions/cache@v6 | |
| with: | |
| path: apps/docs/.next/cache | |
| key: ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('apps/docs/**.[jt]s', 'apps/docs/**.[jt]sx', 'content/**/*.mdx') }} | |
| restore-keys: | | |
| ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}- | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Build Docs | |
| run: pnpm --filter @objectstack/docs build | |
| # Generated artifacts that are committed to the tree: regenerate, fail on drift. | |
| # Named for the class, not for skill docs alone — it has checked the ADR-0087 | |
| # protocol artifacts for a while, and a name that hid them is part of why their | |
| # paths went missing from the filter above. | |
| # | |
| # The generated reference docs (content/docs/references/**) are deliberately NOT | |
| # here: they need to gate spec-only PRs, and this whole job is filter-gated, so | |
| # they live in lint.yml's required, unfiltered "TypeScript Type Check" job. | |
| check-generated: | |
| name: Check Generated Artifacts | |
| needs: filter | |
| if: needs.filter.outputs.generated == 'true' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v7 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: '20' | |
| - name: Enable Corepack | |
| run: corepack enable | |
| - name: Get pnpm store directory | |
| shell: bash | |
| run: | | |
| echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV | |
| - name: Setup pnpm cache | |
| uses: actions/cache@v6 | |
| with: | |
| path: ${{ env.STORE_PATH }} | |
| key: ${{ runner.os }}-pnpm-store-v3-${{ hashFiles('**/pnpm-lock.yaml') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pnpm-store-v3- | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Check skill docs are generated from SKILL.md frontmatter | |
| run: pnpm --filter @objectstack/spec check:skill-docs | |
| - name: Check spec-changes.json is regenerated with the ADR-0087 registries | |
| run: pnpm --filter @objectstack/spec check:spec-changes | |
| - name: Check the protocol upgrade guide is regenerated with the ADR-0087 registries | |
| run: pnpm --filter @objectstack/spec check:upgrade-guide |