feat: publish versioned docs at /docs/vX.Y.Z and /docs/next - #1216
claude[bot] wants to merge 7 commits into
Conversation
… or main `yarn pre-build` keeps its behaviour for `docs/latest`; `yarn pre-build:version vX.Y.Z` (or `next`) downloads the docs of that tag (or of `main`) into `docs/vX.Y.Z` (or `docs/next`) and runs the same fixers. The start-page slug, fiddle path rewrite and the tarball folder prefix (GitHub drops the leading `v` of tag names) are no longer hardcoded to `latest`. The `.sha` of versioned folders stores the commit the tag/branch resolves to. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SXtnHVnLbGQyY34y9CKffG
The GitHub source links and "Open in Fiddle" buttons now derive the Electron ref from the docs folder a file lives in (`docs/<version>/`) instead of always using the latest stable, and the API history table links to the breaking changes of the version being viewed. Adds a banner above every doc page of the per-version builds pointing back to `/docs/latest`, a canonical link override for those builds, and a navbar dropdown (in every build) listing the published docs versions from `/docs/versions.json`. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SXtnHVnLbGQyY34y9CKffG
`docusaurus.versioned.config.ts` derives from the main config to build one docs version (`ELECTRON_DOCS_VERSION=vX.Y.Z|next yarn build:version`) as a self-contained site under `/docs/<version>/`: English only, no blog/pages or network-fetching plugins, `noindex`, broken links downgraded to warnings, and website links turned into absolute links. `sidebars.js` is now generated from `sidebars-template.js` (byte-identical output), which the versioned build reuses while dropping entries whose pages do not exist in that version. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SXtnHVnLbGQyY34y9CKffG
Builds and uploads `/docs/vX.Y.Z/` snapshots and `/docs/next/` to the docs storage account via OIDC, on demand (`workflow_dispatch`, `repository_dispatch`) or daily to backfill stable releases since v38.0.0 (10 per run), then regenerates `/docs/versions.json` from the container's actual contents. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SXtnHVnLbGQyY34y9CKffG
Adds `yarn test` (run on pull requests) with specs for the sidebar template and missing-doc filter, the docs version path helpers, the version dropdown list, the gap-fill selection and the versions.json builder. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SXtnHVnLbGQyY34y9CKffG
Deploying electron-website with
|
| Latest commit: |
1fd863e
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://c5df5974.electron-website.pages.dev |
| Branch Preview URL: | https://versioned-docs.electron-website.pages.dev |
There was a problem hiding this comment.
Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.
Beyond the inline findings already reported, I checked whether DocsVersionDropdown's use of useLocation().pathname breaks on versioned builds where baseUrl is /docs/vX.Y.Z/ (i.e., whether React Router strips the basename before parseDocsVersionPath sees it) — Docusaurus bakes baseUrl into each route's path rather than passing it as a router basename, so pathname still includes the version segment and the parse succeeds; this is not an additional bug beyond what's already flagged inline.
Extended reasoning...
The bug hunt already produced multiple confirmed findings (non-atomic azcopy copy for republished versions, silent-drop of next from versions.json on transient blob-download failures, unbounded trust of fetchPublishedVersions on network errors in the gap-fill script, non-deterministic shared 404.html across concurrent matrix jobs, and an unwired reject in downloadFromGitHub) which will be posted as inline comments, and the hunt exited on max_bugs rather than running dry, so per policy this should never be approved. I independently examined one additional candidate beyond the reported list — whether DocsVersionDropdown's reliance on useLocation().pathname would silently fail to render the dropdown on versioned-doc pages because React Router might strip the site's baseUrl (which is /docs/vX.Y.Z/ for versioned builds) as a router basename before parseDocsVersionPath runs. Docusaurus routes are registered with baseUrl already prefixed into the route path (rather than being applied via a React Router basename), so pathname from useLocation() retains the version segment and parseDocsVersionPath resolves as expected; this concern does not hold up as a separate bug.
Additional findings (outside the current diff — GitHub can't attach inline comments there):
-
🟣
scripts/tasks/download-docs.ts— downloadFromGitHub'snew Promise((resolve) => ...)never wires upreject, so a non-OK tarball fetch (throw new Error(...)at line 79) becomes an unhandled promise rejection instead of a caught error; the pre-build step crashes with an opaque uncaught-exception instead of a clean 'invalid version' message. Fix: reject(error) in a .catch on the fetch chain so callers get a normal rejected promise. This is a pre-existing gap, but the diff makes it fire far more often: it's now exercised for every scheduled gap-fill run (up to 10 historical tags plusnextnightly) and every manual/repository_dispatch version, versus onlylatestbefore.Extended reasoning...
In
downloadFromGitHub, the executor only destructuresresolve; whenresponse.okis false it throws inside.then(), which rejects the inner promise but nothing calls the outerreject, sodownload()'s returned promise never settles (or, depending on Node's unhandledRejection mode, the process aborts with a generic stack trace). Previously this path was only reachable when buildingdocs/latest(rare, well-tested target). Nowpublish-versioned-docs.yml's scheduled job callspre-build.ts --versionfor up to 10 back-filled release tags plusnextevery night, and any bad/renamed/rate-limited tag (electron/electron history has some renamed or yanked tags) triggers this path routinely, hanging or crashing that matrix job with no actionable error message.Verification: pre-existing. The defect is real: in
downloadFromGitHubthe executor at line 76 (return new Promise((resolve) => {) never capturesreject, and thethrow new Error(...)at lines 79-81 fires inside afetch(...).then(...)chain with no.catch. On a non-OK response the outer promise never settles and the thrown error becomes an unhandled rejection (crash in Node's default throw mode).…
`next` is now an alias for the newest alpha/beta prerelease tag, resolved by the CDN from versions.json, so prerelease tags are published as immutable bundles like stable ones and the mutable tree built from main is renamed to `dev`. versions.json gains `next`, `prereleases` and `dev`, the gap-fill also backfills the prereleases of the upcoming major, and the banner, dropdown, pre-build and workflow inputs follow the new labels. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SXtnHVnLbGQyY34y9CKffG
There was a problem hiding this comment.
Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.
Beyond the inline findings, I also checked whether DocsVersionBanner's "back to latest" link breaks under the versioned build's baseUrl: /docs/vX.Y.Z/ — i.e., whether Docusaurus treats that as a router basename and strips the version prefix from useLocation().pathname before parseDocsVersionPath/replaceDocsVersionInPath run. It doesn't: Docusaurus keeps the full baseUrl in the pathname (no basename-stripping router), so the version-prefix parsing in docs-version.ts still sees /docs/vX.Y.Z/... and resolves correctly.
Extended reasoning...
This run reported two new CONFIRMED findings (the retry_on: error vs. timeout gap in publish-versioned-docs.yml, and the unconditional useDocsVersions fetch running before the docs-only guard in DocsVersionDropdown.tsx) that will be posted as inline comments, so a human look is already signaled by those. My review body is limited to the narrow exception: recording one additional candidate issue that was investigated and ruled out this run beyond the inline findings — whether the versioned build's baseUrl acts as a react-router basename and strips the docs-version segment from useLocation().pathname, which would break DocsVersionBanner's link back to /docs/latest. Docusaurus does not use basename-based routing for baseUrl; the full baseUrl remains part of the pathname exposed via useLocation(), so parseDocsVersionPath's docs/<version>/... regex still matches on versioned builds and the banner link resolves correctly. This is offered purely as additional information about what was checked, not as a guarantee that the rest of the versioned-build routing is bug-free, and does not withdraw or supersede any previously reported findings on this PR.
dsanders11
left a comment
There was a problem hiding this comment.
There's a lot to chew on here, I've been working through it and exploring alternative solutions. For now requesting changes just to indicate I'm still actively working on this.
Resolve sidebars.js conflict by keeping the template call and port the boilerplates-and-clis removal from #1222 into sidebars-template.js. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SXtnHVnLbGQyY34y9CKffG
| export function replaceDocsVersionInPath( | ||
| pathname: string, | ||
| targetVersion: string, | ||
| ): string { | ||
| const parsed = parseDocsVersionPath(pathname); | ||
|
|
||
| if (!parsed) { | ||
| return pathname; | ||
| } | ||
|
|
||
| return `/docs/${targetVersion}${parsed.rest ? `/${parsed.rest}` : '/'}`; |
There was a problem hiding this comment.
🔴 Visitors on translated docs pages (e.g. /de/docs/latest/...) get bounced to the English page whenever they use the new version dropdown, even when picking the version they're already on. replaceDocsVersionInPath (docs-version.ts:157-167) rebuilds the URL as /docs/${targetVersion}${rest} with no locale segment, and DocsVersionDropdown's VersionLink (DocsVersionDropdown.tsx:97) feeds it useLocation().pathname, which includes the locale prefix on any non-English page. Fix: preserve the current locale segment (or resolve the link through Docusaurus's locale-aware URL builder) so every dropdown entry, across all 7 non-English locales, keeps the visitor on their language instead of only the English copy.
Extended reasoning...
docusaurus.config.ts keeps locales de/es/fr/ja/pt/ru/zh, each with its own baseUrl prefix (e.g. /de/), and registers the new custom-electronDocsVersionDropdown navbar item globally (docusaurus.config.ts:155-158), so it renders on every localized doc page too. On such a page useLocation().pathname is like /de/docs/latest/tutorial/introduction. DocsVersionDropdown builds every entry's href via docsVersionUrl(pathname, entry.version) (DocsVersionDropdown.tsx:97). docsVersionUrl calls replaceDocsVersionInPath, which uses parseDocsVersionPath to grab only the docs// part and returns /docs/${targetVersion}${rest}, discarding the /de prefix. The resulting absolute link is https://www.electronjs.org/docs/latest/tutorial/introduction — the English page — for every entry, including clicking 'latest' while already on latest. No such dropdown existed before this change, so this is a new regression, not pre-existing.
Verification: normal — The locale prefix is silently dropped when the version dropdown builds its links, so non-English docs readers are bounced to English. Chain: docusaurus.config.ts:65-95 keeps locales de/es/fr/ja/pt/ru/zh each with its own baseUrl (/de/ ...), and docusaurus.config.ts:155-158 registers custom-electronDocsVersionDropdown globally, so the dropdown renders on localized docs pages. On… | nit.…
There was a problem hiding this comment.
Confirmed, with a narrower scope: only docs/latest is translated (the versioned builds run with --locale en and i18n.locales: ['en']), so the latest entry was the one that could keep the reader's locale and didn't. Fix prepared: replaceDocsVersionInPath/docsVersionUrl take an optional locale prefix that is kept for latest and dropped for every other version, with the prefix resolved from useDocusaurusContext().i18n in the dropdown and banner, plus tests. Holding the push until the pending maintainer review lands, since the branch is under review.
Generated by Claude Code
Requested by Samuel Attard · Slack thread
Before: electronjs.org only serves the docs of the latest stable release at
/docs/latest; older releases, prereleases andmainhave no rendered docs.After: Every stable release since v38 and every prerelease of the unreleased major gets a self-contained snapshot at
/docs/vX.Y.Z/,/docs/dev/serveselectron/electron@main, and/docs/nextis a Worker alias to the newest prerelease;/docs/latestis unchanged.yarn pre-build --version vX.Y.Z[-alpha.N]|devdownloads that tag (ormain) intodocs/<version>/;nextand nightlies are rejected.ELECTRON_DOCS_VERSION=<version> yarn build:versionproduces a self-contained Docusaurus build under/docs/<version>/(English only,noindex, canonical to/docs/latest, own assets)./docs/versions.json.publish-versioned-docs.yml:workflow_dispatch,repository_dispatch, and a dailydevrebuild plus gap-fill of v38+ stable releases and the unreleased major's prereleases (10 per run), uploading via OIDC to the docs storage account./docs/versions.jsoncontract:{ latest, next, versions, prereleases, dev: { sha, updated } }.sidebars.jsnow comes from a shared template; its output is unchanged.Tested:
yarn lintclean,yarn test48/48, and offlinev44.3.0,v45.0.0-alpha.6anddevbuilds checked for banner, canonical,noindex, sidebar links and asset paths.Depends on electron/infra#354 applying first (Worker routing for
/docs/v*,/docs/dev*,/docs/next*,/docs/versions.json); the optional review nits are queued for the next push after maintainer review.Checklist
🤖 Generated with Claude Code
https://claude.ai/code/session_01SXtnHVnLbGQyY34y9CKffG
Generated by Claude Code