Skip to content

Commit 04afc49

Browse files
authored
docs(release): support release-line docs refresh (#458)
Signed-off-by: Roel de Cort <roel.decort@adfinis.com>
1 parent b9e02fb commit 04afc49

12 files changed

Lines changed: 287 additions & 8 deletions

File tree

.github/workflows/ci.yml

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,56 @@ jobs:
526526
- name: Build docs
527527
uses: ./.github/actions/docs-build
528528

529+
release-line-docs:
530+
name: Release-line Docs Snapshot
531+
needs: changes
532+
if: github.event_name == 'pull_request' && startsWith(github.base_ref, 'release-') && needs.changes.outputs.docs == 'true'
533+
runs-on: *runner
534+
steps:
535+
- name: Checkout
536+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
537+
with:
538+
fetch-depth: 0
539+
540+
- name: Verify release-line docs snapshot changed
541+
env:
542+
BASE_REF: ${{ github.base_ref }}
543+
BASE_SHA: ${{ github.event.pull_request.base.sha }}
544+
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
545+
run: |
546+
set -euo pipefail
547+
548+
if [[ ! "${BASE_REF}" =~ ^release-([0-9]+)\.([0-9]+)$ ]]; then
549+
echo "Release-line docs check only supports release-X.Y branches; got ${BASE_REF}" >&2
550+
exit 1
551+
fi
552+
553+
docs_version="${BASH_REMATCH[1]}.${BASH_REMATCH[2]}.0"
554+
git fetch --no-tags --prune --depth=1 origin "${BASE_SHA}" || true
555+
556+
changed="$(git diff --name-only "${BASE_SHA}" "${HEAD_SHA}")"
557+
docs_changed="$(grep -E '^(docs/|website/sidebars\.ts$)' <<< "${changed}" || true)"
558+
if [[ -z "${docs_changed}" ]]; then
559+
echo "No source docs changed."
560+
exit 0
561+
fi
562+
563+
snapshot_changed="$(
564+
grep -E "^website/versioned_docs/version-${docs_version//./\\.}/|^website/versioned_sidebars/version-${docs_version//./\\.}-sidebars\\.json$|^website/versions\\.json$" <<< "${changed}" || true
565+
)"
566+
if [[ -n "${snapshot_changed}" ]]; then
567+
echo "Release-line docs snapshot changed for ${docs_version}."
568+
exit 0
569+
fi
570+
571+
{
572+
echo "PRs against ${BASE_REF} that change source docs must refresh the ${docs_version} release-line docs snapshot."
573+
echo
574+
echo "Run from the release branch and commit the generated files:"
575+
echo " make docs-refresh-version DOCS_VERSION=${docs_version}"
576+
} >&2
577+
exit 1
578+
529579
helm:
530580
name: Helm Chart
531581
needs: changes
@@ -1008,6 +1058,7 @@ jobs:
10081058
- fuzz
10091059
- openbao-config-compat
10101060
- docs
1061+
- release-line-docs
10111062
- helm
10121063
- helm-e2e-smoke
10131064
- build-artifacts
@@ -1032,6 +1083,7 @@ jobs:
10321083
FUZZ: ${{ needs.fuzz.result }}
10331084
OPENBAO_CONFIG_COMPAT: ${{ needs.openbao-config-compat.result }}
10341085
DOCS: ${{ needs.docs.result }}
1086+
RELEASE_LINE_DOCS: ${{ needs.release-line-docs.result }}
10351087
HELM: ${{ needs.helm.result }}
10361088
HELM_E2E_SMOKE: ${{ needs.helm-e2e-smoke.result }}
10371089
BUILD_ARTIFACTS: ${{ needs.build-artifacts.result }}
@@ -1066,6 +1118,7 @@ jobs:
10661118
fuzz=${FUZZ}
10671119
openbao-config-compat=${OPENBAO_CONFIG_COMPAT}
10681120
docs=${DOCS}
1121+
release-line-docs=${RELEASE_LINE_DOCS}
10691122
helm=${HELM}
10701123
helm-e2e-smoke=${HELM_E2E_SMOKE}
10711124
build-artifacts=${BUILD_ARTIFACTS}

docs/contribute/release-management.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ journey: contribute
7979

8080
<Callout type="important" title="Stable release-line docs snapshots">
8181

82-
Before merging the first stable `X.Y.0` release PR for a release line, snapshot the docs for that release line and commit the generated artifacts. Patch releases in the same line publish release notes and reuse the `X.Y.0` docs snapshot. Prereleases continue to use `/docs/next` and release notes only; do not add patch, `-alpha`, `-beta`, or `-rc` entries to `website/versions.json`.
82+
Before merging the first stable `X.Y.0` release PR for a release line, snapshot the docs for that release line and commit the generated artifacts. Patch releases in the same line reuse the `X.Y.0` docs version, but user-facing docs fixes for that patch must refresh the existing `X.Y.0` snapshot from the release branch. Prereleases continue to use `/docs/next` and release notes only; do not add patch, `-alpha`, `-beta`, or `-rc` entries to `website/versions.json`.
8383

8484
</Callout>
8585

@@ -98,6 +98,16 @@ GitHub Actions runs workflow definitions from the branch that receives the push.
9898
This updates `website/versioned_docs/`, `website/versioned_sidebars/`, and `website/versions.json`.
9999
</CommandBlock>
100100

101+
<CommandBlock
102+
language="bash"
103+
label="configure"
104+
title="Refresh docs for a patch release line"
105+
code={`git switch release-X.Y
106+
make docs-refresh-version DOCS_VERSION=X.Y.0`}
107+
>
108+
Run this from the release branch after backporting docs that apply to the patch release. This updates the existing release-line docs snapshot without adding a patch version to `website/versions.json`.
109+
</CommandBlock>
110+
101111
<CommandBlock
102112
language="bash"
103113
label="configure"

mk/development.mk

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,11 @@ docs-version: docs-deps ## Snapshot the current docs into a versioned Docusaurus
389389
@test -n "$(DOCS_VERSION)" || { echo "DOCS_VERSION is required, for example: make docs-version DOCS_VERSION=1.2.3"; exit 1; }
390390
@$(DOCS_NPM) --prefix "$(DOCS_DIR)" run version:docs -- "$(DOCS_VERSION)"
391391

392+
.PHONY: docs-refresh-version
393+
docs-refresh-version: docs-deps ## Refresh an existing release-line docs snapshot from the checked-out docs. Set DOCS_VERSION=<X.Y.0>.
394+
@test -n "$(DOCS_VERSION)" || { echo "DOCS_VERSION is required, for example: make docs-refresh-version DOCS_VERSION=1.2.0"; exit 1; }
395+
@$(DOCS_NPM) --prefix "$(DOCS_DIR)" run refresh:docs-version -- "$(DOCS_VERSION)"
396+
392397
# TODO(user): To use a different vendor for e2e tests, modify the setup under 'tests/e2e'.
393398
# The default setup assumes Kind is pre-installed and builds/loads the Manager Docker image locally.
394399
# CertManager is installed by default; skip with:

website/docusaurus.config.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,12 @@ const config: Config = {
6565
label: 'next',
6666
path: 'next',
6767
},
68+
'0.2.0': {
69+
label: '0.2.x',
70+
},
71+
'0.1.0': {
72+
label: '0.1.x',
73+
},
6874
},
6975
},
7076
blog: false,

website/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"write-translations": "docusaurus write-translations",
1717
"write-heading-ids": "docusaurus write-heading-ids",
1818
"version:docs": "node ./scripts/snapshot-version.mjs",
19+
"refresh:docs-version": "node ./scripts/refresh-version.mjs",
1920
"verify:docs-version": "node ./scripts/verify-docs-version.mjs",
2021
"typecheck": "tsc --noEmit",
2122
"test:e2e": "playwright test"
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import {spawnSync} from 'node:child_process';
2+
import fs from 'node:fs/promises';
3+
import path from 'node:path';
4+
5+
const version = process.argv[2];
6+
const websiteRoot = process.cwd();
7+
const versionsPath = path.join(websiteRoot, 'versions.json');
8+
9+
if (!version) {
10+
console.error('Usage: npm run refresh:docs-version -- <version>');
11+
process.exit(1);
12+
}
13+
14+
function isStableLineVersion(candidate) {
15+
return /^\d+\.\d+\.0$/.test(candidate);
16+
}
17+
18+
function run(command, args) {
19+
const result = spawnSync(command, args, {
20+
cwd: websiteRoot,
21+
stdio: 'inherit',
22+
});
23+
24+
if (result.status !== 0) {
25+
throw new Error(`${[command, ...args].join(' ')} failed with status ${result.status ?? 1}`);
26+
}
27+
}
28+
29+
async function pathExists(target) {
30+
try {
31+
await fs.access(target);
32+
return true;
33+
} catch (error) {
34+
if (error?.code === 'ENOENT') {
35+
return false;
36+
}
37+
throw error;
38+
}
39+
}
40+
41+
async function moveIfExists(from, to) {
42+
if (await pathExists(from)) {
43+
await fs.mkdir(path.dirname(to), {recursive: true});
44+
await fs.rename(from, to);
45+
return true;
46+
}
47+
return false;
48+
}
49+
50+
async function restoreIfMoved(from, to, moved) {
51+
if (!moved) {
52+
return;
53+
}
54+
await fs.rm(to, {recursive: true, force: true});
55+
await fs.mkdir(path.dirname(to), {recursive: true});
56+
await fs.rename(from, to);
57+
}
58+
59+
if (!isStableLineVersion(version)) {
60+
console.error(
61+
`Release-line docs refresh only supports stable release-line versions (X.Y.0). Patch releases update the existing release-line snapshot: ${version}`,
62+
);
63+
process.exit(1);
64+
}
65+
66+
const raw = await fs.readFile(versionsPath, 'utf8');
67+
const originalVersions = JSON.parse(raw);
68+
69+
if (!Array.isArray(originalVersions)) {
70+
throw new Error('versions.json is not an array');
71+
}
72+
73+
const dedupedOriginalVersions = [...new Set(originalVersions)];
74+
if (!dedupedOriginalVersions.includes(version)) {
75+
console.error(
76+
`Docs version ${version} is not present in versions.json. Create the release-line snapshot first with: make docs-version DOCS_VERSION=${version}`,
77+
);
78+
process.exit(1);
79+
}
80+
81+
const tempRoot = path.join(websiteRoot, `.tmp-refresh-version-${process.pid}`);
82+
const versionedDocsDir = path.join(websiteRoot, 'versioned_docs', `version-${version}`);
83+
const versionedSidebarPath = path.join(
84+
websiteRoot,
85+
'versioned_sidebars',
86+
`version-${version}-sidebars.json`,
87+
);
88+
const backupDocsDir = path.join(tempRoot, 'versioned_docs', `version-${version}`);
89+
const backupSidebarPath = path.join(
90+
tempRoot,
91+
'versioned_sidebars',
92+
`version-${version}-sidebars.json`,
93+
);
94+
95+
let movedDocs = false;
96+
let movedSidebar = false;
97+
98+
try {
99+
await fs.rm(tempRoot, {recursive: true, force: true});
100+
movedDocs = await moveIfExists(versionedDocsDir, backupDocsDir);
101+
movedSidebar = await moveIfExists(versionedSidebarPath, backupSidebarPath);
102+
await fs.writeFile(
103+
versionsPath,
104+
`${JSON.stringify(dedupedOriginalVersions.filter((candidate) => candidate !== version), null, 2)}\n`,
105+
);
106+
107+
run(process.platform === 'win32' ? 'npm.cmd' : 'npm', ['run', 'prepare:contribute']);
108+
run(process.platform === 'win32' ? 'npx.cmd' : 'npx', ['docusaurus', 'docs:version', version]);
109+
110+
await fs.writeFile(versionsPath, `${JSON.stringify(dedupedOriginalVersions, null, 2)}\n`);
111+
await fs.rm(tempRoot, {recursive: true, force: true});
112+
} catch (error) {
113+
await fs.rm(versionedDocsDir, {recursive: true, force: true});
114+
await fs.rm(versionedSidebarPath, {force: true});
115+
await restoreIfMoved(backupDocsDir, versionedDocsDir, movedDocs);
116+
await restoreIfMoved(backupSidebarPath, versionedSidebarPath, movedSidebar);
117+
await fs.writeFile(versionsPath, `${JSON.stringify(dedupedOriginalVersions, null, 2)}\n`);
118+
await fs.rm(tempRoot, {recursive: true, force: true});
119+
console.error(error.message);
120+
process.exit(1);
121+
}

website/scripts/snapshot-version.mjs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,18 @@ async function dedupeVersions() {
3333
await fs.writeFile(versionsPath, `${JSON.stringify(deduped, null, 2)}\n`);
3434
}
3535

36+
const prepareResult = spawnSync(
37+
process.platform === 'win32' ? 'npm.cmd' : 'npm',
38+
['run', 'prepare:contribute'],
39+
{
40+
stdio: 'inherit',
41+
},
42+
);
43+
44+
if (prepareResult.status !== 0) {
45+
process.exit(prepareResult.status ?? 1);
46+
}
47+
3648
const result = spawnSync(
3749
process.platform === 'win32' ? 'npx.cmd' : 'npx',
3850
['docusaurus', 'docs:version', version],

website/tests/behavior.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,15 @@ test('version dropdown switches from next docs to the stable release line', asyn
4242
await versionDropdown.hover();
4343

4444
const archivedRelease = page.locator('.dropdown__menu').getByRole('link', {
45-
name: '0.2.0',
45+
name: '0.2.x',
4646
exact: true,
4747
});
4848
await expect(archivedRelease).toBeVisible();
4949
await archivedRelease.click();
5050

5151
await expect(page).toHaveURL(/\/openbao-operator\/docs\/get-started\/deployment-decision-guide$/);
5252
await expect(page.getByText('Published release documentation')).toBeVisible();
53-
await expect(page.getByText('Version: 0.2.0')).toBeVisible();
53+
await expect(page.getByText('Version: 0.2.x')).toBeVisible();
5454
});
5555

5656
test.describe('curated legacy redirects stay alive', () => {

website/tests/smoke.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ test('stable docs expose the current release banner', async ({page}) => {
4646

4747
await expect(page.getByRole('heading', {name: 'OpenBao Operator documentation'})).toBeVisible();
4848
await expect(page.getByText('Published release documentation')).toBeVisible();
49-
await expect(page.getByText('Version: 0.2.0')).toBeVisible();
49+
await expect(page.getByText('Version: 0.2.x')).toBeVisible();
5050
});
5151

5252
test('architecture section exposes grouped local navigation', async ({page}) => {

website/versioned_docs/version-0.2.0/contribute/release-management.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ journey: contribute
7171

7272
<Callout type="important" title="Stable release-line docs snapshots">
7373

74-
Before merging the first stable `X.Y.0` release PR for a release line, snapshot the docs for that release line and commit the generated artifacts. Patch releases in the same line publish release notes and reuse the `X.Y.0` docs snapshot. Prereleases continue to use `/docs/next` and release notes only; do not add patch, `-alpha`, `-beta`, or `-rc` entries to `website/versions.json`.
74+
Before merging the first stable `X.Y.0` release PR for a release line, snapshot the docs for that release line and commit the generated artifacts. Patch releases in the same line reuse the `X.Y.0` docs version, but user-facing docs fixes for that patch must refresh the existing `X.Y.0` snapshot from the release branch. Prereleases continue to use `/docs/next` and release notes only; do not add patch, `-alpha`, `-beta`, or `-rc` entries to `website/versions.json`.
7575

7676
</Callout>
7777

@@ -84,6 +84,16 @@ Before merging the first stable `X.Y.0` release PR for a release line, snapshot
8484
This updates `website/versioned_docs/`, `website/versioned_sidebars/`, and `website/versions.json`.
8585
</CommandBlock>
8686

87+
<CommandBlock
88+
language="bash"
89+
label="configure"
90+
title="Refresh docs for a patch release line"
91+
code={`git switch release-X.Y
92+
make docs-refresh-version DOCS_VERSION=X.Y.0`}
93+
>
94+
Run this from the release branch after backporting docs that apply to the patch release. This updates the existing release-line docs snapshot without adding a patch version to `website/versions.json`.
95+
</CommandBlock>
96+
8797
<CommandBlock
8898
language="bash"
8999
label="configure"

0 commit comments

Comments
 (0)