fix(docs-site): stop ignoring version snapshots; assert it stays that way#2313
Conversation
The publish-docs workflow preserves the downstream-owned versioning files (versions.json, versioned_docs/, versioned_sidebars/) before wiping docs-site/, then restores them after copying the SDK source over the top. But the SDK's docs-site/.gitignore (added in PR #2167 alongside the SidebarVersionSelect component, to keep local `docusaurus docs:version` scratch from accidentally being committed in the SDK) is itself copied down with the sync. So the subsequent `git add docs docs-site .nvmrc` silently skips the restored or newly-created snapshots, and the already-issued `git rm -rf docs-site` drops them from the index — net effect: every sync after the .gitignore landed deleted the snapshots from the docs repo. Force-add the three known snapshot paths to stage them regardless of the synced .gitignore. Mirrors the pattern Marie used in PR #2143 for docs/api/ (`git add -f docs/api`) before that path stopped needing it. After merging this, a manual `workflow_dispatch` is needed to re-create the version-0.48 snapshot downstream — subsequent NPM publishes will then keep snapshots intact automatically. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
| # accidentally land in upstream commits. That same .gitignore is then | ||
| # synced down with the rest of docs-site/, so the bare `git add` above |
There was a problem hiding this comment.
How does this work if the gitignore synced to the docs repo also includes the paths we're force adding?
There was a problem hiding this comment.
Good call — your question pointed at the real issue, not just a follow-up.
I scrapped the force-add and instead removed the version-snapshot entries from docs-site/.gitignore entirely. The downstream .gitignore now reflects what the repo actually tracks, no incoherence. The local-scratch protection those entries provided was already guarding a workflow that shouldn't happen in the SDK (versioning lives downstream) — and git status showing 138 untracked files plus PR review covers any accidental commit.
Also added a git check-ignore precondition at step 3a as defense-in-depth: if anyone ever re-adds those entries (or analogous ones for paths the downstream owns), the sync fails loudly with a pointer to the fix rather than silently dropping snapshots. Tested both directions — passes when gitignore is clean, fires with the exact error when entries are re-added.
PR description updated with full dry-run matrix including the negative case. Thanks for catching it.
…around Per review feedback (#2313 — thanks Marie), addressing the root cause rather than working around it. The previous commit added `git add -f` to the publish-docs workflow to bypass docs-site/.gitignore for versions.json / versioned_docs/ / versioned_sidebars/. That worked, but left the downstream `.gitignore` "lying" — claiming to ignore paths the docs repo actually tracks. That kind of incoherence is exactly what caused this incident in the first place (Aaron's ignore looked correct in source isolation, but broke a sync invariant nobody had encoded). Cleaner fix: just don't ignore them. The protection these entries provided (preventing local `docusaurus docs:version` scratch from accidentally landing in SDK PRs) was guarding a workflow that's architecturally out-of-place anyway — versioning lives downstream, so nobody should be generating snapshots locally in the SDK. If someone does, `git status` will show 138+ untracked files and review catches it. Re-ran all three dry-run scenarios against the actual embedded-sdk-docs @origin/main without force-add — all pass: - workflow_dispatch + MINOR=0.48 (bootstrap): 287 staged, 139 version-related - workflow_run + MINOR=0.49 (next minor): versions.json = ["0.49", "0.48"] - workflow_run + MINOR=0.48 (patch refresh): refresh path executes cleanly Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Defense-in-depth check in publish-docs.yaml that runs `git check-ignore` against the synced docs-site/.gitignore for the three downstream-owned snapshot paths (versions.json, versioned_docs/, versioned_sidebars/). If a future SDK change re-introduces ignore rules for any of these (as #2167 did, silently deleting snapshots from the docs repo for two weeks), the sync now fails loudly with a clear error message pointing at the fix — instead of `git add` quietly skipping the restored snapshots and the workflow happily pushing the deletion downstream. Fails fast: runs in step 3a, before snapshot creation/refresh, so a gitignore regression never gets the chance to corrupt the archive. Tested both directions: - Positive (gitignore clean, this PR's state): check passes, all 3 dry-run scenarios (bootstrap, new minor, patch refresh) succeed - Negative (gitignore entries re-added): check fires with the exact error message before any data is staged Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
| # ---- Step 3a: gitignore precondition check ---- | ||
| # This sync's correctness depends on docs-site/.gitignore NOT | ||
| # ignoring the downstream-owned snapshot paths. If the SDK ever | ||
| # re-introduces such rules (as #2167 did, causing snapshots to | ||
| # silently disappear from the docs repo for two weeks), fail | ||
| # loudly here rather than letting `git add` quietly skip the | ||
| # restored snapshots below. | ||
| for path in docs-site/versions.json docs-site/versioned_docs docs-site/versioned_sidebars; do | ||
| if git check-ignore -q "$path"; then | ||
| echo "ERROR: $path is gitignored by the synced docs-site/.gitignore." >&2 | ||
| echo "Cross-repo sync requires this path to be tracked downstream." >&2 | ||
| echo "Remove the corresponding entry from the SDK's docs-site/.gitignore." >&2 | ||
| exit 1 | ||
| fi | ||
| done |
Summary
docs-site/.gitignorelistsversions.json,versioned_docs/, andversioned_sidebars/as ignored. Added in #2167 with the intent of keeping localdocusaurus docs:versionscratch out of SDK PRs. But that same.gitignoresyncs downstream intoGusto/embedded-sdk-docs, where those files are the tracked archive — and the sync workflow's preserve-then-restore-then-git addflow silently drops them on every run since.Net effect: the version picker on sdk.gusto.com has been gone since June 16. Snapshot data has been silently deleted from
embedded-sdk-docs/mainfor two weeks.How docs versioning is split between repos
Latest in the SDK, archive in the docs repo. The SDK is purely the content producer;
Gusto/embedded-sdk-docscarries the version archive + deployment.embedded-react-sdk)embedded-sdk-docs)docs/(live current)docs-site/(config, plugins, theme)docs-site/versions.jsondocs-site/versioned_docs/version-X.Y/docs-site/versioned_sidebars/version-X.Y-sidebars.jsongh-pagessitedocusaurus builddocs-site/docusaurus.config.ts:11–25is identical in both repos. It checksversions.jsonat runtime — if absent, single-version site; if present, multi-version with picker. Snapshots are created bynpx docusaurus docs:version <X.Y>, whichpublish-docs.yamlinvokes inside the docs repo's working tree during sync — never in the SDK.Root cause
The sync workflow does this each run:
versioned_docs//versioned_sidebars//versions.jsonto/tmp✓git rm -rf docs-site— removes them from the index ✓cp -R ../docs-site .— brings in the SDK's.gitignore💥git add docs docs-site .nvmrc— silently skips them (gitignored aftergit rm, now untracked) 💥Smoking gun:
cbb5e90d— first sync after #2167 — deletedversions.jsonand every file underversioned_docs/version-0.47/.Fix (two parts)
1. Remove the ignore entries from
docs-site/.gitignore(root cause)The protection they provided (preventing accidental local-scratch commits in SDK PRs) was guarding a workflow that's already architecturally wrong — versioning lives downstream. If someone does run
docusaurus docs:versionin the SDK locally,git statusshows 138+ untracked files and review catches it.2. Defense-in-depth: assert the rules can't come back undetected
Added a
git check-ignoreprecondition at step 3a inpublish-docs.yaml. If anyone ever re-introduces ignore rules for the three downstream-owned paths, the sync fails loudly with a pointer to the fix instead of silently corrupting the archive:Considered force-add in the workflow as an alternative; rejected because force-add silently recovers from gitignore drift, which is the same failure mode that caused this incident (silent data loss). The assertion makes the architectural invariant explicit and surfaces regressions immediately.
Why this approach over the original PR shape
The first version added a workflow
git add -fblock to bypass the gitignore. That worked, but Marie noted: the downstream.gitignorewould still claim to ignore paths the repo tracks. That's exactly the kind of "rule that doesn't match reality" incoherence that caused this incident. Cleaner to fix at the source AND add a check that surfaces any future regression.Dry-run evidence
Ran the workflow body line-for-line locally against
Gusto/embedded-sdk-docs@origin/main, using the SDK at this PR's commit (gitignore cleaned, assertion in place). Plaingit add docs docs-site .nvmrc— no force-add.workflow_dispatch0.48versions.json = ["0.48"]0.49.0)workflow_run0.49versions.json = ["0.49", "0.48"]0.48.4)workflow_run0.48workflow_dispatch0.48Reproduction
After merge
Runs the sync as
workflow_dispatch. Since the docs repo currently has noversions.json,SNAPSHOT_ACTION=create→npx docusaurus docs:version 0.48runs, plaingit addstages everything, sync commits and pushes. You'll see adocs: sync ... — created version-0.48 snapshotcommit downstream, then a Buildkite publish to gh-pages.Picker won't show yet (only one version, self-hides on
versions.length <= 1) — appears on the next release whenversion-0.49lands alongside.Test plan
embedded-sdk-docs@origin/mainworktree,docusaurus docs:version 0.48,git add docs-site, confirm nothing staged with current SDK gitignoregit add docs-sitestages all snapshot filesversions.json = ["0.47", "0.48"]: confirmversionSelect__TMDclass andVersiontrigger render inbuild/docs/index.htmlworkflow_dispatch+MINOR=0.48(bootstrap): assertion OK, 139 version files stagedworkflow_run+MINOR=0.49(next minor): assertion OK, both 0.48 and 0.49 trackedworkflow_run+MINOR=0.48(patch refresh): assertion OK, refresh path executesworkflow_dispatchand verify the next sync commit inembedded-sdk-docsre-createsversioned_docs/version-0.48/andversions.json🤖 Generated with Claude Code