diff --git a/docs/website/scripts/sync-docs.js b/docs/website/scripts/sync-docs.js index 4e2c06da58ac..99a1b121bfa1 100644 --- a/docs/website/scripts/sync-docs.js +++ b/docs/website/scripts/sync-docs.js @@ -1,4 +1,3 @@ -// scripts/sync-docs.js const fs = require('fs-extra') const path = require('path') @@ -6,47 +5,72 @@ const versions = ['5.0.0', '4.0.0'] const websiteDir = path.resolve(__dirname, '..') const rootDir = path.resolve(websiteDir, '..') +// Define all safe directory paths as a frozen constant +const SAFE_DIRS = Object.freeze([ + 'i18n/zh-Hans/docusaurus-plugin-content-blog', + 'i18n/zh-Hans/docusaurus-plugin-content-docs', + 'versioned_docs', + 'versioned_sidebars' +]) + +// Define all safe paths as a frozen constant object +const SAFE_PATHS = Object.freeze({ + versionsJson: 'versions.json', + primaryDocs: 'docs', + translatedDocs: 'i18n/zh-Hans', + versionedDocs: 'versioned_docs', + versionedSidebars: 'versioned_sidebars', + primarySidebar: 'sidebar.json', + translatedSidebar: 'i18n/zh-Hans/sidebar.json', + codeJson: '5.0/code.json', + translatedCodeJson: 'i18n/zh-Hans/code.json', + translatedBlog: 'blog/zh-Hans' +}) + +// Helper function to validate and normalize paths +function safeJoin (base, ...paths) { + const fullPath = path.resolve(base, ...paths) + if (!fullPath.startsWith(base)) { + throw new Error(`Path traversal attempt blocked: ${fullPath}`) + } + return fullPath +} + async function generateVersionsJson () { - const versionsJsonPath = path.join(websiteDir, 'versions.json') + const versionsJsonPath = safeJoin(websiteDir, SAFE_PATHS.versionsJson) await fs.writeJson(versionsJsonPath, versions, { spaces: 2 }) } async function syncDocs () { try { // Remove specified directories - const dirsToRemove = [ - 'i18n/zh-Hans/docusaurus-plugin-content-blog', - 'i18n/zh-Hans/docusaurus-plugin-content-docs', - 'versioned_docs', - 'versioned_sidebars' - ].map(dir => path.join(websiteDir, dir)) - + const dirsToRemove = SAFE_DIRS.map(dir => safeJoin(websiteDir, dir)) await Promise.all(dirsToRemove.map(dir => fs.remove(dir))) for (const version of versions) { const shortVersion = version.slice(0, 3) - // Sync English docs + // Sync primary language docs await fs.copy( - path.join(rootDir, shortVersion, 'docs'), - path.join(websiteDir, 'versioned_docs', `version-${version}`) + safeJoin(rootDir, shortVersion, SAFE_PATHS.primaryDocs), + safeJoin(websiteDir, SAFE_PATHS.versionedDocs, `version-${version}`) ) - // Sync Chinese docs + // Sync translated docs await fs.copy( - path.join(rootDir, shortVersion, 'i18n', 'zh-Hans'), - path.join(websiteDir, 'i18n/zh-Hans/docusaurus-plugin-content-docs', `version-${version}`) + safeJoin(rootDir, shortVersion, SAFE_PATHS.translatedDocs), + safeJoin(websiteDir, 'i18n/zh-Hans/docusaurus-plugin-content-docs', `version-${version}`) ) // Copy sidebar files const sidebarPaths = [ { - src: path.join(rootDir, shortVersion, 'sidebar.json'), - dest: path.join(websiteDir, 'versioned_sidebars', `version-${version}-sidebars.json`) + src: safeJoin(rootDir, shortVersion, SAFE_PATHS.primarySidebar), + dest: safeJoin(websiteDir, SAFE_PATHS.versionedSidebars, `version-${version}-sidebars.json`) }, { - src: path.join(rootDir, shortVersion, 'i18n', 'zh-Hans', 'sidebar.json'), - dest: path.join(websiteDir, 'i18n/zh-Hans/docusaurus-plugin-content-docs', `version-${version}.json`) + src: safeJoin(rootDir, shortVersion, SAFE_PATHS.translatedSidebar), + dest: safeJoin(websiteDir, 'i18n/zh-Hans/docusaurus-plugin-content-docs', `version-${version}.json`) } ] @@ -59,20 +83,20 @@ async function syncDocs () { // Sync code.json await fs.copy( - path.join(rootDir, '5.0/code.json'), - path.join(websiteDir, 'i18n/zh-Hans/code.json') + safeJoin(rootDir, SAFE_PATHS.codeJson), + safeJoin(websiteDir, SAFE_PATHS.translatedCodeJson) ) // Sync blog content await fs.copy( - path.join(rootDir, 'blog/zh-Hans'), - path.join(websiteDir, 'i18n/zh-Hans/docusaurus-plugin-content-blog') + safeJoin(rootDir, SAFE_PATHS.translatedBlog), + safeJoin(websiteDir, 'i18n/zh-Hans/docusaurus-plugin-content-blog') ) await generateVersionsJson() console.log(`All documents synchronized successfully: - Synced docs for versions: ${versions.join(', ')} - - Updated English and Chinese documentation + - Updated primary and translated documentation - Copied code.json and sidebar files - Synced blog content - Generated versions.json`)