Skip to content

ci: one docsite-test job builds the docsite once and runs the data tests and browser contracts on it - #8891

Merged
cixzhang merged 58 commits into
mainfrom
ci/docsite-one-job
Oct 31, 2026
Merged

cixzhang merged 58 commits into
mainfrom
ci/docsite-one-job

Conversation

@cixzhang

@cixzhang cixzhang commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Built on #8916–#8919 (#8888's split: each job decides its own scope), now on main.

Maintainer impact

A docsite-affecting pull request ran three jobs: docsite-data (built core and the canary packages, generated the docsite data, ran its tests), docsite-browser (built core, the canary packages and every theme again, ran the docsite production build, which generates the data again, then the Playwright contracts) and the docsite-test join. That is two package builds and two data generations per run, about 16,000 job-minutes a day, most of it on 4-core runners. Now one job builds once and runs both suites on that build in about the browser job's time, and the required docsite-test context reports straight from the work instead of from a join.

Change

One docsite-test job (the required context, AST-030 FR7) replaces docsite-data, docsite-browser and the join:

  • It carries main's pull-request gate (spec:AST-030 FR17): as a required context it keeps only the draft skip in its condition, and an outside contributor's run stops at its first step until a maintainer adds ci:run. It runs on ubuntu-latest, as main's docsite build does.
  • It decides its own scope (ci: fold fixture-contrast into test-build #8916–ci: the heavy lanes decide their component scope; drop the scope jobs #8919's step), and it runs docsite-data's production registry gate (verify-production-gate.mjs) after the data tests. The spec-only lane (and any run whose scope step could not decide) validates spec records here as before, the Node-tooling lane skips, and every other change builds and tests.
  • It builds the docsite workspace dependencies once, then runs the docsite production build, which generates the data, once.
  • test-docsite (the docsite data tests) runs in the background on that build while test-docsite-e2e (the Playwright browser contracts) drives it in its own named step. A later move of the e2e step to another tier can take it alone. A result step reports the data tests even after a contract failure, so neither suite can leave the context green when it failed.
  • The browser evidence upload is unchanged.
  • Throttling: the workflow's per-PR cancel-in-progress already covers the job. Skipping drafts safely needs ready_for_review in the workflow's triggers, a workflow-wide change, so it is not done per job here.

Tests

  • ci-docsite-browser.test.mjs is rewritten for the one-job contract. It checks:
    • docsite-data and docsite-browser are gone, docsite-test has no needs, and there is exactly one docsite build;
    • every build and test step is gated on the active lane;
    • the start step, run under bash -e with a pnpm stub, records the data tests' exit code (0 and 1), with errexit off inside its background subshell and the exit file written whole;
    • the start step's id, the result step's condition and its lack of continue-on-error, that the result step waits while no exit code is recorded, and that it passes only on a zero exit;
    • the evidence upload.
  • spec-owner-workflow.test.mjs and ci-tooling-routing.test.mjs follow the merged job, and CONTRIBUTING.md names the docsite-test step instead of the removed job.
  • The workflow contract suites pass locally (207 tests): ci-docsite-browser, spec-owner-workflow, ci-tooling-routing, ci-heavy-lanes, ci-duration-guard, ci-test-routing and release-check-workflow.
  • Every .github/scripts suite passes on this head (76 files, 1,422 tests).

Lines

+187 / −230, net −43 against main c8e1804: ci.yml +48/−121, tests +136/−106, CONTRIBUTING.md +3/−3.

…thout deepening history

Checking out the PR head (#8812) made pr-scope-touches.sh deepen a shallow
history back to the PR's recorded base. On long-lived branches that merge
main often, the interaction-scope decide step went from a few seconds to up
to three minutes, and one run hit the job's five-minute timeout, which fails
the required test join. Both scope jobs now check out the merge commit with
its parents and pass its first parent as the base, so the helper diffs the
PR's own change against the base it merged into with no deepening. The merge
queue keeps its base. A contract test pins the checkout and asserts no
deepening on a branch that merged main four times.
…dates

Pull request updates keep the fast checks and every required context. The
visual, accessibility and RTL owners run for release-check, which Main now
dispatches on each push to main (one at a time, skipping a commit main has
moved past, failing Main when it fails), and which the release gate already
runs before a cut. A pull request opts in with the ci:heavy-lanes label. A
main failure is fixed forward. AST-030 FR12 and the new DEC-7 record the
change.
One run per workflow and ref at a time, never cancelling a running check, so a
burst of pushes is checked once at its newest commit, and Main's fast checks
are no longer held behind the heavy wait.
lint.yml classifies a pull request's files with the trusted base classifier and
skips full ESLint for a spec-only, docsite-only or tooling-only change. Its
depth-1 checkout never let the merge commit reach its merge base, so the
classifier was never fed and every pull request ran full lint (about four
minutes of ESLint on a spec-only change). The checkout now takes the same
50-commit history ci.yml's check-scope does. A contract test runs the real
step and classifier with real git.
The job checked out the whole history on a 4-core runner for every pull
request into main, then learned from the branch name that only a release
merge-back has anything to validate: about six minutes of larger-runner time
per run for nothing. Its condition now names the merge-back branch pattern,
so other pull requests skip it outright; the exact branch contract is still
checked after the checkout, and required lint still validates merge-backs.
Internal Registry's dependency check runs only when a dependency
declaration or the lockfile changes, the CLI smoke test only when a
package, the workspace configuration or its own scripts change (its
paths-ignore becomes an allowlist), and the hardening-issue opener only
when Core component sources change. A registry records every workflow's
kind and reason, and a contract test holds each workflow to it: no
paths-ignore, no match-all glob, and no path filter on a workflow that
reports a required context.
…rds why (review)

Internal Registry's header no longer says to make its check required:
behind its path filter it never starts on a pull request that touches no
dependency file. Re-enabling gating means dropping the filter and
registering it as change-scope. A review-event workflow on
pull_request_target now records in the registry why every pull-request
update matters to review state, and the contract test requires it.
Removes the scope classifier jobs (check-scope, interaction-scope,
native-swift-scope, check-components). Each job that owns tests now runs
.github/actions/test-scope right after a sparse checkout: it reads the pull
request's file list and the base commit's trusted classifier, and skips the
job's remaining steps with success when nothing in its scope changed.
Anything it cannot decide puts everything in scope (spec:AST-030 FR3, FR4).
native-swift-runner is the one pre-job left, because only a job's runner has
to be chosen before it starts.

Also:
- fixture-contrast's steps run in test-build (same Playwright runner);
- registry-contract becomes test-registry-e2e and runs for release-check
  only; its network-free production gate runs with the unit tests;
- ci-duration and the weekly a11y and RTL workflows are removed (the full
  audits run on main);
- the Internal Registry job becomes test-deps, scoped by its trigger paths;
- the test join requires release-bump on a release-branch pull request.
…sts and browser contracts on it

docsite-data, docsite-browser and the docsite-test join become the one
required docsite-test job (AST-030 FR7's name). It builds the packages and
the docsite production build (which generates the data) once, then runs
test-docsite (the data tests, in the background) and test-docsite-e2e (the
Playwright contracts, its own named step) side by side on that build; a
result step reports the data tests even after a contract failure. The
spec-only lane still validates spec records here; the tooling lane skips.

ci-docsite-browser.test.mjs pins the one-job shape, the step gates, the
data-result step's exit code and the evidence upload; spec-owner-workflow
and ci-tooling-routing follow the merged job.
@vercel

vercel Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
astryx Ready Ready Preview Oct 31, 2026 8:04am UTC

Request Review

@astracat-bot

astracat-bot Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
🟢 Reviewed (for maintainers only) Oct 31, 2026, 8:09 AM UTC

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Oct 29, 2026
The main-tier release-check dispatcher runs on every landed commit, like the
Main workflow, so it is a landed-commit workflow.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes requested: the single docsite-test job keeps the required context and builds the docsite once, but a failing docsite data test is not reported as written. GitHub runs these run: steps with bash -e, so the backgrounded subshell in test-docsite (start the data tests) exits before writing docsite-data.exit, and test-docsite (data test results) then polls until the 15-minute job timeout without printing docsite-data.log.

  • Turn errexit off inside that subshell, for example (set +e; pnpm -F @astryxdesign/docsite test > docsite-data.log 2>&1; echo $? > docsite-data.exit.tmp; mv docsite-data.exit.tmp docsite-data.exit) &, and consider bounding the wait loop.
  • In ci-docsite-browser.test.mjs, run the start step's script under bash -e with a failing pnpm stub and assert the start step's id: data and the result step's missing continue-on-error, since either regression currently lets a data-test failure pass with every test green.
  • Update the CONTRIBUTING.md sentence that still says a docsite-browser job feeds docsite-test.

[Automated review]

Comment thread .github/workflows/ci.yml Outdated
- name: test-docsite (start the data tests)
id: data
if: steps.scope.outputs.spec_only != 'true' && steps.scope.outputs.tooling_only != 'true'
run: (pnpm -F @astryxdesign/docsite test > docsite-data.log 2>&1; echo $? > docsite-data.exit) &

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This subshell inherits the runner's bash -e (the workflow sets no shell:), so when pnpm -F @astryxdesign/docsite test fails it exits before echo $? > docsite-data.exit. The result step's while [ ! -f docsite-data.exit ] loop then runs until the job's 15-minute timeout, and docsite-data.log is never printed. Turning errexit off in the subshell, and writing the file atomically, fixes it:

(set +e; pnpm -F @astryxdesign/docsite test > docsite-data.log 2>&1; echo $? > docsite-data.exit.tmp; mv docsite-data.exit.tmp docsite-data.exit) &

Comment on lines +59 to +72
it.each([
[0, true],
[1, false],
])('the data-test result step passes only on exit %i', (code, passes) => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'docsite-data-'));
fs.writeFileSync(path.join(dir, 'docsite-data.log'), 'log\n');
fs.writeFileSync(path.join(dir, 'docsite-data.exit'), `${code}\n`);
const result = spawnSync(
'bash',
['-e', '-c', step('test-docsite (data test results)').run],
{cwd: dir},
);
fs.rmSync(dir, {recursive: true, force: true});
expect(result.status === 0).toBe(passes);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This runs only the result step against a pre-written exit file, so the start step's behaviour under bash -e is untested. Running step('test-docsite (start the data tests)').run with bash -e and a pnpm stub that exits 1, then expecting docsite-data.exit to hold 1, would catch it. Asserting that the start step's id is data and that the result step has no continue-on-error would pin the two other edges that currently let a data-test failure leave docsite-test green without failing any test.

The backgrounded subshell inherited the runner's errexit, so a failing data
test run exited before writing docsite-data.exit and the result step waited
for the job timeout. errexit is off inside the subshell and the exit file is
written whole (tmp + mv). The contract test now runs the start step under
bash -e with a pnpm stub (exit 1 fails on the previous head) and pins the
start step's id and the result step's lack of continue-on-error.
CONTRIBUTING.md names the docsite-test step instead of the removed job.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommended for approval: docsite-test keeps the required context, builds the docsite once, and fails on either a data-test or a browser-contract failure, with the Vitest log printed by the result step. The backgrounded data-test step now records its exit code under bash -e, the contract test pins that path's fail-closed edges, and CONTRIBUTING.md names the new browser-contract step. CI has not run the merged job yet because this branch targets #8888's branch, so please check its wall time against the 15-minute limit on its first run against main.

[Automated review]

…per job

Review follow-ups:
- The registry contract, ci-duration, the weekly sweeps and the Internal
  Registry rename move to their own pull request, so this one only removes
  the scope classifier jobs and folds fixture-contrast into test-build.
  registry-contract stays a pull-request job here, deciding its own scope.
- A ci:heavy-lanes pull request keeps its visual and RTL audits on the
  affected components (spec:AST-030 FR13): the scope action can also run the
  base's component-audit-scope.cjs and list the changed files, and the RTL
  and visual jobs read those instead of a classification job.
- The interaction and native Swift scopes include the scope action, so a
  change to it runs the jobs it gates.
docsite-test takes main's pull-request gate: the required-context draft
skip in its condition and the ci:run first step for outside contributors,
and runs on ubuntu-latest like main's docsite build. docsite-data and
docsite-browser stay removed, also from ci-duration's needs.
@github-actions

github-actions Bot commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

📚 Storybook Preview

View Storybook for this PR

🧪 Sandbox Preview

View Sandbox for this PR

No new or modified components detected.

Bundle Size Summary

No component packages changed.

Accessibility Audit

Status: The accessibility audit did not run on this pull request. It runs after merges to main and before each release; pull requests that touch components run it with the ci:heavy-lanes label.


Generated by PR Enrichment workflow | Storybook | Sandbox | View full report

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes for one small fix: on an outside contributor's pull request without ci:run, the always() evidence upload in docsite-test still runs after the hold step and fails with no files, so the required check shows a second, misleading error beside the ci:run reason. Gating that step on the scope step having run (for example always() && steps.scope.outcome == 'success' && …) and updating the matching assertion in ci-docsite-browser.test.mjs would resolve it. The rest of the merge keeps docsite-test required, fails it on either suite with its log, and has tests that catch each fail-closed edge.

[Automated review]

Comment thread .github/workflows/ci.yml Outdated
exit "$(cat docsite-data.exit)"

- name: Upload docsite browser evidence
if: always() && steps.scope.outputs.spec_only != 'true' && steps.scope.outputs.tooling_only != 'true'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On an outside contributor's pull request without ci:run, the hold step fails and the scope step is skipped, so both scope outputs are empty and this always() condition is true: the upload runs with nothing checked out or tested and fails under if-no-files-found: error, adding a second error beside the ci:run reason. Gating it on the scope step having run keeps it after a build or contract failure:

if: always() && steps.scope.outcome == 'success' && steps.scope.outputs.spec_only != 'true' && steps.scope.outputs.tooling_only != 'true'

The exact assertion at .github/scripts/ci-docsite-browser.test.mjs:144 would change with it.

On an outside contributor's pull request without ci:run, the hold step
fails and the scope step never runs, so the always() evidence upload ran
with nothing built and failed a second time on missing files. It now
also requires the scope step to have succeeded, which keeps it after a
build or contract failure.

@astracat-bot astracat-bot Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommended for approval: docsite-test keeps the required context, builds the docsite once, and fails on either a data-test or a browser-contract failure with its log. The evidence upload now skips when the ci:run hold stops an outside contributor's run, so that run shows only the hold reason, and the contract test pins the new condition. No changes are needed; because this branch targets another pull request's branch, CI has not run the merged job yet, so please check its wall time against the 15-minute limit once it runs against main. The Vercel preview deployment failed on this revision after passing on the previous one, and nothing it builds changed in between, so a re-run is worth trying.

[Automated review]

docsite-test takes over docsite-data's new production registry gate step
(node internal/shadcn-registry/verify-production-gate.mjs), and its
contract test reads it there. Main dropped ci-duration, so its needs list
goes with it.
@cixzhang
cixzhang changed the base branch from ci/test-scope-heavy-lanes to main October 31, 2026 07:27

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting one small test fix: docsite-test keeps the required context, builds the docsite once, passed on this revision in about 11 minutes, and fails on either suite with its log. When the hidden production registry gate moved here from docsite-data, it lost the exact-condition assertion that pinned it, so an extra skip on that step (for example skipping it when the scope step cannot decide) now leaves every .github/scripts test green, even though it is the only pull-request run of that gate for a docsite-only change. Adding 'Verify the hidden production registry gate' to the step list in ci-docsite-browser.test.mjs that already pins the active-lane condition closes it; the comment above test-registry-e2e in ci.yml also still says the gate runs in docsite-data.

[Automated review]

'Build docsite workspace dependencies',
'Build the docsite',
'test-docsite (start the data tests)',
'test-docsite-e2e (browser contracts)',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verify the hidden production registry gate (.github/workflows/ci.yml:458-460) is missing from this list, so nothing pins its condition any more: adding steps.scope.outputs.decided == 'true' && to it leaves every .github/scripts test green, although the base suite's exact-condition loop failed on that edit and the step is a docsite-only pull request's only run of the production gate (AST-030 FR9). Listing it here asserts the exact active-lane condition and no continue-on-error:

      'test-docsite-e2e (browser contracts)',
      'Verify the hidden production registry gate',

The production registry gate step joins the list whose exact active-lane
condition and lack of continue-on-error the contract asserts (AST-030 FR9);
the test-registry-e2e comment names docsite-test.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommended for approval: docsite-test keeps the required context, builds the docsite once, passed on this revision in under 10 minutes, and fails on either a data-test or a browser-contract failure with its log. The hidden production registry gate is back in the exact-condition list in ci-docsite-browser.test.mjs, so narrowing its condition now fails a test, and the test-registry-e2e comment names docsite-test. No changes are needed. One thing to keep in mind: the data tests' output now appears only after the browser contracts finish, so a run cut off by the 15-minute limit during the contracts shows none of it.

[Automated review]

@cixzhang
cixzhang merged commit 7758c68 into main Oct 31, 2026
93 checks passed
@github-actions
github-actions Bot deleted the ci/docsite-one-job branch November 1, 2026 06:05

This branch was successfully deployed

1 active deployment
Preview — 558a2db0 Deployed Oct 31, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant