From f4dce8d8455e53809e7b5ff1d71d9f6c8c965217 Mon Sep 17 00:00:00 2001 From: Minggang Wang Date: Thu, 25 Jun 2026 13:54:07 +0800 Subject: [PATCH 1/2] [Docs] Rebuild JSDoc site as a pure function of manifest + git tags --- .github/workflows/deploy-docs.yml | 7 +- package.json | 3 +- tools/jsdoc/README.md | 99 +++++---- tools/jsdoc/published-versions.json | 35 +++ tools/jsdoc/regenerate-published-docs.js | 272 ++++++----------------- 5 files changed, 160 insertions(+), 256 deletions(-) create mode 100644 tools/jsdoc/published-versions.json diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index f88c01db..f0d07a29 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -31,9 +31,6 @@ jobs: fetch-depth: 0 fetch-tags: true - - name: Fetch gh-pages branch - run: git fetch origin gh-pages:refs/remotes/origin/gh-pages - - name: Setup Node.js uses: actions/setup-node@v6 with: @@ -43,12 +40,12 @@ jobs: run: npm install --ignore-scripts --no-audit --no-fund - name: Build docs - run: npm run docs:gh-pages + run: npm run docs:publish - name: Upload Pages artifact uses: actions/upload-pages-artifact@v5 with: - path: build/gh-pages-docs + path: build/published-docs deploy: needs: build diff --git a/package.json b/package.json index 9353756d..a3867630 100644 --- a/package.json +++ b/package.json @@ -62,8 +62,7 @@ "install": "node scripts/install.cjs", "postinstall": "npm run generate-messages", "docs": "make -C tools/jsdoc", - "docs:gh-pages": "node tools/jsdoc/regenerate-published-docs.js --branch origin/gh-pages --preserve-published", - "docs:gh-pages:full": "node tools/jsdoc/regenerate-published-docs.js --branch origin/gh-pages --full-rebuild", + "docs:publish": "node tools/jsdoc/regenerate-published-docs.js", "test": "node --expose-gc ./scripts/run_test.cjs && tsd && npm install --no-save electron && node test/electron/run_test.cjs", "test:coverage": "c8 node --expose-gc ./scripts/run_test.cjs && tsd && npm install --no-save electron && node test/electron/run_test.cjs", "test-idl": "node --expose-gc ./scripts/run_test.cjs --idl", diff --git a/tools/jsdoc/README.md b/tools/jsdoc/README.md index 4f2f5f29..33aef1f1 100644 --- a/tools/jsdoc/README.md +++ b/tools/jsdoc/README.md @@ -1,8 +1,16 @@ # JSDoc Workflow This directory contains the custom JSDoc template, the landing-page generator, -and the staging script used to prepare the docs content that is published to the -`gh-pages` branch. +and the staging script used to prepare the docs content that is published to +GitHub Pages. + +The published version set is curated in +[`published-versions.json`](./published-versions.json). That manifest is the +source of truth for which versions appear on the live docs site. Each staging +run rebuilds the whole site from scratch: every listed version is rebuilt from +its Git tag and the in-development version from the current workspace. The +staged tree is therefore a pure function of the manifest plus the tags — there +is no `gh-pages` branch state to maintain or drift out of sync. ## Commands @@ -18,52 +26,46 @@ Output: Use this to verify the docs for the version currently declared in `package.json`. -### `npm run docs:gh-pages` +### `npm run docs:publish` -Stage the publishable docs tree under `build/gh-pages-docs/`. +Stage the full publishable docs tree under `build/published-docs/`. Behavior: -- reads the currently published version set from `origin/gh-pages` -- preserves that published history -- regenerates docs for the current workspace version +- reads the published version set from `published-versions.json` +- rebuilds every listed version from its Git tag +- rebuilds the current workspace version from the working tree (always included) - rebuilds the staged landing page index -This is the normal command to use for a new release. - -If you delete `build/` and rerun `npm run docs:gh-pages`, the staged tree will -still contain all currently published versions. That command recreates -`build/gh-pages-docs/` by copying the published docs snapshot from -`origin/gh-pages`, then regenerating only the current workspace version. +This is the normal command to use for a new release, and it is fully +deterministic: deleting `build/` and rerunning reproduces the identical tree. -### `npm run docs:gh-pages:full` +This does **not** rebuild docs for every historical `rclnodejs` tag — only the +curated subset listed in the manifest. To change which versions are published, +edit `published-versions.json`. -Fully rebuild the currently published docs history under -`build/gh-pages-docs/`. +The script reads the manifest next to it by default. Override the inputs only +for testing: -Behavior: - -- reads the published version set from `origin/gh-pages` -- rebuilds only those published versions from tags -- regenerates docs for the current workspace version -- rebuilds the staged landing page index - -This does **not** rebuild docs for every historical `rclnodejs` tag. It only -rebuilds the subset that is actually published online. +- `--manifest ` — use a different version manifest +- `--out ` — stage into a different directory +- `--keep-worktrees` — leave the temporary Git worktrees in place for inspection ## New Release Example For a new release such as `1.9.0`: 1. Update `package.json` to `1.9.0`. -2. Run `npm run docs`. -3. Verify the local output in `docs/1.9.0/` and `docs/index.html`. -4. Run `npm run docs:gh-pages`. -5. Verify the staged output in: - - `build/gh-pages-docs/docs/1.9.0/` - - `build/gh-pages-docs/docs/index.html` - - `build/gh-pages-docs/.nojekyll` -6. Publish the contents of `build/gh-pages-docs/` to the `gh-pages` branch. +2. Add `1.9.0` to the `versions` array in `published-versions.json`. +3. Run `npm run docs`. +4. Verify the local output in `docs/1.9.0/` and `docs/index.html`. +5. Run `npm run docs:publish`. +6. Verify the staged output in: + - `build/published-docs/docs/1.9.0/` + - `build/published-docs/docs/index.html` + - `build/published-docs/.nojekyll` +7. Publish the contents of `build/published-docs/` to GitHub Pages (the + `deploy-docs.yml` workflow does this automatically on a `docs-*` tag). ## GitHub Actions Deployment @@ -82,11 +84,18 @@ so manual dispatches and `docs-*` tag pushes there will not run the docs build. ### What it does -1. Full checkout with all tags and the `origin/gh-pages` branch. -2. Runs `npm run docs:gh-pages` to stage the docs tree. +1. Full checkout with all tags. +2. Runs `npm run docs:publish` to stage the docs tree. This reads + `published-versions.json` and rebuilds every listed version from its tag + plus the current workspace version. 3. Uploads the staged output as a Pages artifact. 4. Deploys to GitHub Pages (skipped when `dry_run` is `true`). +The workflow only needs `contents: read` and never writes to any branch. +Because the manifest plus the Git tags fully describe the published set, the +live site is always reconstructed from a fresh build — nothing can silently +drop off. + ### Testing - Run the workflow manually with `dry_run` enabled first to verify the build @@ -99,29 +108,29 @@ so manual dispatches and `docs-*` tag pushes there will not run the docs build. ## Manual Landing Page Rebuild If the staged docs tree already exists and you only want to rebuild -`build/gh-pages-docs/docs/index.html`, run `tools/jsdoc/build-index.js` against +`build/published-docs/docs/index.html`, run `tools/jsdoc/build-index.js` against that docs root and point it at the package metadata for the latest published version. Example for published version `1.8.0`: ```bash -mkdir -p build/gh-pages-docs/.tmp -git show 1.8.0:package.json > build/gh-pages-docs/.tmp/package-1.8.0.json +mkdir -p build/published-docs/.tmp +git show 1.8.0:package.json > build/published-docs/.tmp/package-1.8.0.json -export RCLNODEJS_DOCS_ROOT="$PWD/build/gh-pages-docs/docs" -export RCLNODEJS_DOCS_INDEX_PATH="$PWD/build/gh-pages-docs/docs/index.html" +export RCLNODEJS_DOCS_ROOT="$PWD/build/published-docs/docs" +export RCLNODEJS_DOCS_INDEX_PATH="$PWD/build/published-docs/docs/index.html" export RCLNODEJS_LOCAL_INDEX_PATH='' -export RCLNODEJS_PACKAGE_JSON_PATH="$PWD/build/gh-pages-docs/.tmp/package-1.8.0.json" +export RCLNODEJS_PACKAGE_JSON_PATH="$PWD/build/published-docs/.tmp/package-1.8.0.json" node tools/jsdoc/build-index.js -rm -rf build/gh-pages-docs/.tmp +rm -rf build/published-docs/.tmp ``` ## Notes -- The staged publish output keeps shared assets in `build/gh-pages-docs/docs/_static/`. +- The staged publish output keeps shared assets in `build/published-docs/docs/_static/`. - `.nojekyll` must remain in the staged output because the published docs tree uses an underscore-prefixed directory. -- The live docs index is the source of truth for which versions should remain - published. +- `published-versions.json` is the source of truth for which versions are + published; the Git tags are the content source. diff --git a/tools/jsdoc/published-versions.json b/tools/jsdoc/published-versions.json new file mode 100644 index 00000000..38acd702 --- /dev/null +++ b/tools/jsdoc/published-versions.json @@ -0,0 +1,35 @@ +{ + "$comment": "Curated list of rclnodejs versions whose JSDoc is published to GitHub Pages. This file is the source of truth for the published set: add a version here when cutting its release. Each deploy rebuilds every listed version from its Git tag, plus the current package.json version from the workspace, so the live site is a pure function of this manifest and the tags.", + "versions": [ + "0.12.0", + "0.13.0", + "0.14.0", + "0.14.1", + "0.15.0", + "0.15.2", + "0.16.0", + "0.17.0", + "0.18.0", + "0.20.0", + "0.21.3", + "0.22.3", + "0.26.0", + "0.28.0", + "0.29.0", + "0.30.0", + "0.31.0", + "0.32.0", + "0.33.0", + "1.0.0", + "1.1.0", + "1.2.0", + "1.3.0", + "1.5.0", + "1.6.0", + "1.7.0", + "1.8.0", + "1.9.0", + "2.0.0", + "2.1.0" + ] +} diff --git a/tools/jsdoc/regenerate-published-docs.js b/tools/jsdoc/regenerate-published-docs.js index dd63b4e4..acda52ee 100644 --- a/tools/jsdoc/regenerate-published-docs.js +++ b/tools/jsdoc/regenerate-published-docs.js @@ -21,40 +21,29 @@ const path = require('path'); const repoRoot = path.resolve(__dirname, '../..'); const buildIndexScript = path.join(__dirname, 'build-index.js'); -const defaultOutputRoot = path.join(repoRoot, 'build', 'gh-pages-docs'); +const defaultOutputRoot = path.join(repoRoot, 'build', 'published-docs'); const semverDirectoryPattern = /^\d+\.\d+\.\d+$/; const sharedAssetDirectoryName = '_static'; const sharedAssetFolders = ['fonts', 'scripts', 'styles']; +const defaultManifestPath = path.join(__dirname, 'published-versions.json'); + function parseArgs(argv) { const options = { - branch: 'gh-pages', outputRoot: defaultOutputRoot, - versions: null, - preservePublished: false, - fullRebuild: false, + manifestPath: defaultManifestPath, keepWorktrees: false, }; for (let index = 0; index < argv.length; index += 1) { const arg = argv[index]; - if (arg === '--branch') { - options.branch = argv[index + 1]; - index += 1; - } else if (arg === '--out') { + if (arg === '--out') { options.outputRoot = path.resolve(argv[index + 1]); index += 1; - } else if (arg === '--versions') { - options.versions = argv[index + 1] - .split(',') - .map((value) => value.trim()) - .filter(Boolean); + } else if (arg === '--manifest') { + options.manifestPath = path.resolve(argv[index + 1]); index += 1; - } else if (arg === '--preserve-published') { - options.preservePublished = true; - } else if (arg === '--full-rebuild') { - options.fullRebuild = true; } else if (arg === '--keep-worktrees') { options.keepWorktrees = true; } else { @@ -103,18 +92,39 @@ function compareVersionsAsc(left, right) { return 0; } -function getPublishedVersions(branch) { - const output = runCommand('git', [ - 'ls-tree', - '--name-only', - `${branch}:docs`, - ]); +function readManifestVersions(manifestPath) { + let parsed; - return output - .split(/\r?\n/) - .map((entry) => entry.trim()) - .filter((entry) => semverDirectoryPattern.test(entry)) - .sort(compareVersionsAsc); + try { + parsed = JSON.parse(fs.readFileSync(manifestPath, 'utf8')); + } catch (error) { + throw new Error( + `Unable to read versions manifest ${manifestPath}: ${error.message}` + ); + } + + const list = Array.isArray(parsed) ? parsed : parsed.versions; + + if (!Array.isArray(list)) { + throw new Error( + `Manifest ${manifestPath} must be an array or contain a "versions" array.` + ); + } + + const versions = list.map((value) => String(value).trim()).filter(Boolean); + const invalid = versions.filter( + (value) => !semverDirectoryPattern.test(value) + ); + + if (invalid.length) { + throw new Error( + `Manifest ${manifestPath} contains non-release versions: ${invalid.join( + ', ' + )}` + ); + } + + return Array.from(new Set(versions)).sort(compareVersionsAsc); } function getTaggedReleaseVersions() { @@ -172,21 +182,6 @@ function getGeneratedVersions(outputRoot) { .sort(compareVersionsAsc); } -function writeBranchFile(branch, sourcePath, destinationPath) { - try { - const contents = runCommand('git', ['show', `${branch}:${sourcePath}`]); - fs.mkdirSync(path.dirname(destinationPath), { recursive: true }); - fs.writeFileSync(destinationPath, contents, 'utf8'); - } catch (error) { - if (sourcePath === '.nojekyll') { - fs.writeFileSync(destinationPath, '', 'utf8'); - return; - } - - throw error; - } -} - function appendJsFiles(directoryPath, inputs) { if (!fs.existsSync(directoryPath)) { return; @@ -267,52 +262,6 @@ function writeIndexPackage(packageJsonPath, outputRoot) { return destinationPath; } -function copyIfExists(sourcePath, destinationPath) { - if (!fs.existsSync(sourcePath)) { - return; - } - - fs.mkdirSync(path.dirname(destinationPath), { recursive: true }); - fs.cpSync(sourcePath, destinationPath, { recursive: true, force: true }); -} - -function copyDirectoryContents(sourceDir, destinationDir) { - if (!fs.existsSync(sourceDir)) { - return; - } - - fs.mkdirSync(destinationDir, { recursive: true }); - - fs.readdirSync(sourceDir, { withFileTypes: true }).forEach((entry) => { - fs.cpSync( - path.join(sourceDir, entry.name), - path.join(destinationDir, entry.name), - { recursive: true, force: true } - ); - }); -} - -function copyPublishedSnapshot(branch, outputRoot, tempRoot, keepWorktrees) { - const worktreePath = addWorktree(branch, tempRoot, `branch-${branch}`); - const rootFiles = ['.nojekyll', 'README.md', '_index-package.json']; - - try { - copyDirectoryContents( - path.join(worktreePath, 'docs'), - path.join(outputRoot, 'docs') - ); - - rootFiles.forEach((fileName) => { - copyIfExists( - path.join(worktreePath, fileName), - path.join(outputRoot, fileName) - ); - }); - } finally { - removeWorktree(worktreePath, keepWorktrees); - } -} - function buildVersionDocs(version, outputDocsRoot, tempRoot, keepWorktrees) { const worktreePath = addWorktree(version, tempRoot, version); @@ -440,139 +389,60 @@ function hoistSharedAssets(outputRoot) { }); } -function ensureSharedAssetsForVersion(outputRoot, version) { - const docsRoot = path.join(outputRoot, 'docs'); - const versionRoot = path.join(docsRoot, version); - const sharedRoot = path.join(docsRoot, sharedAssetDirectoryName); - - if (!fs.existsSync(versionRoot)) { - return; - } - - fs.mkdirSync(sharedRoot, { recursive: true }); - - sharedAssetFolders.forEach((folderName) => { - const sourcePath = path.join(versionRoot, folderName); - - if (!fs.existsSync(sourcePath)) { - return; - } - - fs.cpSync(sourcePath, path.join(sharedRoot, folderName), { - recursive: true, - force: true, - }); - }); - - rewriteVersionHtmlToSharedAssets(versionRoot); - - sharedAssetFolders.forEach((folderName) => { - fs.rmSync(path.join(versionRoot, folderName), { - recursive: true, - force: true, - }); - }); -} - function main() { const options = parseArgs(process.argv.slice(2)); - if (options.preservePublished && options.fullRebuild) { - throw new Error( - 'Use either --preserve-published or --full-rebuild, not both.' - ); - } - - const publishedVersions = options.versions - ? options.versions - : getPublishedVersions(options.branch); + const publishedVersions = readManifestVersions(options.manifestPath); const currentVersion = getCurrentWorkspaceVersion(); const versions = Array.from( new Set(publishedVersions.concat(currentVersion)) ).sort(compareVersionsAsc); + const latestVersion = versions[versions.length - 1]; - if (!versions.length) { - throw new Error(`No published versions found in ${options.branch}:docs`); - } - + // The staged site is a pure function of the manifest and the Git tags: every + // published version is rebuilt from its tag and the in-development version + // from the current workspace. There is no published-branch state to drift, so + // a fresh build always reproduces the full site. assertTagsExist( publishedVersions.filter((version) => version !== currentVersion) ); ensureCleanOutput(options.outputRoot); + fs.writeFileSync(path.join(options.outputRoot, '.nojekyll'), '', 'utf8'); + const docsRoot = path.join(options.outputRoot, 'docs'); const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'rclnodejs-jsdoc-')); - const latestVersion = versions[versions.length - 1]; let latestPackageJsonPath = null; try { - if (options.preservePublished) { - copyPublishedSnapshot( - options.branch, - options.outputRoot, - tempRoot, - options.keepWorktrees - ); - console.log( - `Preserved ${publishedVersions.length} published versions from ${options.branch}.` - ); - } else { - writeBranchFile( - options.branch, - '.nojekyll', - path.join(options.outputRoot, '.nojekyll') - ); - writeBranchFile( - options.branch, - 'README.md', - path.join(options.outputRoot, 'README.md') - ); - } - - console.log(`Regenerating ${versions.length} documentation versions.`); - - if (!options.preservePublished || options.fullRebuild) { - publishedVersions.forEach((version) => { - if (version === currentVersion) { - return; - } - - console.log(`- ${version}`); - - const buildResult = buildVersionDocs( - version, - path.join(options.outputRoot, 'docs'), - tempRoot, - options.keepWorktrees - ); + console.log(`Building ${versions.length} documentation versions.`); - if (version === latestVersion) { - latestPackageJsonPath = writeIndexPackage( - buildResult.packageJsonPath, - options.outputRoot - ); - } + publishedVersions.forEach((version) => { + if (version === currentVersion) { + return; + } - removeWorktree(buildResult.worktreePath, options.keepWorktrees); - }); - } + console.log(`- ${version} (from tag)`); - if (options.preservePublished && !options.fullRebuild) { - const existingIndexPackagePath = path.join( - options.outputRoot, - '_index-package.json' + const buildResult = buildVersionDocs( + version, + docsRoot, + tempRoot, + options.keepWorktrees ); - if (fs.existsSync(existingIndexPackagePath)) { - latestPackageJsonPath = existingIndexPackagePath; + if (version === latestVersion) { + latestPackageJsonPath = writeIndexPackage( + buildResult.packageJsonPath, + options.outputRoot + ); } - } + + removeWorktree(buildResult.worktreePath, options.keepWorktrees); + }); console.log(`- ${currentVersion} (current workspace)`); - const buildResult = buildDocsFromSourceRoot( - repoRoot, - path.join(options.outputRoot, 'docs') - ); + const buildResult = buildDocsFromSourceRoot(repoRoot, docsRoot); if (currentVersion === latestVersion) { latestPackageJsonPath = writeIndexPackage( @@ -585,13 +455,7 @@ function main() { options.outputRoot, latestPackageJsonPath || path.join(repoRoot, 'package.json') ); - - if (options.preservePublished && !options.fullRebuild) { - ensureSharedAssetsForVersion(options.outputRoot, currentVersion); - } else { - hoistSharedAssets(options.outputRoot); - } - + hoistSharedAssets(options.outputRoot); removeTemporaryPublishArtifacts(options.outputRoot); console.log(`Published docs tree ready at ${options.outputRoot}`); From 8c901cb2e747b6c80342363fbefe4130b8c781f0 Mon Sep 17 00:00:00 2001 From: Minggang Wang Date: Thu, 25 Jun 2026 14:13:45 +0800 Subject: [PATCH 2/2] Address comments --- tools/jsdoc/README.md | 16 ++- tools/jsdoc/regenerate-published-docs.js | 129 ++++++----------------- 2 files changed, 39 insertions(+), 106 deletions(-) diff --git a/tools/jsdoc/README.md b/tools/jsdoc/README.md index 33aef1f1..7b5b26b9 100644 --- a/tools/jsdoc/README.md +++ b/tools/jsdoc/README.md @@ -8,9 +8,8 @@ The published version set is curated in [`published-versions.json`](./published-versions.json). That manifest is the source of truth for which versions appear on the live docs site. Each staging run rebuilds the whole site from scratch: every listed version is rebuilt from -its Git tag and the in-development version from the current workspace. The -staged tree is therefore a pure function of the manifest plus the tags — there -is no `gh-pages` branch state to maintain or drift out of sync. +its Git tag. The staged tree is therefore a pure function of the manifest plus +the tags — there is no `gh-pages` branch state to maintain or drift out of sync. ## Commands @@ -34,7 +33,6 @@ Behavior: - reads the published version set from `published-versions.json` - rebuilds every listed version from its Git tag -- rebuilds the current workspace version from the working tree (always included) - rebuilds the staged landing page index This is the normal command to use for a new release, and it is fully @@ -49,7 +47,6 @@ for testing: - `--manifest ` — use a different version manifest - `--out ` — stage into a different directory -- `--keep-worktrees` — leave the temporary Git worktrees in place for inspection ## New Release Example @@ -57,8 +54,10 @@ For a new release such as `1.9.0`: 1. Update `package.json` to `1.9.0`. 2. Add `1.9.0` to the `versions` array in `published-versions.json`. -3. Run `npm run docs`. -4. Verify the local output in `docs/1.9.0/` and `docs/index.html`. +3. Run `npm run docs` to preview the new version locally (`docs/1.9.0/` and + `docs/index.html`). +4. Cut and push the `1.9.0` release tag so `npm run docs:publish` can rebuild + it from Git. 5. Run `npm run docs:publish`. 6. Verify the staged output in: - `build/published-docs/docs/1.9.0/` @@ -86,8 +85,7 @@ so manual dispatches and `docs-*` tag pushes there will not run the docs build. 1. Full checkout with all tags. 2. Runs `npm run docs:publish` to stage the docs tree. This reads - `published-versions.json` and rebuilds every listed version from its tag - plus the current workspace version. + `published-versions.json` and rebuilds every listed version from its tag. 3. Uploads the staged output as a Pages artifact. 4. Deploys to GitHub Pages (skipped when `dry_run` is `true`). diff --git a/tools/jsdoc/regenerate-published-docs.js b/tools/jsdoc/regenerate-published-docs.js index acda52ee..0ff7481a 100644 --- a/tools/jsdoc/regenerate-published-docs.js +++ b/tools/jsdoc/regenerate-published-docs.js @@ -32,20 +32,25 @@ function parseArgs(argv) { const options = { outputRoot: defaultOutputRoot, manifestPath: defaultManifestPath, - keepWorktrees: false, }; for (let index = 0; index < argv.length; index += 1) { const arg = argv[index]; - if (arg === '--out') { - options.outputRoot = path.resolve(argv[index + 1]); - index += 1; - } else if (arg === '--manifest') { - options.manifestPath = path.resolve(argv[index + 1]); + if (arg === '--out' || arg === '--manifest') { + const value = argv[index + 1]; + + if (value === undefined) { + throw new Error(`Missing value for ${arg}`); + } + + if (arg === '--out') { + options.outputRoot = path.resolve(value); + } else { + options.manifestPath = path.resolve(value); + } + index += 1; - } else if (arg === '--keep-worktrees') { - options.keepWorktrees = true; } else { throw new Error(`Unknown argument: ${arg}`); } @@ -135,20 +140,6 @@ function getTaggedReleaseVersions() { .sort(compareVersionsAsc); } -function getCurrentWorkspaceVersion() { - const packageInfo = JSON.parse( - fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8') - ); - - if (!semverDirectoryPattern.test(packageInfo.version)) { - throw new Error( - `Current package.json version is not a release semver: ${packageInfo.version}` - ); - } - - return packageInfo.version; -} - function assertTagsExist(versions) { const tags = new Set(getTaggedReleaseVersions()); @@ -232,11 +223,7 @@ function addWorktree(ref, tempRoot, directoryName = ref) { return worktreePath; } -function removeWorktree(worktreePath, keepWorktrees) { - if (keepWorktrees) { - return; - } - +function removeWorktree(worktreePath) { try { runCommand('git', ['worktree', 'remove', '--force', worktreePath]); } catch { @@ -262,7 +249,7 @@ function writeIndexPackage(packageJsonPath, outputRoot) { return destinationPath; } -function buildVersionDocs(version, outputDocsRoot, tempRoot, keepWorktrees) { +function buildVersionDocs(version, outputDocsRoot, tempRoot) { const worktreePath = addWorktree(version, tempRoot, version); try { @@ -288,34 +275,11 @@ function buildVersionDocs(version, outputDocsRoot, tempRoot, keepWorktrees) { packageJsonPath: path.join(worktreePath, 'package.json'), }; } catch (error) { - removeWorktree(worktreePath, keepWorktrees); + removeWorktree(worktreePath); throw error; } } -function buildDocsFromSourceRoot(sourceRoot, outputDocsRoot) { - const inputs = getJsdocInputs(sourceRoot); - - runCommand( - 'npx', - [ - 'jsdoc', - '--package', - path.join(sourceRoot, 'package.json'), - ...inputs, - '-t', - __dirname, - '-d', - outputDocsRoot, - ], - { cwd: repoRoot } - ); - - return { - packageJsonPath: path.join(sourceRoot, 'package.json'), - }; -} - function buildDocsIndex(outputRoot, packageJsonPath) { runCommand('node', [buildIndexScript], { cwd: repoRoot, @@ -331,9 +295,7 @@ function buildDocsIndex(outputRoot, packageJsonPath) { } function removeTemporaryPublishArtifacts(outputRoot) { - ['README.md', '_index-package.json'].forEach((fileName) => { - fs.rmSync(path.join(outputRoot, fileName), { force: true }); - }); + fs.rmSync(path.join(outputRoot, '_index-package.json'), { force: true }); } function rewriteVersionHtmlToSharedAssets(versionRoot) { @@ -392,20 +354,18 @@ function hoistSharedAssets(outputRoot) { function main() { const options = parseArgs(process.argv.slice(2)); - const publishedVersions = readManifestVersions(options.manifestPath); - const currentVersion = getCurrentWorkspaceVersion(); - const versions = Array.from( - new Set(publishedVersions.concat(currentVersion)) - ).sort(compareVersionsAsc); + const versions = readManifestVersions(options.manifestPath); + + if (!versions.length) { + throw new Error(`Manifest ${options.manifestPath} lists no versions.`); + } + const latestVersion = versions[versions.length - 1]; // The staged site is a pure function of the manifest and the Git tags: every - // published version is rebuilt from its tag and the in-development version - // from the current workspace. There is no published-branch state to drift, so - // a fresh build always reproduces the full site. - assertTagsExist( - publishedVersions.filter((version) => version !== currentVersion) - ); + // listed version is rebuilt from its release tag, so a fresh build always + // reproduces the full site with no branch state to drift. + assertTagsExist(versions); ensureCleanOutput(options.outputRoot); fs.writeFileSync(path.join(options.outputRoot, '.nojekyll'), '', 'utf8'); @@ -416,19 +376,10 @@ function main() { try { console.log(`Building ${versions.length} documentation versions.`); - publishedVersions.forEach((version) => { - if (version === currentVersion) { - return; - } + versions.forEach((version) => { + console.log(`- ${version}`); - console.log(`- ${version} (from tag)`); - - const buildResult = buildVersionDocs( - version, - docsRoot, - tempRoot, - options.keepWorktrees - ); + const buildResult = buildVersionDocs(version, docsRoot, tempRoot); if (version === latestVersion) { latestPackageJsonPath = writeIndexPackage( @@ -437,32 +388,16 @@ function main() { ); } - removeWorktree(buildResult.worktreePath, options.keepWorktrees); + removeWorktree(buildResult.worktreePath); }); - console.log(`- ${currentVersion} (current workspace)`); - - const buildResult = buildDocsFromSourceRoot(repoRoot, docsRoot); - - if (currentVersion === latestVersion) { - latestPackageJsonPath = writeIndexPackage( - buildResult.packageJsonPath, - options.outputRoot - ); - } - - buildDocsIndex( - options.outputRoot, - latestPackageJsonPath || path.join(repoRoot, 'package.json') - ); + buildDocsIndex(options.outputRoot, latestPackageJsonPath); hoistSharedAssets(options.outputRoot); removeTemporaryPublishArtifacts(options.outputRoot); console.log(`Published docs tree ready at ${options.outputRoot}`); } finally { - if (!options.keepWorktrees) { - fs.rmSync(tempRoot, { recursive: true, force: true }); - } + fs.rmSync(tempRoot, { recursive: true, force: true }); } }