diff --git a/docs/site/package.json b/docs/site/package.json index ea1585c3de3..e0d230a385e 100644 --- a/docs/site/package.json +++ b/docs/site/package.json @@ -6,6 +6,7 @@ "docusaurus": "docusaurus", "start": "docusaurus start", "build": "docusaurus build", + "postbuild": "node scripts/duplicate-redirect-stubs.mjs", "stage:agents-docs": "node scripts/stage-agents-docs.mjs", "build:sidebars": "node scripts/gen-documentation-sidebar.mjs && node scripts/gen-developer-sidebar.mjs", "build:openapi:spec": "node scripts/build-openapi.mjs", diff --git a/docs/site/scripts/duplicate-redirect-stubs.mjs b/docs/site/scripts/duplicate-redirect-stubs.mjs new file mode 100644 index 00000000000..15c4fdb5251 --- /dev/null +++ b/docs/site/scripts/duplicate-redirect-stubs.mjs @@ -0,0 +1,67 @@ +#!/usr/bin/env node +// Copy every plugin-client-redirects stub from `build//index.html` to +// `build/.html` so the docs CloudFront function's `.html` fallback +// branch finds it (see mattermost-iac-docs-prod → 05-cdn → +// clean-url-rewrite.js). + +import {readFileSync, writeFileSync, existsSync, statSync} from 'node:fs'; +import {join, resolve, dirname, sep} from 'node:path'; +import {fileURLToPath} from 'node:url'; + +const HERE = dirname(fileURLToPath(import.meta.url)); +const SITE_ROOT = resolve(HERE, '..'); +const BUILD_DIR = join(SITE_ROOT, 'build'); +const REDIRECTS_JSON = join(SITE_ROOT, 'sidebars', 'active-redirects.json'); + +if (!existsSync(BUILD_DIR)) { + console.error(`[duplicate-redirect-stubs] ${BUILD_DIR} not found; run \`docusaurus build\` first.`); + process.exit(1); +} + +const {redirects} = JSON.parse(readFileSync(REDIRECTS_JSON, 'utf8')); +const BUILD_DIR_PREFIX = BUILD_DIR + sep; + +let duplicated = 0; +let skippedExisting = 0; +let skippedMissingStub = 0; +let skippedHtmlSuffix = 0; +let skippedOutsideBuild = 0; + +for (const {from} of redirects) { + // CFF Phase A already 301s `.html` to the extensionless form. + if (from.endsWith('.html')) { + skippedHtmlSuffix++; + continue; + } + + const rel = from.replace(/^\/+/, ''); + const stubPath = resolve(BUILD_DIR, rel, 'index.html'); + const flatPath = resolve(BUILD_DIR, `${rel}.html`); + if (!stubPath.startsWith(BUILD_DIR_PREFIX) || !flatPath.startsWith(BUILD_DIR_PREFIX)) { + skippedOutsideBuild++; + continue; + } + + if (!existsSync(stubPath) || !statSync(stubPath).isFile()) { + skippedMissingStub++; + continue; + } + + // Never shadow a real content page at the flat key. + if (existsSync(flatPath)) { + skippedExisting++; + continue; + } + + writeFileSync(flatPath, readFileSync(stubPath)); + duplicated++; +} + +console.log( + `[duplicate-redirect-stubs] duplicated=${duplicated} ` + + `skipped-existing=${skippedExisting} ` + + `skipped-missing-stub=${skippedMissingStub} ` + + `skipped-html-suffix=${skippedHtmlSuffix} ` + + `skipped-outside-build=${skippedOutsideBuild} ` + + `total=${redirects.length}`, +);