From 22ce3239fc1d5ca9cc4b0f039156e6cd14fea2e9 Mon Sep 17 00:00:00 2001 From: Claude Code Date: Mon, 11 May 2026 10:02:09 -0700 Subject: [PATCH] chore(docs): freeze @site/-aliased data imports at version cut too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend freezeDataImports to also handle MDX imports that use the Docusaurus `@site/` alias (e.g. feature-flags.mdx imports `@site/static/feature-flags.json` to render the feature-flag tables). Previously these were skipped because the regex only matched escaping relative paths, so the snapshot kept reading the live JSON — meaning a 6.1.0 snapshot would silently grow new flags every time someone added one in master. The freeze now matches both prefixes: - `from '../../foo/bar.json'` — relative escape (existing) - `from '@site/static/foo.json'` — site-root alias (new) `@site/` always resolves against the docs root, so we don't need the depth check; instead we just skip any import that resolves inside the section root (which would be copied with the section anyway). Verified end-to-end with a throwaway admin_docs cut: the snapshot's feature-flags page renders all 62 current feature flags from a frozen JSON, alongside the country-map-tools freeze. --- docs/scripts/manage-versions.mjs | 76 ++++++++++++++++++++------------ 1 file changed, 47 insertions(+), 29 deletions(-) diff --git a/docs/scripts/manage-versions.mjs b/docs/scripts/manage-versions.mjs index 39b888bac3c..1ba79fdb4cf 100644 --- a/docs/scripts/manage-versions.mjs +++ b/docs/scripts/manage-versions.mjs @@ -44,9 +44,11 @@ function saveConfig(config) { } function freezeDataImports(section, version) { - // MDX files can `import` JSON/YAML data from outside the section (e.g. - // country-map-tools.mdx imports `../../data/countries.json`). Without - // intervention, the snapshot keeps reading the live file, so the + // MDX files can `import` JSON/YAML data from outside the section, either + // via escaping relative paths (e.g. country-map-tools.mdx imports + // `../../data/countries.json`) or via the `@site/` alias (e.g. + // feature-flags.mdx imports `@site/static/feature-flags.json`). Without + // intervention the snapshot keeps reading the live file, so the // historical version's content silently changes whenever the data file // is updated. Copy each escaping data import into a snapshot-local // `_versioned_data/` dir and rewrite the import to point there. @@ -66,9 +68,44 @@ function freezeDataImports(section, version) { console.log(` Freezing data imports in ${versionedDocsDir}...`); - // Matches `from '../../foo/bar.json'` and similar — only escaping paths - // (one or more `../`) targeting JSON/YAML files. - const dataImportRe = /(from\s+['"])((?:\.\.\/)+)([^'"\s]+\.(?:json|ya?ml))(['"])/g; + // Matches data file imports in two flavors: + // `from '../../foo/bar.json'` (relative, must escape one or more dirs) + // `from '@site/static/foo.json'` (Docusaurus site-root alias) + const dataImportRe = /(from\s+['"])((?:\.\.\/)+|@site\/)([^'"\s]+\.(?:json|ya?ml))(['"])/g; + + function freezeOne(fullPath, depth, prefix, pathSpec, importPath, suffix) { + let resolvedSource; + if (pathSpec === '@site/') { + // `@site/...` always resolves relative to the docs root. + resolvedSource = path.join(docsRoot, importPath); + } else { + // Relative path — must escape the file's depth within the section + // to point at content outside the section. Imports that stay inside + // are copied wholesale by Docusaurus, so we leave them alone. + const upCount = pathSpec.match(/\.\.\//g).length; + if (upCount <= depth) return null; + const relativeFromVersioned = path.relative(versionedDocsPath, fullPath); + const originalDir = path.dirname(path.join(sectionRoot, relativeFromVersioned)); + resolvedSource = path.resolve(originalDir, pathSpec + importPath); + } + // Skip imports that land inside the section root — those get copied + // with the section snapshot already. + const relFromSection = path.relative(sectionRoot, resolvedSource); + if (!relFromSection.startsWith('..')) return null; + const relFromDocsRoot = path.relative(docsRoot, resolvedSource); + if (relFromDocsRoot.startsWith('..') || !fs.existsSync(resolvedSource)) { + return null; + } + const destPath = path.join(frozenDataDir, relFromDocsRoot); + fs.mkdirSync(path.dirname(destPath), { recursive: true }); + fs.copyFileSync(resolvedSource, destPath); + const rewritten = path + .relative(path.dirname(fullPath), destPath) + .split(path.sep) + .join('/'); + const finalImport = rewritten.startsWith('.') ? rewritten : `./${rewritten}`; + return `${prefix}${finalImport}${suffix}`; + } function walk(dir, depth) { for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { @@ -86,30 +123,11 @@ function freezeDataImports(section, version) { return line; } if (inFence) return line; - return line.replace(dataImportRe, (match, prefix, dots, importPath, suffix) => { - const upCount = dots.match(/\.\.\//g).length; - // Imports that stay inside the section are copied wholesale by - // Docusaurus, so they don't need freezing. - if (upCount <= depth) return match; - // Resolve the import against the file's *original* location to - // find the source file in the live tree. - const relativeFromVersioned = path.relative(versionedDocsPath, fullPath); - const originalDir = path.dirname(path.join(sectionRoot, relativeFromVersioned)); - const resolvedSource = path.resolve(originalDir, dots + importPath); - const relFromDocsRoot = path.relative(docsRoot, resolvedSource); - if (relFromDocsRoot.startsWith('..') || !fs.existsSync(resolvedSource)) { - return match; - } - const destPath = path.join(frozenDataDir, relFromDocsRoot); - fs.mkdirSync(path.dirname(destPath), { recursive: true }); - fs.copyFileSync(resolvedSource, destPath); - const rewritten = path - .relative(path.dirname(fullPath), destPath) - .split(path.sep) - .join('/'); - const finalImport = rewritten.startsWith('.') ? rewritten : `./${rewritten}`; + return line.replace(dataImportRe, (match, prefix, pathSpec, importPath, suffix) => { + const rewritten = freezeOne(fullPath, depth, prefix, pathSpec, importPath, suffix); + if (rewritten === null) return match; mutated = true; - return `${prefix}${finalImport}${suffix}`; + return rewritten; }); }).join('\n'); if (mutated) {