Add versioned SDK & CLI docs#19800
Draft
CamSoper wants to merge 21 commits into
Draft
Conversation
Publish immutable, per-release snapshots of each SDK and CLI docset to a
permanent S3 bucket, served via CloudFront under /docs/versioned/{tool}/{version}/.
The publish step is additive to the existing doc-generation workflows; historical
versions never enter git, Hugo, or the per-deploy origin sync, so site build and
deploy times are unaffected.
- infrastructure/versioned-docs/: permanent bucket + GitHub-OIDC publisher role
- main stack: /docs/versioned/* origin + behavior behind a versionedDocsStack config,
with a cache policy that honors origin Cache-Control (no max-age=60 clobber)
- scripts/versioned-docs/: inject, publish, snapshot-cli (vendors assets + trims the
CLI left-nav to a self-contained command list), redact, head-tag assertion
- static/js/versioned-docs.{js,css}: evergreen version selector (fails silent)
- 10 release workflows wired with a safe gate: publishing stays off until the repo
variable VERSIONED_DOCS_ENABLED is set, so real releases are unaffected
- backfill prereqs: update_repos.sh honors a requested tag; generate_python_docs.sh
pins the requested version
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Collaborator
|
Your site preview for commit 213c081 is ready! 🎉 http://www-testing-pulumi-docs-origin-pr-19800-213c0819.s3-website.us-west-2.amazonaws.com |
Collaborator
Lighthouse Performance ReportCommit: 213c081 | Metric definitions
|
…rkflows The dotnet, java, typescript, policy-ts, and both esc-sdk (dotnet/ts) workflows check the docs repo out into a `docs/` subdirectory because they also clone the SDK source repo alongside it. Their generation steps already run with `working-directory: docs`, but the new "Publish versioned snapshot" step did not — so `./scripts/versioned-docs/publish-version.sh` resolved against the runner root and failed with exit 127 (No such file or directory). Because the step is continue-on-error, the job still went green while publishing nothing. Add `working-directory: docs` to the publish step in the six split-checkout workflows. The four root-checkout workflows (cli, python, policy-python, esc-python) already worked and are unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
DocFX/Javadoc SDKs (.NET, Java) emit type-named .html files and no root index.html — their `/docs/reference/pkg/<sdk>/` landing page is generated by Hugo (`_index.md`) and is not part of the prebuilt API output we snapshot. As a result the bare version root (`/docs/versioned/dotnet/vX/`) 404'd, which also broke the version-selector's root fallback. When the prebuilt has no root index.html, synthesize a minimal landing that links each top-level namespace entry (`<ns>/<ns>.html`, or `<ns>/` when that dir has its own index). Done before tag injection so the landing also gets the selector loader, noindex, and canonical. SDKs that already ship a root index.html (TypeDoc/nodejs, Sphinx/python) are untouched. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…st selector UI UI rework (versioned-docs.js/.css), all evergreen — existing archives pick it up with no republish: - Archives + live SDK pages (their own generator themes, no site chrome) now render a fixed top bar with max z-index that measures its own height and offsets the known themes' fixed chrome (Sphinx RTD sidebar, DocFX navbar, TypeDoc toolbar), so it never clips. Fixes the Python (RTD) overlap and pre-empts the same on .NET (DocFX). - Live CLI command pages (with site chrome + a mount) keep the quiet inline control. - One shared dropdown control, context-appropriate framing (archive adds the view-latest notice; latest is just the quiet dropdown). Live SDK selectors (no commit churn): scripts/versioned-docs/inject-live-sdk-selectors.sh injects the latest-mode loader into the SDK reference docsets in public/ at site-build time (wired into build-site.sh). Nested policy/ESC subtrees are tagged with their own tool id first; the idempotent injector leaves them alone on the parent pass. Backfill without noisy PRs: add a `publish_only` workflow input to all 10 SDK/CLI doc workflows; when set, the latest-docs PR-creation job is skipped while the versioned snapshot still publishes. Fixes: trim-cli-nav.py labels the version-root self-link "Overview" instead of a second "pulumi"; corrected the nodejs policy/ESC --src/--live-root paths (they were missing the pulumi/ segment and would have failed on "src dir not found"). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The nodejs (/docs/reference/pkg/nodejs/pulumi/) and .NET ESC SDK (/docs/reference/pkg/dotnet/esc-sdk/) liveRoots are bare container dirs with no index.html — the site links to a deeper entry, so the bare path 404s, and so does the version selector's "View latest" for those tools. At build time, drop a small redirect landing at those roots pointing to the real entry, only when one isn't already present. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nx doctree cruft - Guard the run-id branch push in all 10 doc-gen workflows behind publish_only != true, so backfill (publish-only) runs no longer leave orphan remote branches behind. - Direct Sphinx's .doctrees pickle cache out of the python output dir (it defaults to OUTDIR/.doctrees), keeping build state out of the committed prebuilt and the immutable snapshots. - Belt-and-braces: exclude *.doctree/.doctrees/.buildinfo from the versioned-docs publish sync. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017QYJeFHZw6NLaUu2wLpuay
The fixed selector bar reserves space by measuring its own height into --vdocs-bar-h, but that measurement ran before the async-loaded /js/versioned-docs.css applied — so the bar was measured unstyled (too short) and the reserved gap was too small, leaving the generator's own top chrome tucked under the bar on every live SDK docset. - Re-measure the bar via ResizeObserver (fires on the initial observe and whenever the box changes: CSS applies, web fonts load, wrap, resize), with load/onload/rAF fallbacks where ResizeObserver is unavailable. - Offset the generator top chrome that wasn't covered: DocFX's Bootstrap-3 .navbar-fixed-top (.NET, ESC .NET) and Javadoc's .fixedNav (Java). The old rule only matched DocFX's Bootstrap-4 .navbar.fixed-top. Verified headless across TypeDoc (nodejs), DocFX (dotnet), and Javadoc (java); the policy/ESC docsets share these generators. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017QYJeFHZw6NLaUu2wLpuay
The selector banner only rendered on individual command pages (single.html). The commands section index (/docs/iac/cli/commands/) is the canonical live root the selector points at, so render the banner there too — gated on the cli_command_page cascade, which only the commands section carries. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017QYJeFHZw6NLaUu2wLpuay
/docs/iac/cli/ and /docs/iac/cli/commands/ both rendered the full
{{< pulumi-command >}} reference, so they were near-duplicates. Drop the
full command dump from the overview and link to the canonical command
reference instead; the overview keeps the intro, install, common-commands
quick links, environment variables, and exit codes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017QYJeFHZw6NLaUu2wLpuay
CLI archives are Hugo-rendered site pages, so they need the site CSS. Previously snapshot-cli-docs.sh vendored a frozen per-version copy of the fingerprinted bundle into each archive's _vassets/ — robust, but it meant re-theming the whole back-catalog required republishing every version. Now every CLI archive references one shared, permanent contract URL, /css/versioned-docs-archive.css, re-derived from the docs CSS bundle on every site build (build-site.sh). Update that one file and the entire CLI back-catalog re-themes at once. - build-site.sh: copy public/css/bundle.<id>.css -> versioned-docs-archive.css after the CSS purge. - snapshot-cli-docs.sh: rewrite archive CSS refs to the shared bundle instead of vendoring; drop ALL site /js/ <script src> bundles (archives are static — the nav is trimmed to a static list, and the site bundle lazy-loads fingerprinted chunks the snapshot never vendored, which would 404 once the main site rotates). Removing the live latest-mode selector tag also lets the archive-mode tag inject cleanly. Add --out-dir for a local dry run (no publish). Verified headless: a dry-run snapshot renders fully themed from the shared bundle with a correct archive-mode (vX.Y.Z) selector and no heavy site JS. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017QYJeFHZw6NLaUu2wLpuay
… semver A back-corpus run (publish_only) of an OLD release went through publish-version.sh with its defaults — mark-latest=true and date=now — so it stole the "latest" flag and, dated today, sorted ahead of newer versions. Surfaced by the first real CLI back-corpus run (v3.244.0). - publish-version.sh: order the manifest newest-first by SEMVER (numeric component compare, so 3.10.0 > 3.9.0), not by publish date — a version archived today still sorts below newer ones released earlier. - snapshot-cli-docs.sh: accept --no-mark-latest / --date and pass them to publish-version.sh. - All 10 doc-gen workflows: on publish_only (backfill), pass --no-mark-latest so the archived old version doesn't claim "latest". Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017QYJeFHZw6NLaUu2wLpuay
…ce-free publish-version.sh updates the manifest with a read-modify-write, which races under a parallel back-corpus backfill (last-write-wins drops entries). rebuild-manifest.sh instead regenerates versions.json from the bucket's archive prefixes in one shot: semver-sorted, with the live --latest-version emitted as a liveRoot/latest entry. Run it once after a parallel fan-out to get a consistent manifest. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017QYJeFHZw6NLaUu2wLpuay
snapshot-cli-docs.sh deleted the per-command markdown content-negotiation outputs, so archived CLI versions had no .md for LLMs/agents (only the live docs did). Keep them, and rewrite their frontmatter `url:` and cross-command links to the versioned prefix (the new replace_in_content covers .md as well as .html). The HTML stays noindex/canonical-to-live; the .md are the machine-readable format, now versioned too. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017QYJeFHZw6NLaUu2wLpuay
The .NET / ESC .NET (DocFX) pages put position:fixed on the <header> element itself (docfx.css), while the navbar inside is just .navbar.navbar-inverse — so the old .navbar-fixed-top offset matched nothing and the bar overlapped the navbar/logo on those archives. Offset `header` instead. Safe across generators: it also matches TypeDoc's <header class="tsd-page-toolbar"> (same offset value, already applied), Sphinx has no <header>, and Javadoc's <header> is static so `top` is a no-op there. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017QYJeFHZw6NLaUu2wLpuay
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017QYJeFHZw6NLaUu2wLpuay
The Reference > CLI > Pulumi CLI nesting was pointless once the ESC CLI was retired, leaving the CLI container wrapping a single Pulumi CLI entry. Remove the reference-cli container node and re-parent Pulumi CLI directly under reference-home (weight 1, where CLI was). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017QYJeFHZw6NLaUu2wLpuay
The trimmed CLI-archive left nav is a static command list with no search. Add a quiet client-side substring filter above it, injected by the shared loader (not baked into snapshots) so it works on already-published archives and stays controllable from the main repo without re-publishing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017QYJeFHZw6NLaUu2wLpuay
CLI snapshots strip the site JS bundle, so the right-rail On this page ToC (JS-populated by theme/src/ts/misc.ts) shipped empty, and the Edit this Page / Request a Change links, the Copy Page / top-button web components, and the feedback widget were dead or meaningless on a frozen snapshot. trim-cli-nav.py now, in the same pass that trims the left nav: drops the right-rail actions/feedback chrome, and statically rebuilds the ToC from each page id'd h2/h3 (mirroring misc.ts) into the desktop + mobile lists. Stdlib only; each pass no-ops if its hook is absent. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017QYJeFHZw6NLaUu2wLpuay
e3e1950 to
23dc488
Compare
The live "Copy Page" menu is the pulumi-llm-menu web component, compiled into the site JS bundle that CLI snapshots strip — so the bare element can't work on an archive. But every archived page publishes its markdown sibling (index.md), so the menu's actions still apply. versioned-docs.js now rebuilds the component's exact DOM/classes on CLI archive pages (loader-injected like the nav filter, so it works on already-published archives without re-snapshotting) and wires all five actions against the page's own index.md: Copy URL, Copy as Markdown, View as Markdown, Open in ChatGPT, Open in Claude. The shared archive theme bundle already carries the .llm-menu-* styles; versioned-docs.css only restores the trigger/chevron sizing the live component set inline. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017QYJeFHZw6NLaUu2wLpuay
…llow wire fns The loader runs as an async script. When the DOM is already parsed (typical behind a CDN), onReady ran its callback synchronously — mid-IIFE, before the var ICON / helper assignments below it — so wireCliCopyMenu hit an undefined ICON and threw. The outer try swallowed it AND aborted the manifest fetch, so both the Copy Page menu and the version selector silently vanished on live archives (worked locally only because the script there runs mid-parse and defers to DOMContentLoaded). Defer to a macrotask instead: the whole IIFE has executed and wire-fn errors no longer abort the selector render. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017QYJeFHZw6NLaUu2wLpuay
CLI archives kept the inline pre-paint script that reads the shared "pulumi-docs-theme" key and sets data-theme on <html>, and the archive theme bundle carries the dark overrides + .docs-theme-toggle styles — so a snapshot already renders in the user's chosen theme. But the toggle control lives in the trimmed sidebar and its wiring (theme/src/ts/docs-theme.ts) is in the stripped site bundle, so there was no way to change it from an archive. versioned-docs.js now injects the same three-button control into the CLI archive nav and wires it against the identical localStorage key + data-theme/ -theme-pref attributes, so a preference set anywhere on the docs site — main site or archive — stays consistent and persists everywhere. Loader-injected, so it applies to every already-published archive without re-snapshotting; CLI archives only (SDK generator themes have no docs dark mode). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017QYJeFHZw6NLaUu2wLpuay
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Publishes immutable, per-release snapshots of each SDK and CLI docset to a permanent S3 bucket, served through the existing CloudFront distribution under
/docs/versioned/{tool}/{version}/. The publish step is additive to the doc-generation workflows that already run on each release — historical versions never enter git, Hugo, or the per-deploy origin sync, so site build and deploy times are unaffected.What's here
infrastructure/versioned-docs/— permanent bucket (pulumi-docs-versioned-{env}, website hosting, versioning, public-read GetObject,protect) + a GitHub-OIDC publisher role scoped torepo:pulumi/docs.versionedDocsStackconfig, adds one origin + a/docs/versioned/*behavior (ordered ahead of dotnet//docs/*) and a cache policy that honors each object's originCache-Control, plus a pass-through response-headers policy so immutable archives aren't clobbered tomax-age=60.scripts/versioned-docs/—inject-version-switcher,publish-version,snapshot-cli-docs(vendors fingerprinted assets and trims the CLI left-nav to a self-contained command list with Docs Home / Latest Version),redact-version,assert-head-tag, plus a runbookREADME.static/js/versioned-docs.{js,css}— evergreen version selector; fails silent where no manifest exists.VERSIONED_DOCS_ENABLEDis set (or on an explicit dispatch). Real releases are unaffected until then.update_repos.shhonors a requested tag;generate_python_docs.shpins the requested version.Status
www.pulumi-test.io): bucket + behavior deployed, immutable cache headers confirmed, full publish → serve → redact cycle exercised, CLI snapshot validated on a real page (vendored styling, trimmed nav, single canonical).scripts/versioned-docs/README.md(deploy the prod stack, wire the main prod stack, deploy the site, setVERSIONED_DOCS_ENABLED+VERSIONED_DOCS_PROD_DISTRIBUTION_ID).