ci: deploy website in its own workflow, only on docs output change#9650
Conversation
Move the docs build and Void deploy out of ci.yml into a dedicated deploy-website.yml workflow. Docs are validated on every PR/merge-queue run. On main the site is deployed only when the rendered output actually changes: a content fingerprint of the git-tracked docs sources plus the generated TypeDoc reference (docs/reference) is compared against an actions/cache marker keyed by that hash, so a packages/rolldown/src change that does not affect the public API produces the same reference and is skipped. workflow_dispatch force-deploys. Drops the docs-changes detection and the docs-validation/deploy-void jobs from ci.yml.
✅ Deploy Preview for rolldown-rs canceled.
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 74104eba61
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…d steps The merged job carried environment: void while running build/validation steps on PRs, so a malicious PR could materialize VOID_TOKEN in those steps. Split back into two jobs: - docs-validation: builds/validates and computes the deploy decision (content fingerprint + cache marker lookup). No environment, no secret. - deploy-void: environment: void; runs only on main and only when docs-validation says a deploy is needed. Does nothing but checkout + setup + deploy, so VOID_TOKEN is in scope only for the deploy step and unchanged content never enters the void environment.
…ommit Replace the actions/cache fingerprint marker with a direct comparison of the generated content between HEAD and HEAD^. docs-validation regenerates the TypeDoc reference for both commits and hashes the git-tracked docs sources plus docs/reference; the deploy runs only when they differ (or on workflow_dispatch). The built dist itself is not compared because it is not reproducible (vitepress-plugin-group-icons and vitepress-plugin-llms emit unstable ordering); TypeDoc output and the tracked sources are deterministic, which makes this comparison reliable. No GitHub Actions cache is used. Any failure regenerating the previous commit falls through to deploying.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ed5155e8ef
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| # Regenerate the reference from the previous commit and fingerprint that. Any failure | ||
| # here (e.g. a dependency change between commits) falls through to deploying. | ||
| prev_fp="" | ||
| if git checkout HEAD^ -- docs packages/rolldown && vp run --filter rolldown-docs generate; then |
There was a problem hiding this comment.
Compare against the last deployed fingerprint
On a push to main that contains multiple commits, GitHub runs this workflow once for the final SHA, but this line compares the rendered docs only to HEAD^. If commit A changes the docs output and commit B does not, the final run sees head_fp == prev_fp and skips deployment even though the live site still predates commit A. Compare against the push's previous SHA or a persisted last-deployed fingerprint/cache marker instead of only the immediately preceding commit.
Useful? React with 👍 / 👎.
Build the full site for HEAD and HEAD^ and diff the dist; deploy only when it changed (or on workflow_dispatch). This compares the actual rendered output instead of the generated reference, so source changes that don't affect the site are skipped. The dist diff requires reproducible builds. vitepress-plugin-group-icons emitted the [data-title] selectors within each CSS rule in unstable order (file-transform order), which changed the CSS hash and cascaded to every HTML page; patch it to sort the selectors. With that, two builds of the same commit are byte-identical except the generated llms*.txt aggregations, which the diff excludes (real content changes still show up in the HTML). No GitHub Actions cache is used. A failure building the previous commit falls through to deploying.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 86e4fc951e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| && vp run --filter rolldown-docs generate \ | ||
| && vp run --filter rolldown-docs build; then | ||
| mv docs/.vitepress/dist /tmp/dist-prev | ||
| if diff -rq -x 'llms.txt' -x 'llms-full.txt' /tmp/dist-head /tmp/dist-prev >/dev/null 2>&1; then |
There was a problem hiding this comment.
Include LLM artifacts in the deploy gate
When a main push changes only LLM-specific output, such as the llmstxt description, details, or ignoreFiles settings in docs/.vitepress/config.ts, the workflow is triggered by docs/** but this diff ignores both generated LLM files. Because those settings do not need to affect any HTML, changed can stay 0 and deploy=false, leaving the live /llms.txt or /llms-full.txt stale even though tracked docs inputs changed.
Useful? React with 👍 / 👎.
Pick the diff baseline by context: on main compare the built site against the previous commit; on a PR compare against the target branch (main, fetched shallowly). The comparison now also runs on PRs as an informational signal (written to the job summary); deploy stays main-only.
… dist The vitepress-plugin-group-icons patch broke installs on pnpm 10 (Node 20) with ERR_PNPM_LOCKFILE_CONFIG_MISMATCH, because the patch was committed with pnpm 11 whose lockfile encoding of patchedDependencies differs. Remove the patch entirely. Without it the built dist isn't reproducible, so the deploy gate compares the deterministic generated content (git-tracked docs sources + the TypeDoc reference) against the baseline instead — keeping the contextual baseline (previous commit on main, target branch on a PR).
…script zizmor flagged interpolating github.ref_name / github.base_ref directly into the run block (a branch name could inject shell). Pass them through env and reference shell variables instead.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fed698de54
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Build the full site for the current ref and the baseline (previous commit on main, target branch on a PR) and diff the dist. The build isn't byte-reproducible (vitepress-plugin-group-icons emits unstable CSS selector/rule ordering, which cascades into the asset hash; vitepress-plugin-llms reorders llms*.txt), so both dists are normalized before diffing via scripts/misc/normalize-docs-dist.mjs: canonicalize the CSS, de-hash the CSS filename/refs, and drop llms*.txt. The deploy still ships the real build. Verified: two builds of the same commit normalize to an identical tree, and a real content change is still detected. No dependency patch, no Actions cache.
The shallow checkout (fetch-depth 2) made vitepress lastUpdated fall back to file mtime, so the per-page data chunks (and their hashes, and sitemap lastmod) differed between the two builds and the normalized diff always reported a change. Fetch full history so lastUpdated resolves from git and is identical across both builds. Also log the differing files when a change is detected.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a23f7a3fb5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| .join(','); | ||
| return `${selectors}{${chunk.slice(i + 1)}`; | ||
| }); | ||
| chunks.sort(); |
There was a problem hiding this comment.
Preserve CSS rule order when normalizing
When a docs change intentionally reorders equal-specificity CSS rules, such as in docs/.vitepress/theme/styles.css, the shipped CSS changes because cascade order is meaningful, but this normalization sorts every CSS rule chunk before diffing. In that scenario the normalized head and baseline trees can compare equal after the hash/reference rewrite, so a main push that only changes CSS ordering skips the deploy even though the live site would render differently.
Useful? React with 👍 / 👎.
After the full-clone fix, the only residual diff was assets/style.css. The canonicalizer is order-invariant (proven: 4 builds canonicalize to one hash), so a residual diff means the group-icons CSS content itself differs between CI builds (the label collection races). Drop the [data-title] rules from the CSS comparison entirely; the rest of the CSS is deterministic. Verified locally: same-commit builds normalize identical, real content change still detected.
Drop the built-output diff (and scripts/misc/normalize-docs-dist.mjs) — the built site isn't reproducible. Instead, on main, fingerprint the deterministic deployable content (git-tracked docs sources + the TypeDoc reference) and use it as an actions/cache key: deploy only when that fingerprint hasn't been deployed before (or on workflow_dispatch). Deploys remain main-only; PRs only validate.
On main, fingerprint the deterministic deployable content (git-tracked docs sources + the TypeDoc reference) and use it as an actions/cache key: deploy only when that fingerprint hasn't been deployed before (or on workflow_dispatch). The built site is never hashed (not reproducible). Deploys stay main-only.
- The deploy decision is a pure function of the cache lookup + event, so express it directly in the job output and remove the separate gate step. - The deploy marker's contents are never read (cache key is the fingerprint), so touch it instead of writing the hash through an env var.
## [1.1.1] - 2026-06-11 ### 🚀 Features - ecmascript_utils: introduce AstFactory for AST construction (#9682) by @hyf0 - drop no-op init calls for empty wrapped-ESM modules (#9678) by @IWANABETHATGUY ### 🐛 Bug Fixes - resolver: honor package.json#type for .jsx/.tsx extensions (#9690) (#9699) by @IWANABETHATGUY - hmr: report the full-reload reason for the invalidate-loop case (#9708) by @hyf0 - explicit `moduleSideEffects` from a hook must take priority over the `package.json#sideEffects` (#9688) by @sapphi-red - keep the `rolldown-runtime` name for the standalone runtime chunk (#9685) by @shulaoda - lazy-barrel: request all exports for entry barrels on first encounter (#9672) by @shulaoda - finalizer: skip init_*() for tree-shaken wrapped ESM owners (#9669) by @IWANABETHATGUY - order `chunk.imports` by execution order (#9654) by @chuganzy - vite-resolve: preserve slash separators for Sass partial exports (#9546) by @cjc0013 - cross-chunk CJS wrapper shadowed by author-local binding (#9648) by @IWANABETHATGUY ### 🚜 Refactor - precompute wrapped-ESM init metadata in generate stage (#9712) by @IWANABETHATGUY - ecmascript_utils: fold construction ext traits onto AstFactory and delete AstSnippet (#9702) by @hyf0 - finalizer: finish ScopeHoistingFinalizer migration to AstFactory (#9701) by @hyf0 - finalizer: migrate module_finalizers/mod.rs to AstFactory (#9700) by @hyf0 - hmr: migrate hmr finalizer to AstFactory (#9695) by @hyf0 - plugin: migrate vite_build_import_analysis to AstFactory (#9693) by @hyf0 - scanner: migrate tweak_ast_for_scanning to AstFactory (#9683) by @hyf0 - always split runtime module first (#9419) by @IWANABETHATGUY ### 📚 Documentation - tsconfig: correct auto-discovery resolution to match TypeScript (#9714) by @shulaoda - design: plan to unify all internal AST construction (#9673) by @hyf0 - tsconfig: align reference resolution docs with TypeScript behavior (#9641) by @shulaoda ### ⚡ Performance - avoid per-module join Strings in scope-hoisting concatenation (#9645) by @Boshen - avoid intermediate Strings in the ESM export clause (#9644) by @Boshen - reuse a scratch buffer for facade namespace names in the scanner (#9642) by @Boshen - reuse the import-matching tracker stack across named imports (#9643) by @Boshen - avoid cloning the per-chunk export-items map in render_chunk_exports (#9639) by @Boshen - avoid CompactStr allocation in sorted-exports membership check (#9640) by @Boshen ### 🧪 Testing - add more `moduleSideEffects` precedence tests (#9689) by @sapphi-red - 9651: drop shimMissingExports from regression fixture (#9674) by @IWANABETHATGUY ### ⚙️ Miscellaneous Tasks - update react repo links (#9711) by @iiio2 - remove outdated pnpm configurations (#9666) by @btea - deps: update github actions to v2.81.5 (#9665) by @renovate[bot] - testing: migrate test harness off deprecated inlineDynamicImports (#9710) by @IWANABETHATGUY - hmr: delete commented-out dead code left from old register-module codegen (#9707) by @hyf0 - deps: update napi-rs toolchain (#9706) by @shulaoda - deps: update rollup submodule for tests to v4.61.1 (#9676) by @rolldown-guard[bot] - deps: update test262 submodule for tests (#9677) by @rolldown-guard[bot] - deps: update oxc to 0.135.0 (#9670) by @Boshen - pass ALGOLIA_APP_ID and ALGOLIA_API_KEY to void deploy (#9667) by @Boshen - deps: update github actions (#9662) by @renovate[bot] - deps: update rust crates (#9663) by @renovate[bot] - deps: update rolldown-plugin-dts to v0.25.2 (#9661) by @renovate[bot] - deps: update typos to v1.47.2 (#9660) by @renovate[bot] - deps: update docs dependencies (#9657) by @bddjr - deps: update typos to v1.47.1 (#9655) by @renovate[bot] - deploy website in its own workflow, only on docs output change (#9650) by @Boshen - publish to pkg.pr.new add pm and `commentWithDev` option (#9638) by @btea ### ❤️ New Contributors * @chuganzy made their first contribution in [#9654](#9654) * @cjc0013 made their first contribution in [#9546](#9546)
What
Moves the docs build + Void deploy out of
ci.ymlinto a dedicated.github/workflows/deploy-website.yml, and deploys only when the docs content actually changes.Why
Previously
ci.ymldeployed on every push tomainthat touchedpackages/rolldown/src/**— even when the rendered docs were unchanged. Docs should be validated on every PR, but a deploy should only happen when the output actually changes.How
Two jobs, split so the deploy secret is isolated from build steps:
docs-validation— runs on every PR / merge-queue / main run. Builds the whole site (the PR validation build) and, onmain, decides whether to deploy. It has noenvironmentand no access toVOID_TOKEN.main, computes a deterministic fingerprint of the deployable content — git-trackeddocs/**sources plus the generateddocs/reference/**— and uses it as a GitHub Actions cache key to identify an already-deployed state. Apackages/rolldown/srcchange that doesn't alter the public API yields the same reference → same fingerprint → already cached → no deploy.deploy-void—environment: void, runs only onmainand only when the fingerprint is new (cache miss) or onworkflow_dispatch. Deploys viavp run docs:voidand records the fingerprint in the cache. The only placeVOID_TOKENis in scope, and it never runs on a PR.The built
distis never hashed — it isn't reproducible (some vitepress plugins emit unstable ordering). TypeDoc output and the git-tracked sources are deterministic, which is what makes the cache fingerprint reliable.ci.yml: drops thedocs-changesfilter/output and the olddocs-validation/deploy-voidjobs.🤖 Generated with Claude Code