Repository navigation
chore(release): version packages #2163
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 | |
| # KEEP IN SYNC: the `paths-ignore` lists below must exactly mirror the | |
| # `paths:` list in ci-docs-noop.yml — the no-op twin that reports the required | |
| # lint/typecheck/test/editor-ux contexts on docs-only PRs, which would | |
| # otherwise stay BLOCKED because this workflow never runs for them. | |
| on: | |
| push: | |
| branches: [main] | |
| paths-ignore: | |
| - '*.md' | |
| - '**/*.md' | |
| - 'docs/**' | |
| - '.github/*.md' | |
| pull_request: | |
| branches: [main] | |
| paths-ignore: | |
| - '*.md' | |
| - '**/*.md' | |
| - 'docs/**' | |
| - '.github/*.md' | |
| # Manual trigger, and the probe for the fail-closed event dispatch below | |
| # (0372): `workflow_dispatch` is deliberately an event none of the jobs name, | |
| # so running it by hand exercises the unknown-event path end to end. Every | |
| # gate in this file must do real work under it — a green dispatch run that | |
| # executed nothing is the bug, not the pass. | |
| workflow_dispatch: | |
| # Cancel superseded PR runs (group keyed on the PR number). Pushes to main key | |
| # on the commit SHA so each merge gets a unique group — never cancelled, never | |
| # queued behind the previous merge's run. (Exploration 0193.) | |
| concurrency: | |
| group: ci-${{ github.event.pull_request.number || github.sha }} | |
| cancel-in-progress: true | |
| jobs: | |
| # Fast checks that need NO build: prettier, eslint (not type-aware), and the | |
| # cloud license-boundary grep. Kept under the name `lint` so the existing | |
| # required status check still resolves. These used to sit serially behind the | |
| # ~250s build+typecheck step; now a formatting/lint failure surfaces in under | |
| # a minute instead of after the build (exploration 0193). | |
| lint: | |
| runs-on: ubuntu-latest | |
| steps: | |
| # fetch-depth: 0 — check:exploration-fallow dates each exploration from | |
| # the commit that introduced it. A shallow clone has no such commit, so | |
| # every document would look new and the ratchet would pass vacuously; it | |
| # fails loudly below a coverage floor rather than reporting a false green. | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - uses: ./.github/actions/setup | |
| - name: Format check | |
| run: pnpm prettier --check "packages/**/*.{ts,tsx}" "apps/**/*.{ts,tsx}" | |
| - name: Lint | |
| run: pnpm lint | |
| - name: Cloud license boundary | |
| run: pnpm check:cloud-boundary | |
| - name: Plugin license policy (0196) | |
| run: pnpm check:plugin-licenses | |
| # The negative control for the values-as-code gates (0430). Their green | |
| # state is otherwise unfalsifiable: a regex that stopped matching after a | |
| # rename looks exactly like a clean tree, and a footprint metric that | |
| # stopped being measurable looks exactly like a lean app. This plants | |
| # violations they MUST flag, so green means something. | |
| - name: Gate negative controls (0430) | |
| run: pnpm check:gate-controls | |
| - name: Motion vocabulary (0199) | |
| run: pnpm check:motion-vocab | |
| - name: Surface tokens (0299) | |
| run: pnpm check:surface-tokens | |
| - name: Humane patterns (0234) | |
| run: pnpm check:humane-patterns | |
| # POSSE's machine-checkable half (0432): a syndicated post is a mirror, | |
| # so every announced post must carry a link home to xnet.fyi, fit | |
| # Bluesky's 300-grapheme budget, and have link facets whose byte ranges | |
| # actually decode to their URL. | |
| - name: Syndication invariants (0432) | |
| run: pnpm check:syndication | |
| # Charter §Agency's second half: a capability you cannot see is not a | |
| # degree of freedom you have. Every other check here asks whether the | |
| # code is correct — none asks whether a shipped capability is findable, | |
| # so a feature can land reachable-but-invisible and nothing notices. | |
| # Advisory for now; goes --strict once the flag population has held | |
| # green for a release. (0428.) | |
| - name: Capability surfaces (0428) | |
| run: pnpm check:capability-surface | |
| # Every agent lane must build its retrieval. The omission is invisible in | |
| # review — the call reads fine, it is what it doesn't pass that drops the | |
| # lane to a keyword scan with no graph stage. (0415.) | |
| - name: AI surfaces wire retrieval (0415) | |
| run: pnpm check:ai-retrieval | |
| # The dev-only point-and-change source stamp must never reach a | |
| # production bundle — it would publish the repo's directory layout in the | |
| # DOM of every page. A diff cannot show that; this can. (0399.) | |
| - name: Source stamp is dev-only (0399) | |
| run: pnpm guard:no-source-stamp | |
| # Exploration status lives in the filename, so checking a doc off renames | |
| # it and silently breaks every reference spelling the old name. 25 had | |
| # rotted before this check existed — including four public site/ legal | |
| # pages and three package READMEs. Absolute, not a ratchet: zero is | |
| # reachable today, so every new break is a fresh mistake. (0421.) | |
| - name: Exploration references resolve (0421) | |
| run: pnpm check:exploration-links | |
| # The backlog grows ~85 documents a month and nothing closes one. Ratchet | |
| # only — the count may not EXCEED the committed baseline, so a green repo | |
| # stays green and the fix is always a one-line frontmatter edit. Needs the | |
| # full history checked out below: creation dates are what make a document | |
| # "stale", and a shallow clone has none. (0421.) | |
| - name: Exploration backlog ratchet (0421) | |
| run: pnpm check:exploration-fallow | |
| - name: Publish closure (0220) | |
| run: pnpm check:publish-closure | |
| # `changeset version` only runs on push to main, so a malformed changeset | |
| # lands green on its own PR and reds the *release* instead — the Version | |
| # Packages PR stops refreshing and nothing publishes. Four changesets | |
| # mixing ignored and publishable packages did exactly that for 12 days. | |
| # changesets aborts on the first offender, so this reports all of them. | |
| - name: Changesets assemble into a release plan (0265) | |
| run: pnpm check:changesets | |
| # site/public/llms-full.txt is generated from the docs but committed, and | |
| # CI does not build site/ on pull requests — so an MDX edit that skips the | |
| # regeneration ships a stale artifact and nothing says so. Commit 4c00be2c7 | |
| # renumbered an exploration reference in decisions.mdx and left the file | |
| # quoting the dead number until an unrelated commit happened to regenerate | |
| # it. Byte equality against a fresh render; the fix is one command. | |
| - name: llms-full.txt matches the docs | |
| run: pnpm check:llms-full | |
| # The hub Dockerfile hand-lists the packages it COPYs. A dependency added | |
| # three packages away silently staled that list, and nothing failed until | |
| # a six-minute image build died on "Cannot find module" — the same build | |
| # Railway runs, so the first symptom can be a failed deploy. (0415.) | |
| - name: Hub image copies its dependency closure (0415) | |
| run: pnpm check:hub-image | |
| - name: Electron parity (0238) | |
| run: pnpm check:electron-parity | |
| # The storybook plugins shim must mirror every @xnetjs/plugins name the | |
| # story bundle imports; drift broke Visual UI Capture three times (0279, | |
| # 0280, #412) six minutes into an optional job. Fail here in seconds | |
| # instead — visual-capture is continue-on-error by contract. (0283.) | |
| - name: Storybook plugins shim conformance (0283) | |
| run: pnpm check:storybook-shim | |
| # Workflow files themselves: an unparseable workflow made GitHub emit a | |
| # phantom failed run on EVERY push for weeks (exploration 0283). Severity | |
| # floor `warning` keeps shellcheck's style/info pedantry out of the gate. | |
| - name: Lint workflow files (actionlint, 0283) | |
| env: | |
| SHELLCHECK_OPTS: --severity=warning | |
| run: | | |
| bash <(curl -fsSL https://raw.githubusercontent.com/rhysd/actionlint/v1.7.12/scripts/download-actionlint.bash) 1.7.12 | |
| ./actionlint -color | |
| # Validate changelog fragments and dry-run PR-number resolution on PRs, so a | |
| # malformed entry (which would otherwise fail only the production deploy and | |
| # freeze the whole changelog) or one that would ship numberless fails the PR | |
| # instead (exploration 0203). Deliberately light: plain Node + on-the-fly tsx, | |
| # no workspace install — both scripts are dependency-free / type-only. | |
| changelog: | |
| runs-on: ubuntu-latest | |
| # Fail-closed (0372): named as an EXCLUSION of the one event we mean to | |
| # skip, not an allow-list of two. `push` has no PR to validate against; | |
| # every other event — known or not — runs the check rather than skipping | |
| # into a green it did not earn. See CHANGELOG_BASE_REF below: this job has | |
| # a second fail-open mouth, and flipping the gate without feeding it would | |
| # only move the silent pass one level down. | |
| if: github.event_name != 'push' | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: '23' | |
| - name: Validate changelog fragments | |
| run: npx --yes tsx site/scripts/validate-changelog.ts | |
| - name: Dry-run PR-number resolution | |
| run: node scripts/changelog/resolve-prs.mjs --check | |
| env: | |
| GITHUB_TOKEN: ${{ github.token }} | |
| # Fail-closed (0372): resolve-prs.mjs treats an EMPTY base ref as | |
| # "local run" and downgrades every unresolved fragment from an error | |
| # to a warning — so on a non-PR event this step would exit 0 having | |
| # gated nothing. `pull_request` is undefined off a PR, hence the | |
| # fallback chain, ending at `origin/main` (available because the | |
| # checkout above is fetch-depth: 0). | |
| CHANGELOG_BASE_REF: ${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha || 'origin/main' }} | |
| # The heavy half of the old lint job: build the workspace and typecheck it. | |
| # typecheck genuinely needs `^build` (turbo builds dependencies first), so it | |
| # stays coupled to the build. Runs in parallel with `lint`, `test`, and | |
| # `editor-ux`; it is the pipeline's long pole. | |
| typecheck: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: ./.github/actions/setup | |
| - name: Build & Type check | |
| run: pnpm turbo run build typecheck | |
| # The committed API reports (packages/*/etc/*.api.md) are the record of | |
| # what we promise to keep working. api-extractor fails when the built | |
| # surface no longer matches the report, so a change to what we export | |
| # cannot land without showing up as a reviewable diff. Must run AFTER the | |
| # build -- it reads dist/*.d.ts, which is why it is not in `lint` (that | |
| # job is deliberately build-free, exploration 0193). (0370) | |
| - name: API report drift (0370) | |
| run: pnpm check:api-report | |
| # publint over every publishable package: can a consumer actually install | |
| # and import this? Upstream of any versioning promise -- a correct semver | |
| # bump on a package whose export map doesn't resolve is worth nothing. | |
| # Errors only; the suggestion tier is unpassable by design. (0370) | |
| - name: Packaging validity (0370) | |
| run: pnpm check:packaging | |
| # Charter §7 "Floor": software is how working computers become waste, so | |
| # a change that raises what a first load costs is a change that retires | |
| # someone's hardware. Ratchets the critical-path bytes against | |
| # footprint-baseline.json. Lives here rather than in `lint` for the same | |
| # reason check:api-report does -- it reads build output, and `lint` is | |
| # deliberately build-free (0193). Its negative control runs in `lint` | |
| # beside the other gate controls. (0434) | |
| - name: Footprint budget (0434) | |
| run: pnpm check:footprint-budget | |
| test: | |
| runs-on: ubuntu-latest | |
| strategy: | |
| matrix: | |
| shard: [1/3, 2/3, 3/3] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - uses: ./.github/actions/setup | |
| # No build step: vitest.config.ts aliases every @xnetjs/* import to the | |
| # package's src/, and no test imports from dist/, so the workspace build | |
| # is dead weight for the test run (exploration 0193). better-sqlite3 — the | |
| # only native dependency tests touch — is rebuilt in the setup composite. | |
| # No --coverage: CI never enforced thresholds (vitest.config.ts skips | |
| # them when CI=true because shards see partial coverage) and the codecov | |
| # upload had no consumer — no badge, no gate (exploration 0294). Local | |
| # full runs still enforce the configured thresholds. | |
| # FAIL-CLOSED EVENT DISPATCH (0372). This used to be two mutually | |
| # exclusive `if:` gates — `push` → full run, `pull_request` → changed-only. | |
| # On ANY other event (merge_group, workflow_call, workflow_dispatch, | |
| # schedule) neither matched, both steps were skipped, and the job | |
| # SUCCEEDED having executed zero tests. `test (1/3..3/3)` are required | |
| # checks in the `Protect main` ruleset and GitHub counts a skipped job as | |
| # passing, so the gate reported green on an untested tree — it failed | |
| # safe-looking, which is worse than failing loudly. | |
| # | |
| # The scope is now resolved in ONE place with the unknown event falling | |
| # through to the FULL suite. An unforeseen trigger costs CI minutes; it | |
| # never costs a false green. Narrowing (`--changed`) must be opted into | |
| # by name, never arrived at by default. | |
| - name: Resolve test scope | |
| id: scope | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| BASE_REF: ${{ github.base_ref }} | |
| run: | | |
| case "$EVENT_NAME" in | |
| pull_request) echo "args=--changed=origin/$BASE_REF --passWithNoTests" >> "$GITHUB_OUTPUT" ;; | |
| *) echo "args=" >> "$GITHUB_OUTPUT" ;; | |
| esac | |
| - name: Run tests (shard ${{ matrix.shard }}) | |
| run: pnpm vitest run --shard=${{ matrix.shard }} ${{ steps.scope.outputs.args }} | |
| editor-ux: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| # Fail-closed (0372): `editor-ux` is a REQUIRED check, so a skip reports | |
| # green. Excluding `push` (where the merged result is already covered by | |
| # the full `test` run) leaves every other event running the suite. Nothing | |
| # in this job reads PR context, so the unknown-event path is sound as-is. | |
| if: github.event_name != 'push' | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: ./.github/actions/setup | |
| - name: Build packages | |
| run: pnpm turbo run build --filter='./packages/*' | |
| # Cache the Playwright browser binaries (~/.cache/ms-playwright), keyed on | |
| # the pinned @playwright/test version and the browser set this job needs. | |
| # The big cost is the browser download; OS deps are not in this path, so | |
| # on a cache hit we still run `install-deps` (apt only) to keep webkit's | |
| # libraries present. (Exploration 0193.) | |
| - name: Cache Playwright browsers | |
| id: playwright-cache | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.cache/ms-playwright | |
| key: playwright-${{ runner.os }}-1.58.1-chromium-webkit | |
| - name: Install Playwright browsers (cache miss) | |
| if: steps.playwright-cache.outputs.cache-hit != 'true' | |
| run: pnpm --filter @xnetjs/e2e-tests exec playwright install --with-deps chromium webkit | |
| - name: Install Playwright system deps (cache hit) | |
| if: steps.playwright-cache.outputs.cache-hit == 'true' | |
| run: pnpm --filter @xnetjs/e2e-tests exec playwright install-deps chromium webkit | |
| - name: Run focused editor interaction tests | |
| run: pnpm --filter @xnetjs/editor exec vitest run src/blocknote/collab-convergence.test.tsx src/blocknote/doc-utils.test.ts src/blocknote/legacy-import.test.ts | |
| - name: Start web app | |
| run: pnpm --filter xnet-web dev --host 127.0.0.1 --port 5173 > /tmp/xnet-web.log 2>&1 & | |
| - name: Wait for web app | |
| run: for _ in {1..60}; do curl -fsS http://127.0.0.1:5173 >/dev/null && exit 0; sleep 2; done; echo "web app did not start"; exit 1 | |
| - name: Run editor UX smoke tests (desktop + mobile) | |
| run: pnpm --filter @xnetjs/e2e-tests exec playwright test src/editor-ux.spec.ts src/editor-ux-mobile.spec.ts src/safety-ui.spec.ts --project=chromium --project=mobile-chromium --fail-on-flaky-tests | |
| env: | |
| CI: 'true' | |
| PLAYWRIGHT_TEST_BASE_URL: 'http://127.0.0.1:5173' | |
| # Desktop only: the mobile shell doesn't render sections at all (0388 | |
| # open question), so this spec has nothing to assert under mobile-chromium. | |
| - name: Run left-nav section tests (desktop) | |
| run: pnpm --filter @xnetjs/e2e-tests exec playwright test src/left-nav.spec.ts --project=chromium --fail-on-flaky-tests | |
| env: | |
| CI: 'true' | |
| PLAYWRIGHT_TEST_BASE_URL: 'http://127.0.0.1:5173' | |
| # Preserve the screenshots these smoke specs already take (they were | |
| # written to tmp/playwright and discarded). The richer changed-UI gallery | |
| # lives in the visual-capture workflow; this just keeps debug evidence. | |
| - name: Upload editor UX screenshots | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: editor-ux-screenshots | |
| # Playwright runs with cwd=tests/e2e, so screenshots land there; the | |
| # root glob is a fallback for any spec run from the repo root. | |
| path: | | |
| tests/e2e/tmp/playwright/**/*.png | |
| tmp/playwright/**/*.png | |
| if-no-files-found: ignore | |
| retention-days: 14 | |
| # Electron desktop e2e (exploration 0238, L2 + L3): the cross-client | |
| # convergence matrix (web↔web · electron↔web · electron↔electron, over ws and | |
| # best-effort webrtc, incl. offline→reconnect) and the `_electron.launch()` app | |
| # smoke (launch, no-console-errors, SQLite persist across restart, xnet:// | |
| # deep-link routing, core canvas flow). Headless under xvfb. | |
| # | |
| # Native-module ABI: `deps:electron` rebuilds the shared better-sqlite3 for | |
| # Electron's ABI so the unpacked app can load it — which means plain Node can't. | |
| # The in-process hub therefore runs under Electron's OWN Node runtime | |
| # (ELECTRON_RUN_AS_NODE via XNET_HUB_ELECTRON_BIN), so the hub and the app share | |
| # the one Electron-ABI copy. `electron-cache: 'true'` caches ~/.cache/electron so | |
| # the rebuild is paid once. `--fail-on-flaky-tests` quarantines flakes. | |
| electron-e2e: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| # Fail-closed (0372): same shape as `editor-ux` — skip only the `push` | |
| # case we deliberately exempt, run on everything else. This job reads no | |
| # PR context either (fetch-depth: 0 is for history, not for a base ref). | |
| if: github.event_name != 'push' | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - uses: ./.github/actions/setup | |
| with: | |
| electron-cache: 'true' | |
| - name: Build packages + Electron renderer/main | |
| run: pnpm turbo run build --filter=xnet-desktop --filter=@xnetjs/hub | |
| - name: Rebuild native modules for Electron (unpacked app) | |
| run: pnpm --filter xnet-desktop run deps:electron | |
| - name: Install desktop test services | |
| run: sudo apt-get update && sudo apt-get install -y xvfb dbus-x11 gnome-keyring libsecret-1-0 | |
| - name: Cache Playwright browser | |
| id: playwright-cache | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.cache/ms-playwright | |
| key: playwright-${{ runner.os }}-1.58.1-chromium | |
| - name: Install Playwright Chromium (cache miss) | |
| if: steps.playwright-cache.outputs.cache-hit != 'true' | |
| run: pnpm --filter @xnetjs/e2e-tests exec playwright install --with-deps chromium | |
| - name: Install Playwright system deps (cache hit) | |
| if: steps.playwright-cache.outputs.cache-hit == 'true' | |
| run: pnpm --filter @xnetjs/e2e-tests exec playwright install-deps chromium | |
| # Resolve the Electron binary and expose it as the hub's Node runtime, then | |
| # smoke the two ABI-sensitive paths before the full run: better-sqlite3 must | |
| # load under Electron's ABI, and the hub must start under ELECTRON_RUN_AS_NODE. | |
| - name: Probe Electron launch (diagnostic) | |
| run: | | |
| ELECTRON_BIN=$(cd apps/electron && node -e "process.stdout.write(require('electron'))") | |
| echo "electron binary: $ELECTRON_BIN" | |
| echo "XNET_HUB_ELECTRON_BIN=$ELECTRON_BIN" >> "$GITHUB_ENV" | |
| echo "--- better-sqlite3 under Electron ABI ---" | |
| (cd apps/electron && ELECTRON_RUN_AS_NODE=1 "$ELECTRON_BIN" -e "new (require('better-sqlite3'))(':memory:'); console.log('better-sqlite3 OK under Electron')") \ | |
| || echo "PROBE FAILED: better-sqlite3 cannot load under Electron" | |
| echo "--- hub starts under ELECTRON_RUN_AS_NODE ---" | |
| ELECTRON_RUN_AS_NODE=1 timeout 25 "$ELECTRON_BIN" --import tsx packages/hub/src/cli.ts \ | |
| --port 39997 --no-auth --storage memory 2>&1 | head -25 || true | |
| - name: Run convergence matrix + Electron app smoke (xvfb) | |
| run: | | |
| bash scripts/with-linux-test-keyring.sh \ | |
| xvfb-run --auto-servernum pnpm --filter @xnetjs/e2e-tests exec \ | |
| playwright test src/sync-matrix.spec.ts src/electron-smoke.spec.ts \ | |
| --project=chromium --project=electron --fail-on-flaky-tests | |
| env: | |
| CI: 'true' | |
| XNET_ELECTRON_NO_SANDBOX: '1' | |
| - name: Upload Electron e2e artifacts | |
| if: failure() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: electron-e2e-artifacts | |
| path: | | |
| tests/e2e/test-results/** | |
| tests/e2e/tmp/** | |
| if-no-files-found: ignore | |
| retention-days: 14 | |
| # Rust core conformance (exploration 0257): the portable kernel in | |
| # rust/xnet-core must reproduce the shared golden vectors in conformance/vectors | |
| # byte-for-byte — the same L0–L3 set the TypeScript core is gated on. Without | |
| # this job a non-TS implementation can drift from the protocol undetected. | |
| # cargo is preinstalled on ubuntu-latest runners, so no toolchain action needed. | |
| conformance-rust: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Cache cargo | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| rust/xnet-core/target | |
| key: cargo-${{ runner.os }}-${{ hashFiles('rust/xnet-core/Cargo.lock') }} | |
| - name: Rust kernel conformance vectors | |
| run: cargo test --manifest-path rust/xnet-core/Cargo.toml --locked |