From 302daf44f70598c23a94a6e656c4db94e7d1e8cf Mon Sep 17 00:00:00 2001 From: bh0fer Date: Thu, 25 Dec 2025 17:20:45 +0100 Subject: [PATCH 1/9] poc of package docs sync --- package.json | 4 +- tdev-website/docs/packages/.gitignore | 2 + updateSync/packageDocsSync/index.ts | 60 +++++++++++++++++++++++++++ updateSync/packageDocsSync/watch.ts | 37 +++++++++++++++++ yarn.lock | 7 +++- 5 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 tdev-website/docs/packages/.gitignore create mode 100644 updateSync/packageDocsSync/index.ts create mode 100644 updateSync/packageDocsSync/watch.ts diff --git a/package.json b/package.json index e760ff5aa..0ee38c084 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "private": true, "scripts": { "docusaurus": "docusaurus", - "start": "docusaurus start", + "start": "ts-node updateSync/packageDocsSync/watch.ts --src packages --dest tdev-website/docs/packages & docusaurus start", "build": "docusaurus build", "swizzle": "docusaurus swizzle", "deploy": "docusaurus deploy", @@ -65,6 +65,7 @@ "mdast-util-math": "^3.0.0", "micromark-extension-math": "^3.1.0", "micromatch": "^4.0.8", + "minimist": "^1.2.8", "mobx": "^6.15.0", "mobx-react-lite": "^4.1.1", "mobx-utils": "^6.1.1", @@ -97,6 +98,7 @@ "@types/fs-extra": "^11.0.4", "@types/js-yaml": "^4.0.9", "@types/micromatch": "^4.0.9", + "@types/minimist": "^1.2.5", "@types/react-dom": "^19.0.3", "@types/react-katex": "^3.0.4", "@types/svg-parser": "^2.0.6", diff --git a/tdev-website/docs/packages/.gitignore b/tdev-website/docs/packages/.gitignore new file mode 100644 index 000000000..c96a04f00 --- /dev/null +++ b/tdev-website/docs/packages/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/updateSync/packageDocsSync/index.ts b/updateSync/packageDocsSync/index.ts new file mode 100644 index 000000000..b06229fe7 --- /dev/null +++ b/updateSync/packageDocsSync/index.ts @@ -0,0 +1,60 @@ +import { spawn } from 'child_process'; +import path from 'path'; +import fs from 'fs'; +import { debounce } from 'es-toolkit/compat'; + +interface TdevPackageConfig { + type: 'local' | 'git'; + docs?: Partial<{ + path: string; + include: string[]; + exclude: string[]; + }>; +} + +export const META_FILES_TEST = /_category_\.(yml|json)$/; +function getDocsRoot( + packageDir: string, + destDir: string, + filePath: string +): { src: string; dest: string } | null { + // filePath = ".../packages///docs/(...)?..." + const parts = filePath.split(path.sep); + console.log(parts); + const docsIdx = parts.lastIndexOf('docs'); + if (META_FILES_TEST.test(filePath)) { + if (fs.existsSync(filePath)) { + return { src: filePath, dest: filePath.replace(packageDir, destDir) }; + } + } + if (docsIdx >= 2) { + const org = parts[docsIdx - 2]; + const pkg = parts[docsIdx - 1]; + const docsSrc = path.join(packageDir, org, pkg, 'docs'); + const docsDest = path.join(destDir, org, pkg, 'docs'); + if (fs.existsSync(docsSrc) && fs.statSync(docsSrc).isDirectory()) { + return { src: `${docsSrc}/`, dest: `${docsDest}/` }; + } + } + return null; +} + +function syncDocsFolder(src: string, dest: string) { + if (dest.endsWith(path.sep)) { + fs.mkdirSync(dest, { recursive: true }); + } + spawn('rsync', ['-av', '--delete', src, dest], { stdio: 'inherit' }); +} +export const getDebouncedSyncer = (packageDir: string, destDir: string) => { + const syncQueue = new Set(); + const syncDebounced = debounce(() => { + for (const docsPath of syncQueue) { + const info = getDocsRoot(packageDir, destDir, docsPath); + if (info) { + syncDocsFolder(info.src, info.dest); + } + } + syncQueue.clear(); + }, 300); + return { syncQueue, syncDebounced }; +}; diff --git a/updateSync/packageDocsSync/watch.ts b/updateSync/packageDocsSync/watch.ts new file mode 100644 index 000000000..7bd678fc1 --- /dev/null +++ b/updateSync/packageDocsSync/watch.ts @@ -0,0 +1,37 @@ +import chokidar from 'chokidar'; +import minimist from 'minimist'; +import path from 'path'; +import { getDebouncedSyncer, META_FILES_TEST } from '.'; + +const argv = minimist(process.argv.slice(2), { + string: ['src', 'dest'], + alias: { src: 'packages', dest: 'out' }, + default: { + src: 'packages' + } +}); + +const PACKAGES_DIR = path.resolve(process.cwd(), argv.src); +const DEST_ROOT = path.resolve(process.cwd(), argv.dest); + +const watcher = chokidar.watch(PACKAGES_DIR, { ignoreInitial: false, persistent: true }); + +const { syncQueue, syncDebounced } = getDebouncedSyncer(PACKAGES_DIR, DEST_ROOT); + +const NODE_MODULES_TEST = /node_modules/; +const DOCS_PATH_TEST = new RegExp(`${path.sep}docs${path.sep}|${path.sep}docs$`); + +watcher + .on('all', (_event, filePath) => { + if (NODE_MODULES_TEST.test(filePath)) { + return null; + } + + if (DOCS_PATH_TEST.test(filePath) || META_FILES_TEST.test(filePath)) { + syncQueue.add(filePath); + syncDebounced(); + } + }) + .on('ready', () => { + console.log('Initial scan complete. Watching for docs changes...'); + }); diff --git a/yarn.lock b/yarn.lock index 1b74813fd..5e52da581 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5897,6 +5897,11 @@ resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== +"@types/minimist@^1.2.5": + version "1.2.5" + resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.5.tgz#ec10755e871497bcd83efe927e43ec46e8c0747e" + integrity sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag== + "@types/ms@*": version "2.1.0" resolved "https://registry.yarnpkg.com/@types/ms/-/ms-2.1.0.tgz#052aa67a48eccc4309d7f0191b7e41434b90bb78" @@ -12097,7 +12102,7 @@ minimatch@^9.0.4: dependencies: brace-expansion "^2.0.1" -minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.6: +minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.6, minimist@^1.2.8: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== From df89d78702f4d48ba8aff3636ce286b0a59bfc7e Mon Sep 17 00:00:00 2001 From: bh0fer Date: Fri, 26 Dec 2025 09:34:42 +0100 Subject: [PATCH 2/9] introduce build plugin --- docusaurus.config.ts | 2 + updateSync/packageDocsSync/actions.ts | 135 ++++++++++++++++++++++++++ updateSync/packageDocsSync/index.ts | 70 +++---------- updateSync/packageDocsSync/watch.ts | 4 +- 4 files changed, 155 insertions(+), 56 deletions(-) create mode 100644 updateSync/packageDocsSync/actions.ts diff --git a/docusaurus.config.ts b/docusaurus.config.ts index c3b4b6dd1..a149ff89f 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -42,6 +42,7 @@ import { import { remarkPdfPluginConfig } from '@tdev/remark-pdf'; import { GlobExcludeDefault } from '@docusaurus/utils'; import extractPackageDocs from './src/siteConfig/extractPackageDocs'; +import packageDocsSync from './updateSync/packageDocsSync'; const withSiteConfig = async (): Promise => { if (process.env.SITE_CONFIG_PATH) { @@ -368,6 +369,7 @@ const docusaurusConfig = withSiteConfig().then(async (siteConfig) => { ...(siteConfig.themeConfig || {}) } satisfies Preset.ThemeConfig, plugins: [ + packageDocsSync, sassPluginConfig, dynamicRouterPluginConfig, rsDoctorPluginConfig, diff --git a/updateSync/packageDocsSync/actions.ts b/updateSync/packageDocsSync/actions.ts new file mode 100644 index 000000000..a8f829021 --- /dev/null +++ b/updateSync/packageDocsSync/actions.ts @@ -0,0 +1,135 @@ +import { spawn } from 'child_process'; +import path from 'path'; +import fsSym from 'fs'; +import fs from 'fs/promises'; +import yaml from 'js-yaml'; +import { debounce } from 'es-toolkit/compat'; + +interface TdevPackageConfig { + path: string; + docs?: Partial<{ + path: string; + include: string[]; + exclude: string[]; + }>; +} + +const DEFAULT_DOCS_CONFIG: Partial = { + docs: { + path: 'docs' + } +}; + +const DEFAULT_README_CONFIG: Partial = { + docs: { + path: '.', + include: ['_category_.yml', '_category_.json', 'assets/**', 'images/**', 'img/**'] + } +}; + +export const META_FILES_TEST = /_category_\.(yml|json)$/; + +const getDocsRoot = ( + packageDir: string, + destDir: string, + filePath: string +): { src: string; dest: string } | null => { + // filePath = ".../packages///docs/(...)?..." + const parts = filePath.split(path.sep); + console.log(parts); + const docsIdx = parts.lastIndexOf('docs'); + if (META_FILES_TEST.test(filePath)) { + if (fsSym.existsSync(filePath)) { + return { src: filePath, dest: filePath.replace(packageDir, destDir) }; + } + } + if (docsIdx >= 2) { + const org = parts[docsIdx - 2]; + const pkg = parts[docsIdx - 1]; + const docsSrc = path.join(packageDir, org, pkg, 'docs'); + const docsDest = path.join(destDir, org, pkg, 'docs'); + if (fsSym.existsSync(docsSrc) && fsSym.statSync(docsSrc).isDirectory()) { + return { src: `${docsSrc}/`, dest: `${docsDest}/` }; + } + } + return null; +}; + +const syncDocsFolder = (src: string, dest: string) => { + if (dest.endsWith(path.sep)) { + fsSym.mkdirSync(dest, { recursive: true }); + } + spawn('rsync', ['-av', '--delete', src, dest], { stdio: 'inherit' }); +}; +export const getPackageDocsConfig = async (packagesDir: string): Promise => { + const orgDirs = await fs.readdir(packagesDir, { withFileTypes: true }); + const allConfigs: TdevPackageConfig[] = []; + + for (const orgDir of orgDirs) { + if (!orgDir.isDirectory()) continue; + const orgPath = path.join(packagesDir, orgDir.name); + const packageDirs = await fs.readdir(orgPath, { withFileTypes: true }); + + for (const packageDirEnt of packageDirs) { + if (!packageDirEnt.isDirectory()) continue; + const packagePath = path.join(orgPath, packageDirEnt.name); + + // Heuristic 1: tdevPackage.config.yml + const configYml = path.join(packagePath, 'tdevPackage.config.yml'); + try { + await fs.access(configYml); + const raw = await fs.readFile(configYml, 'utf8'); + const config: any = yaml.load(raw) || {}; + allConfigs.push({ + path: packagePath, + ...config + }); + continue; + } catch {} + + // Heuristic 2: docs folder + const docsDir = path.join(packagePath, 'docs'); + try { + const stat = await fs.stat(docsDir); + if (stat.isDirectory()) { + allConfigs.push({ + path: packagePath, + ...DEFAULT_DOCS_CONFIG + }); + continue; + } + } catch {} + + // Heuristic 3: README.mdx? at root + for (const file of ['README.mdx', 'README.md']) { + const readmePath = path.join(packagePath, file); + try { + const stat = await fs.stat(readmePath); + if (stat.isFile()) { + allConfigs.push({ + path: packagePath, + ...DEFAULT_README_CONFIG + }); + break; // done for this package + } + } catch {} + } + } + } + + return allConfigs; +}; + +export const getDebouncedSyncer = (packageDir: string, destDir: string) => { + const syncQueue = new Set(); + const syncDebounced = debounce(() => { + for (const docsPath of syncQueue) { + const info = getDocsRoot(packageDir, destDir, docsPath); + if (info) { + syncDocsFolder(info.src, info.dest); + } + } + syncQueue.clear(); + }, 300); + return { syncQueue, syncDebounced }; +}; diff --git a/updateSync/packageDocsSync/index.ts b/updateSync/packageDocsSync/index.ts index b06229fe7..62b537f03 100644 --- a/updateSync/packageDocsSync/index.ts +++ b/updateSync/packageDocsSync/index.ts @@ -1,60 +1,22 @@ -import { spawn } from 'child_process'; +import { LoadContext, PluginModule, RouteConfig } from '@docusaurus/types'; +import { getPackageDocsConfig } from './actions'; import path from 'path'; -import fs from 'fs'; -import { debounce } from 'es-toolkit/compat'; -interface TdevPackageConfig { - type: 'local' | 'git'; - docs?: Partial<{ - path: string; - include: string[]; - exclude: string[]; - }>; +export interface Config { + packageDir: string; + destDir: string; } -export const META_FILES_TEST = /_category_\.(yml|json)$/; -function getDocsRoot( - packageDir: string, - destDir: string, - filePath: string -): { src: string; dest: string } | null { - // filePath = ".../packages///docs/(...)?..." - const parts = filePath.split(path.sep); - console.log(parts); - const docsIdx = parts.lastIndexOf('docs'); - if (META_FILES_TEST.test(filePath)) { - if (fs.existsSync(filePath)) { - return { src: filePath, dest: filePath.replace(packageDir, destDir) }; +const packageDocsSync: PluginModule = (context: LoadContext, options) => { + const opts = options as Config; + return { + name: 'package-docs-sync-plugin', + async loadContent() { + const data = await getPackageDocsConfig(path.resolve(process.cwd(), 'packages')); + console.log('Loaded package docs config:', JSON.stringify(data, null, 2)); + return data; } - } - if (docsIdx >= 2) { - const org = parts[docsIdx - 2]; - const pkg = parts[docsIdx - 1]; - const docsSrc = path.join(packageDir, org, pkg, 'docs'); - const docsDest = path.join(destDir, org, pkg, 'docs'); - if (fs.existsSync(docsSrc) && fs.statSync(docsSrc).isDirectory()) { - return { src: `${docsSrc}/`, dest: `${docsDest}/` }; - } - } - return null; -} - -function syncDocsFolder(src: string, dest: string) { - if (dest.endsWith(path.sep)) { - fs.mkdirSync(dest, { recursive: true }); - } - spawn('rsync', ['-av', '--delete', src, dest], { stdio: 'inherit' }); -} -export const getDebouncedSyncer = (packageDir: string, destDir: string) => { - const syncQueue = new Set(); - const syncDebounced = debounce(() => { - for (const docsPath of syncQueue) { - const info = getDocsRoot(packageDir, destDir, docsPath); - if (info) { - syncDocsFolder(info.src, info.dest); - } - } - syncQueue.clear(); - }, 300); - return { syncQueue, syncDebounced }; + }; }; + +export default packageDocsSync; diff --git a/updateSync/packageDocsSync/watch.ts b/updateSync/packageDocsSync/watch.ts index 7bd678fc1..bb3f76247 100644 --- a/updateSync/packageDocsSync/watch.ts +++ b/updateSync/packageDocsSync/watch.ts @@ -1,7 +1,7 @@ import chokidar from 'chokidar'; import minimist from 'minimist'; import path from 'path'; -import { getDebouncedSyncer, META_FILES_TEST } from '.'; +import { getDebouncedSyncer, META_FILES_TEST } from './actions'; const argv = minimist(process.argv.slice(2), { string: ['src', 'dest'], @@ -14,7 +14,7 @@ const argv = minimist(process.argv.slice(2), { const PACKAGES_DIR = path.resolve(process.cwd(), argv.src); const DEST_ROOT = path.resolve(process.cwd(), argv.dest); -const watcher = chokidar.watch(PACKAGES_DIR, { ignoreInitial: false, persistent: true }); +const watcher = chokidar.watch(PACKAGES_DIR, { ignoreInitial: true, persistent: true }); const { syncQueue, syncDebounced } = getDebouncedSyncer(PACKAGES_DIR, DEST_ROOT); From 0177e4a4cf7760cd0ab663e2f16626be6f3da81d Mon Sep 17 00:00:00 2001 From: bh0fer Date: Sat, 27 Dec 2025 18:52:50 +0100 Subject: [PATCH 3/9] update package change tracker --- docusaurus.config.ts | 13 +- package.json | 3 +- src/siteConfig/extractPackageDocs.ts | 44 ---- tdev-website/docs/packages/.gitignore | 2 +- updateSync/packageDocsSync/actions.ts | 286 ++++++++++++++++++-------- updateSync/packageDocsSync/index.ts | 35 +++- updateSync/packageDocsSync/watch.ts | 48 +++-- yarn.lock | 100 +++++++-- 8 files changed, 347 insertions(+), 184 deletions(-) delete mode 100644 src/siteConfig/extractPackageDocs.ts diff --git a/docusaurus.config.ts b/docusaurus.config.ts index a149ff89f..45acdcde9 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -41,7 +41,6 @@ import { } from './src/siteConfig/markdownPluginConfigs'; import { remarkPdfPluginConfig } from '@tdev/remark-pdf'; import { GlobExcludeDefault } from '@docusaurus/utils'; -import extractPackageDocs from './src/siteConfig/extractPackageDocs'; import packageDocsSync from './updateSync/packageDocsSync'; const withSiteConfig = async (): Promise => { @@ -273,10 +272,6 @@ const docusaurusConfig = withSiteConfig().then(async (siteConfig) => { beforeDefaultRemarkPlugins: BEFORE_DEFAULT_REMARK_PLUGINS, ...DEFAULT_ADMONITION_CONFIG, exclude: [...new Set([...GlobExcludeDefault, '**/node_modules/**'])], - async sidebarItemsGenerator({defaultSidebarItemsGenerator, ...args}) { - const sidebarItems = await defaultSidebarItemsGenerator(args); - return extractPackageDocs(sidebarItems); - }, ...(siteConfig.docs || {}) } : false, @@ -369,7 +364,13 @@ const docusaurusConfig = withSiteConfig().then(async (siteConfig) => { ...(siteConfig.themeConfig || {}) } satisfies Preset.ThemeConfig, plugins: [ - packageDocsSync, + [ + packageDocsSync, + { + packageDir: 'packages', + destDir: 'tdev-website/docs/packages' + } + ], sassPluginConfig, dynamicRouterPluginConfig, rsDoctorPluginConfig, diff --git a/package.json b/package.json index 0ee38c084..10fe4f21f 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "private": true, "scripts": { "docusaurus": "docusaurus", - "start": "ts-node updateSync/packageDocsSync/watch.ts --src packages --dest tdev-website/docs/packages & docusaurus start", + "start": "concurrently --raw --kill-others 'docusaurus start' 'ts-node updateSync/packageDocsSync/watch.ts --src packages --dest tdev-website/docs/packages'", "build": "docusaurus build", "swizzle": "docusaurus swizzle", "deploy": "docusaurus deploy", @@ -105,6 +105,7 @@ "@types/uuid": "^10.0.0", "@types/wicg-file-system-access": "^2023.10.6", "@vitest/coverage-v8": "^2.0.5", + "concurrently": "^9.2.1", "fs-extra": "^11.2.0", "prettier": "^3.3.2", "remark": "^15.0.1", diff --git a/src/siteConfig/extractPackageDocs.ts b/src/siteConfig/extractPackageDocs.ts deleted file mode 100644 index f427d413e..000000000 --- a/src/siteConfig/extractPackageDocs.ts +++ /dev/null @@ -1,44 +0,0 @@ -import type { - NormalizedSidebar, - NormalizedSidebarItem -} from '@docusaurus/plugin-content-docs/src/sidebars/types.js'; - -const extractPackageDocs = (items: NormalizedSidebar): NormalizedSidebar => { - // Reverse items in categories - const result = items.map((item) => { - if (item.type !== 'category' || item.key !== 'packages-docs') { - return item; - } - const itemCategory = { - ...item, - items: item.items.slice().flatMap((subItem) => { - if (subItem.type !== 'category') { - return [subItem]; - } - const newItems = subItem.items.reduce((acc, subSubItem) => { - // if a doc is found as a direct child of the category, keep it - if (subSubItem.type !== 'category') { - return [...acc, subSubItem]; - } - // if the sub-sub-category is empty and has a link to a doc, keep the link - if (subSubItem.items.length === 0 && subSubItem.link?.type === 'doc') { - return [...acc, subSubItem]; - } - // otherwise, inline the items of the sub-sub-category - if (subSubItem.items.length === 1 && !subSubItem.link) { - return [...acc, ...subSubItem.items]; - } - return [...acc, subSubItem]; - }, [] as NormalizedSidebarItem[]); - return { - ...subItem, - items: newItems - } as NormalizedSidebarItem; - }) - }; - return itemCategory; - }); - return result; -}; - -export default extractPackageDocs; diff --git a/tdev-website/docs/packages/.gitignore b/tdev-website/docs/packages/.gitignore index c96a04f00..d6b7ef32c 100644 --- a/tdev-website/docs/packages/.gitignore +++ b/tdev-website/docs/packages/.gitignore @@ -1,2 +1,2 @@ * -!.gitignore \ No newline at end of file +!.gitignore diff --git a/updateSync/packageDocsSync/actions.ts b/updateSync/packageDocsSync/actions.ts index a8f829021..28b915543 100644 --- a/updateSync/packageDocsSync/actions.ts +++ b/updateSync/packageDocsSync/actions.ts @@ -1,118 +1,182 @@ -import { spawn } from 'child_process'; import path from 'path'; -import fsSym from 'fs'; import fs from 'fs/promises'; import yaml from 'js-yaml'; import { debounce } from 'es-toolkit/compat'; interface TdevPackageConfig { path: string; - docs?: Partial<{ + docs: { + org: string; + package: string; path: string; - include: string[]; - exclude: string[]; - }>; + include?: string[]; + exclude?: string[]; + }; } -const DEFAULT_DOCS_CONFIG: Partial = { - docs: { - path: 'docs' - } +const TDEV_PACKAGE_CONFIG_YML = 'tdevPackage.config.yml' as const; +const DEFAULT_DOCS_CONFIG: Omit = { + path: 'docs' }; +const CWD = process.cwd(); -const DEFAULT_README_CONFIG: Partial = { - docs: { - path: '.', - include: ['_category_.yml', '_category_.json', 'assets/**', 'images/**', 'img/**'] +const DEFAULT_README_CONFIG: Omit = { + path: '.', + include: [ + 'README.md', + 'README.mdx', + '_category_.yml', + '_category_.json', + 'assets/**', + 'images/**', + 'img/**' + ] +}; + +interface PackageInfo { + packageDir: string; + org: string; + package: string; + relativeSubPath?: string; +} + +const resolveDir = (pkgPath: string, ...parts: string[]): string => { + if (pkgPath.startsWith(CWD)) { + return path.resolve(pkgPath, ...parts); } + return path.resolve(CWD, pkgPath, ...parts); }; -export const META_FILES_TEST = /_category_\.(yml|json)$/; - -const getDocsRoot = ( - packageDir: string, - destDir: string, - filePath: string -): { src: string; dest: string } | null => { - // filePath = ".../packages///docs/(...)?..." - const parts = filePath.split(path.sep); - console.log(parts); - const docsIdx = parts.lastIndexOf('docs'); - if (META_FILES_TEST.test(filePath)) { - if (fsSym.existsSync(filePath)) { - return { src: filePath, dest: filePath.replace(packageDir, destDir) }; - } +export const packageInfo = (filePath: string, packageDir: string): PackageInfo | null => { + if (!filePath.startsWith(packageDir)) { + return null; } - if (docsIdx >= 2) { - const org = parts[docsIdx - 2]; - const pkg = parts[docsIdx - 1]; - const docsSrc = path.join(packageDir, org, pkg, 'docs'); - const docsDest = path.join(destDir, org, pkg, 'docs'); - if (fsSym.existsSync(docsSrc) && fsSym.statSync(docsSrc).isDirectory()) { - return { src: `${docsSrc}/`, dest: `${docsDest}/` }; + const relPath = path.relative(packageDir, filePath); + const parts = relPath.split(path.sep); + if (parts.length >= 2) { + const pkgInfo: PackageInfo = { + packageDir, + org: parts[0], + package: parts[1] + }; + const relativeSubPath = parts.slice(2).join(path.sep); + if (relativeSubPath.length > 0) { + pkgInfo.relativeSubPath = relativeSubPath; } + return pkgInfo; } return null; }; -const syncDocsFolder = (src: string, dest: string) => { - if (dest.endsWith(path.sep)) { - fsSym.mkdirSync(dest, { recursive: true }); +export const syncDocsFolder = async (pkgConfig: TdevPackageConfig, packageDocsDir: string) => { + const { org, package: packageName, path: docsPath } = pkgConfig.docs; + const srcPath = path.resolve(pkgConfig.path, docsPath); + const destPackagePath = resolveDir(packageDocsDir, org, packageName); + await fs.mkdir(destPackagePath, { recursive: true }); + const rsyncArgs = ['-avq', '--delete', '--chmod=Fa-w']; + if (pkgConfig.docs.include && pkgConfig.docs.include.length > 0) { + pkgConfig.docs.include.forEach((inc) => { + rsyncArgs.push('--include', inc); + }); + rsyncArgs.push('--exclude', '*'); + } + if (pkgConfig.docs.exclude && pkgConfig.docs.exclude.length > 0) { + pkgConfig.docs.exclude.forEach((exc) => { + rsyncArgs.push('--exclude', exc); + }); + } + rsyncArgs.push(`${srcPath}/`, destPackagePath); + const { spawn } = await import('child_process'); + return new Promise((resolve, reject) => { + const rsync = spawn('rsync', rsyncArgs, { stdio: 'inherit' }); + rsync.on('close', (code) => { + if (code === 0) { + resolve(`✅ ${pkgConfig.docs.org}/${pkgConfig.docs.package} docs synced.`); + } else { + console.error(`rsync failed with exit code ${code}`); + reject(new Error(`rsync process exited with code ${code}`)); + } + }); + }); +}; + +const getPackageDocsConfig = async ( + packagesDir: string, + orgName: string, + packageName: string +): Promise => { + const packagePath = path.join(packagesDir, orgName, packageName); + + // Heuristic 1: tdevPackage.config.yml + const configYml = path.join(packagePath, TDEV_PACKAGE_CONFIG_YML); + try { + await fs.access(configYml); + const raw = await fs.readFile(configYml, 'utf8'); + const config: any = yaml.load(raw) || {}; + return { + path: packagePath, + docs: { + org: orgName, + package: packageName, + ...config.docs + } + }; + } catch {} + + // Heuristic 2: docs folder + const docsDir = path.join(packagePath, 'docs'); + try { + const stat = await fs.stat(docsDir); + if (stat.isDirectory()) { + return { + path: packagePath, + docs: { + ...DEFAULT_DOCS_CONFIG, + org: orgName, + package: packageName + } + }; + } + } catch {} + + // Heuristic 3: README.mdx? at root + for (const file of ['README.mdx', 'README.md']) { + const readmePath = path.join(packagePath, file); + try { + const stat = await fs.stat(readmePath); + if (stat.isFile()) { + return { + path: packagePath, + docs: { + ...DEFAULT_README_CONFIG, + org: orgName, + package: packageName + } + }; + } + } catch {} } - spawn('rsync', ['-av', '--delete', src, dest], { stdio: 'inherit' }); + return null; }; -export const getPackageDocsConfig = async (packagesDir: string): Promise => { + +export const getPackageDocsConfigs = async (packagesDir: string): Promise => { const orgDirs = await fs.readdir(packagesDir, { withFileTypes: true }); const allConfigs: TdevPackageConfig[] = []; for (const orgDir of orgDirs) { - if (!orgDir.isDirectory()) continue; + if (!orgDir.isDirectory()) { + continue; + } const orgPath = path.join(packagesDir, orgDir.name); const packageDirs = await fs.readdir(orgPath, { withFileTypes: true }); for (const packageDirEnt of packageDirs) { - if (!packageDirEnt.isDirectory()) continue; - const packagePath = path.join(orgPath, packageDirEnt.name); - - // Heuristic 1: tdevPackage.config.yml - const configYml = path.join(packagePath, 'tdevPackage.config.yml'); - try { - await fs.access(configYml); - const raw = await fs.readFile(configYml, 'utf8'); - const config: any = yaml.load(raw) || {}; - allConfigs.push({ - path: packagePath, - ...config - }); + if (!packageDirEnt.isDirectory()) { continue; - } catch {} - - // Heuristic 2: docs folder - const docsDir = path.join(packagePath, 'docs'); - try { - const stat = await fs.stat(docsDir); - if (stat.isDirectory()) { - allConfigs.push({ - path: packagePath, - ...DEFAULT_DOCS_CONFIG - }); - continue; - } - } catch {} - - // Heuristic 3: README.mdx? at root - for (const file of ['README.mdx', 'README.md']) { - const readmePath = path.join(packagePath, file); - try { - const stat = await fs.stat(readmePath); - if (stat.isFile()) { - allConfigs.push({ - path: packagePath, - ...DEFAULT_README_CONFIG - }); - break; // done for this package - } - } catch {} + } + const pkgConfig = await getPackageDocsConfig(packagesDir, orgDir.name, packageDirEnt.name); + if (pkgConfig) { + allConfigs.push(pkgConfig); } } } @@ -120,16 +184,58 @@ export const getPackageDocsConfig = async (packagesDir: string): Promise { - const syncQueue = new Set(); - const syncDebounced = debounce(() => { - for (const docsPath of syncQueue) { - const info = getDocsRoot(packageDir, destDir, docsPath); - if (info) { - syncDocsFolder(info.src, info.dest); +export const getDebouncedSyncer = async (packageDir: string, destDir: string) => { + const syncQueue = new Set(); + const PackageConfigCache: Map = new Map(); + getPackageDocsConfigs(packageDir).then((configs) => { + for (const cfg of configs) { + const pkgKey = `${cfg.docs.org}/${cfg.docs.package}`; + PackageConfigCache.set(pkgKey, cfg); + } + }); + + const setPackageConfig = async (pkgKey: string, newConfig?: TdevPackageConfig) => { + if (!newConfig) { + return false; + } + const prevConfig = PackageConfigCache.get(pkgKey); + if (prevConfig) { + const prevDocsPath = resolveDir(destDir, prevConfig.docs.org, prevConfig.docs.package); + const currentDocsPath = resolveDir(destDir, newConfig.docs.org, newConfig.docs.package); + if (prevDocsPath !== currentDocsPath) { + fs.rm(prevDocsPath, { recursive: true, force: true }).catch(() => {}); + } + } + PackageConfigCache.set(pkgKey, newConfig); + return true; + }; + + const syncDebounced = debounce(async () => { + const syncTasks: Promise[] = []; + for (const pkgInfo of syncQueue) { + if (!pkgInfo) { + continue; + } + const pkgKey = `${pkgInfo.org}/${pkgInfo.package}`; + if (pkgInfo.relativeSubPath === TDEV_PACKAGE_CONFIG_YML) { + const res = getPackageDocsConfig(packageDir, pkgInfo.org, pkgInfo.package).then( + (newConfig) => { + if (setPackageConfig(pkgKey, newConfig)) { + return syncDocsFolder(newConfig, destDir); + } + return Promise.resolve(`ℹ️ ${pkgKey} docs config unchanged.`); + } + ); + syncTasks.push(res); + } else { + const pkgConfig = PackageConfigCache.get(pkgKey); + if (pkgConfig) { + syncTasks.push(syncDocsFolder(pkgConfig, destDir)); + } } } syncQueue.clear(); + return Promise.all(syncTasks); }, 300); return { syncQueue, syncDebounced }; }; diff --git a/updateSync/packageDocsSync/index.ts b/updateSync/packageDocsSync/index.ts index 62b537f03..24cc3ccb2 100644 --- a/updateSync/packageDocsSync/index.ts +++ b/updateSync/packageDocsSync/index.ts @@ -1,5 +1,6 @@ -import { LoadContext, PluginModule, RouteConfig } from '@docusaurus/types'; -import { getPackageDocsConfig } from './actions'; +import { LoadContext, PluginModule } from '@docusaurus/types'; +import fs from 'fs/promises'; +import { getPackageDocsConfigs, syncDocsFolder } from './actions'; import path from 'path'; export interface Config { @@ -12,9 +13,33 @@ const packageDocsSync: PluginModule = (context: LoadContext, options) => { return { name: 'package-docs-sync-plugin', async loadContent() { - const data = await getPackageDocsConfig(path.resolve(process.cwd(), 'packages')); - console.log('Loaded package docs config:', JSON.stringify(data, null, 2)); - return data; + const data = await getPackageDocsConfigs(path.resolve(process.cwd(), opts.packageDir)); + // create the dest dir if it doesn't exist + const destPath = path.resolve(process.cwd(), opts.destDir); + await fs.rm(destPath, { recursive: true, force: true }); + await fs.mkdir(destPath, { recursive: true }); + /** + * add .gitignore to destPath to ignore all files + */ + const gitignorePath = path.join(destPath, '.gitignore'); + const gitignoreContent = '*\n!.gitignore\n'; + await fs.writeFile(gitignorePath, gitignoreContent, 'utf8'); + /** + * copy the package docs according to the config with rsync. + * Ensure the copied files are read only. + * When an include is specified, only copy those files/folders. + * When an exclude is specified, do not copy those files/folders (exclude wins over include when both are specified). + * Use rsync options to achieve this. + * Perform asynchronously. + * Copy the content to the root of `destPath///`. + */ + const result = await Promise.all( + data.map(async (pkgConfig) => { + return syncDocsFolder(pkgConfig, destPath); + }) + ); + console.log(result.join('\n')); + return; } }; }; diff --git a/updateSync/packageDocsSync/watch.ts b/updateSync/packageDocsSync/watch.ts index bb3f76247..835306c83 100644 --- a/updateSync/packageDocsSync/watch.ts +++ b/updateSync/packageDocsSync/watch.ts @@ -1,7 +1,7 @@ import chokidar from 'chokidar'; import minimist from 'minimist'; import path from 'path'; -import { getDebouncedSyncer, META_FILES_TEST } from './actions'; +import { getDebouncedSyncer, packageInfo } from './actions'; const argv = minimist(process.argv.slice(2), { string: ['src', 'dest'], @@ -16,22 +16,30 @@ const DEST_ROOT = path.resolve(process.cwd(), argv.dest); const watcher = chokidar.watch(PACKAGES_DIR, { ignoreInitial: true, persistent: true }); -const { syncQueue, syncDebounced } = getDebouncedSyncer(PACKAGES_DIR, DEST_ROOT); - -const NODE_MODULES_TEST = /node_modules/; -const DOCS_PATH_TEST = new RegExp(`${path.sep}docs${path.sep}|${path.sep}docs$`); - -watcher - .on('all', (_event, filePath) => { - if (NODE_MODULES_TEST.test(filePath)) { - return null; - } - - if (DOCS_PATH_TEST.test(filePath) || META_FILES_TEST.test(filePath)) { - syncQueue.add(filePath); - syncDebounced(); - } - }) - .on('ready', () => { - console.log('Initial scan complete. Watching for docs changes...'); - }); +const main = async () => { + const { syncQueue, syncDebounced } = await getDebouncedSyncer(PACKAGES_DIR, DEST_ROOT); + + const NODE_MODULES_TEST = /node_modules/; + + watcher + .on('all', async (_event, filePath) => { + if (NODE_MODULES_TEST.test(filePath)) { + return null; + } + const pkgInfo = packageInfo(filePath, PACKAGES_DIR); + if (pkgInfo === null) { + return null; + } + + syncQueue.add(pkgInfo); + await syncDebounced(); + }) + .on('ready', () => { + console.log('Watching for docs changes in packages...'); + }); +}; + +main().catch((err) => { + console.error('Error in docs watcher:', err); + process.exit(1); +}); diff --git a/yarn.lock b/yarn.lock index 5e52da581..7582f8aaa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7139,6 +7139,14 @@ chainsaw@~0.1.0: dependencies: traverse ">=0.3.0 <0.4" +chalk@4.1.2, chalk@^4.0.0, chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -7148,14 +7156,6 @@ chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4.0.0, chalk@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - chalk@^5.0.1, chalk@^5.2.0: version "5.6.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.6.2.tgz#b1238b6e23ea337af71c7f8a295db5af0c158aea" @@ -7308,6 +7308,15 @@ cli-table3@^0.6.3: optionalDependencies: "@colors/colors" "1.5.0" +cliui@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" + integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.1" + wrap-ansi "^7.0.0" + clone-deep@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" @@ -7471,6 +7480,18 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== +concurrently@^9.2.1: + version "9.2.1" + resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-9.2.1.tgz#248ea21b95754947be2dad9c3e4b60f18ca4e44f" + integrity sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng== + dependencies: + chalk "4.1.2" + rxjs "7.8.2" + shell-quote "1.8.3" + supports-color "8.1.1" + tree-kill "1.2.2" + yargs "17.7.2" + confbox@^0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/confbox/-/confbox-0.1.8.tgz#820d73d3b3c82d9bd910652c5d4d599ef8ff8b06" @@ -9384,6 +9405,11 @@ gensync@^1.0.0-beta.2: resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" @@ -14127,6 +14153,11 @@ repeat-string@^1.0.0: resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" integrity sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w== +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + require-from-string@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" @@ -14283,6 +14314,13 @@ rw@1: resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4" integrity sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ== +rxjs@7.8.2: + version "7.8.2" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.2.tgz#955bc473ed8af11a002a2be52071bf475638607b" + integrity sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA== + dependencies: + tslib "^2.1.0" + sade@^1.7.3: version "1.8.1" resolved "https://registry.yarnpkg.com/sade/-/sade-1.8.1.tgz#0a78e81d658d394887be57d2a409bf703a3b2701" @@ -14529,7 +14567,7 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -shell-quote@^1.8.3: +shell-quote@1.8.3, shell-quote@^1.8.3: version "1.8.3" resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.3.tgz#55e40ef33cf5c689902353a3d8cd1a6725f08b4b" integrity sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw== @@ -14820,7 +14858,7 @@ strict-event-emitter@^0.4.3: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string-width@^4.1.0, string-width@^4.2.0: +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -14947,6 +14985,13 @@ stylis@^4.1.3, stylis@^4.3.6: resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.3.6.tgz#7c7b97191cb4f195f03ecab7d52f7902ed378320" integrity sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ== +supports-color@8.1.1, supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + supports-color@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" @@ -14961,13 +15006,6 @@ supports-color@^7.1.0: dependencies: has-flag "^4.0.0" -supports-color@^8.0.0: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" @@ -15177,6 +15215,11 @@ tree-dump@^1.0.3, tree-dump@^1.1.0: resolved "https://registry.yarnpkg.com/tree-dump/-/tree-dump-1.1.0.tgz#ab29129169dc46004414f5a9d4a3c6e89f13e8a4" integrity sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA== +tree-kill@1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" + integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== + trim-lines@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/trim-lines/-/trim-lines-3.0.1.tgz#d802e332a07df861c48802c04321017b1bd87338" @@ -16054,6 +16097,11 @@ xmlhttprequest-ssl@~2.1.1: resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz#e9e8023b3f29ef34b97a859f584c5e6c61418e23" integrity sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ== +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + yallist@^3.0.2: version "3.1.1" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" @@ -16064,6 +16112,24 @@ yaml@^1.10.0: resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + +yargs@17.7.2: + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== + dependencies: + cliui "^8.0.1" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.1.1" + yn@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" From debfb73aebf8805a6e442914f2d6a96185212eac Mon Sep 17 00:00:00 2001 From: bh0fer Date: Sat, 27 Dec 2025 21:39:50 +0100 Subject: [PATCH 4/9] tmp --- docusaurus.config.ts | 10 +-- updateSync/packageDocsSync/actions.ts | 5 +- updateSync/packageDocsSync/index.ts | 89 +++++++++++++++------------ 3 files changed, 56 insertions(+), 48 deletions(-) diff --git a/docusaurus.config.ts b/docusaurus.config.ts index 45acdcde9..6a1e78e04 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -80,6 +80,9 @@ const docusaurusConfig = withSiteConfig().then(async (siteConfig) => { const DOCS_PATH = useTdevContentPath(siteConfig, 'docs'); const BLOG_PATH = useTdevContentPath(siteConfig, 'blog'); + console.log('RUNNING BUILD WITH DOCS PATH:', DOCS_PATH); + //await packageDocsSync('packages', `${DOCS_PATH}/packages`); + const BEFORE_DEFAULT_REMARK_PLUGINS = siteConfig.beforeDefaultRemarkPlugins ?? recommendedBeforeDefaultRemarkPlugins; @@ -364,13 +367,6 @@ const docusaurusConfig = withSiteConfig().then(async (siteConfig) => { ...(siteConfig.themeConfig || {}) } satisfies Preset.ThemeConfig, plugins: [ - [ - packageDocsSync, - { - packageDir: 'packages', - destDir: 'tdev-website/docs/packages' - } - ], sassPluginConfig, dynamicRouterPluginConfig, rsDoctorPluginConfig, diff --git a/updateSync/packageDocsSync/actions.ts b/updateSync/packageDocsSync/actions.ts index 28b915543..37db9e6e0 100644 --- a/updateSync/packageDocsSync/actions.ts +++ b/updateSync/packageDocsSync/actions.ts @@ -53,6 +53,9 @@ export const packageInfo = (filePath: string, packageDir: string): PackageInfo | } const relPath = path.relative(packageDir, filePath); const parts = relPath.split(path.sep); + if (parts.length === 0) { + return null; + } if (parts.length >= 2) { const pkgInfo: PackageInfo = { packageDir, @@ -70,7 +73,7 @@ export const packageInfo = (filePath: string, packageDir: string): PackageInfo | export const syncDocsFolder = async (pkgConfig: TdevPackageConfig, packageDocsDir: string) => { const { org, package: packageName, path: docsPath } = pkgConfig.docs; - const srcPath = path.resolve(pkgConfig.path, docsPath); + const srcPath = resolveDir(pkgConfig.path, docsPath); const destPackagePath = resolveDir(packageDocsDir, org, packageName); await fs.mkdir(destPackagePath, { recursive: true }); const rsyncArgs = ['-avq', '--delete', '--chmod=Fa-w']; diff --git a/updateSync/packageDocsSync/index.ts b/updateSync/packageDocsSync/index.ts index 24cc3ccb2..f00b628dc 100644 --- a/updateSync/packageDocsSync/index.ts +++ b/updateSync/packageDocsSync/index.ts @@ -1,47 +1,56 @@ -import { LoadContext, PluginModule } from '@docusaurus/types'; import fs from 'fs/promises'; import { getPackageDocsConfigs, syncDocsFolder } from './actions'; import path from 'path'; -export interface Config { - packageDir: string; - destDir: string; -} - -const packageDocsSync: PluginModule = (context: LoadContext, options) => { - const opts = options as Config; - return { - name: 'package-docs-sync-plugin', - async loadContent() { - const data = await getPackageDocsConfigs(path.resolve(process.cwd(), opts.packageDir)); - // create the dest dir if it doesn't exist - const destPath = path.resolve(process.cwd(), opts.destDir); - await fs.rm(destPath, { recursive: true, force: true }); - await fs.mkdir(destPath, { recursive: true }); - /** - * add .gitignore to destPath to ignore all files - */ - const gitignorePath = path.join(destPath, '.gitignore'); - const gitignoreContent = '*\n!.gitignore\n'; - await fs.writeFile(gitignorePath, gitignoreContent, 'utf8'); - /** - * copy the package docs according to the config with rsync. - * Ensure the copied files are read only. - * When an include is specified, only copy those files/folders. - * When an exclude is specified, do not copy those files/folders (exclude wins over include when both are specified). - * Use rsync options to achieve this. - * Perform asynchronously. - * Copy the content to the root of `destPath///`. - */ - const result = await Promise.all( - data.map(async (pkgConfig) => { - return syncDocsFolder(pkgConfig, destPath); - }) - ); - console.log(result.join('\n')); - return; - } - }; +const packageDocsSync = async (packageDir: string, destDir: string) => { + const srcPath = path.resolve(process.cwd(), packageDir); + const data = await getPackageDocsConfigs(srcPath); + // create the dest dir if it doesn't exist + const destPath = path.resolve(process.cwd(), destDir); + await fs.rm(destPath, { recursive: true, force: true }); + await fs.mkdir(destPath, { recursive: true }); + /** + * add .gitignore to destPath to ignore all files + */ + const gitignorePath = path.join(destPath, '.gitignore'); + const gitignoreContent = '*\n!.gitignore\n'; + await fs.writeFile(gitignorePath, gitignoreContent, 'utf8'); + const orgs = [...new Set(data.map((cfg) => cfg.docs.org))]; + const result = await Promise.all([ + ...data.map(async (pkgConfig) => { + return syncDocsFolder(pkgConfig, destPath); + }), + ...orgs.map(async (org) => { + const orgSrc = path.join(srcPath, org); + for (const ext of ['.json', '.yaml', '.yml']) { + const categoryFileSrc = path.join(orgSrc, `_category_${ext}`); + try { + const stat = await fs.stat(categoryFileSrc); + if (stat.isFile()) { + const categoryFileDest = path.join(destPath, org, `_category_${ext}`); + await fs.mkdir(path.dirname(categoryFileDest), { recursive: true }); + await fs.copyFile(categoryFileSrc, categoryFileDest); + return `Copied ${org} _category_ file`; + } + } catch {} + } + }), + ...['.json', '.yaml', '.yml'].map(async (ext) => { + const orgSrc = path.join(srcPath); + const categoryFileSrc = path.join(orgSrc, `_category_${ext}`); + try { + const stat = await fs.stat(categoryFileSrc); + if (stat.isFile()) { + const categoryFileDest = path.join(destPath, `_category_${ext}`); + await fs.mkdir(path.dirname(categoryFileDest), { recursive: true }); + await fs.copyFile(categoryFileSrc, categoryFileDest); + return 'Copied root _category_ file'; + } + } catch {} + }) + ]); + console.log(result.join('\n')); + return result; }; export default packageDocsSync; From 88bf76cacfedab4d0d63fe416260c201826bc4c3 Mon Sep 17 00:00:00 2001 From: bh0fer Date: Sat, 27 Dec 2025 22:59:13 +0100 Subject: [PATCH 5/9] prepare for prebuild step --- docusaurus.config.ts | 19 ++----------------- src/siteConfig/withSiteConfig.ts | 16 ++++++++++++++++ updateSync/packageDocsSync/preBuild.ts | 20 ++++++++++++++++++++ updateSync/packageDocsSync/watch.ts | 3 ++- 4 files changed, 40 insertions(+), 18 deletions(-) create mode 100644 src/siteConfig/withSiteConfig.ts create mode 100644 updateSync/packageDocsSync/preBuild.ts diff --git a/docusaurus.config.ts b/docusaurus.config.ts index 6a1e78e04..07520c3a5 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -2,7 +2,6 @@ require('dotenv').config(); import type { EditThisPageOption, ShowEditThisPage, - SiteConfig, TdevConfig } from '@tdev/siteConfig/siteConfig'; import { themes as prismThemes } from 'prism-react-renderer'; @@ -37,24 +36,11 @@ import path from 'path'; import { recommendedBeforeDefaultRemarkPlugins, recommendedRehypePlugins, - recommendedRemarkPlugins + recommendedRemarkPlugins, } from './src/siteConfig/markdownPluginConfigs'; import { remarkPdfPluginConfig } from '@tdev/remark-pdf'; import { GlobExcludeDefault } from '@docusaurus/utils'; -import packageDocsSync from './updateSync/packageDocsSync'; - -const withSiteConfig = async (): Promise => { - if (process.env.SITE_CONFIG_PATH) { - console.log(`Using site config from ${process.env.SITE_CONFIG_PATH}`); - const pathToConfig = path.resolve(process.cwd(), process.env.SITE_CONFIG_PATH); - const getConfig = await import(pathToConfig).then((mod) => mod.default); - return getConfig(); - } else { - console.log(`Using site config from default './siteConfig'`); - const getConfig = await import('./siteConfig').then((mod) => mod.default); - return getConfig(); - } -}; +import { withSiteConfig } from '@tdev/siteConfig/withSiteConfig'; const BUILD_LOCATION = __dirname; const GIT_COMMIT_SHA = process.env.GITHUB_SHA || Math.random().toString(36).substring(7); @@ -80,7 +66,6 @@ const docusaurusConfig = withSiteConfig().then(async (siteConfig) => { const DOCS_PATH = useTdevContentPath(siteConfig, 'docs'); const BLOG_PATH = useTdevContentPath(siteConfig, 'blog'); - console.log('RUNNING BUILD WITH DOCS PATH:', DOCS_PATH); //await packageDocsSync('packages', `${DOCS_PATH}/packages`); diff --git a/src/siteConfig/withSiteConfig.ts b/src/siteConfig/withSiteConfig.ts new file mode 100644 index 000000000..7885bd33b --- /dev/null +++ b/src/siteConfig/withSiteConfig.ts @@ -0,0 +1,16 @@ +import type { SiteConfig } from './siteConfig'; +import path from 'path'; + +export const withSiteConfig = async (): Promise => { + if (process.env.SITE_CONFIG_PATH) { + console.log(`Using site config from ${process.env.SITE_CONFIG_PATH}`); + const pathToConfig = path.resolve(process.cwd(), process.env.SITE_CONFIG_PATH); + const getConfig = await import(pathToConfig).then((mod) => mod.default); + return getConfig(); + } else { + console.log(`Using site config from default './siteConfig'`); + const pathToConfig = path.resolve(process.cwd(), 'siteConfig'); + const getConfig = await import(pathToConfig).then((mod) => mod.default); + return getConfig(); + } +}; diff --git a/updateSync/packageDocsSync/preBuild.ts b/updateSync/packageDocsSync/preBuild.ts new file mode 100644 index 000000000..c160540f4 --- /dev/null +++ b/updateSync/packageDocsSync/preBuild.ts @@ -0,0 +1,20 @@ +require('dotenv').config(); +import path from 'path'; +import minimist from 'minimist'; +const argv = minimist(process.argv.slice(2), { + string: ['src', 'dest'], + alias: { src: 'packages', dest: 'out' }, + default: { + src: 'packages' + } +}); + +const main = async () => { + // const DOCS_PATH = useTdevContentPath(siteConfig, 'docs'); + // await packageDocsSync(PACKAGES_DIR, DEST_ROOT); +}; + +main().catch((err) => { + console.error('Error in docs watcher:', err); + process.exit(1); +}); diff --git a/updateSync/packageDocsSync/watch.ts b/updateSync/packageDocsSync/watch.ts index 835306c83..ebb6082a8 100644 --- a/updateSync/packageDocsSync/watch.ts +++ b/updateSync/packageDocsSync/watch.ts @@ -2,6 +2,7 @@ import chokidar from 'chokidar'; import minimist from 'minimist'; import path from 'path'; import { getDebouncedSyncer, packageInfo } from './actions'; +import packageDocsSync from '.'; const argv = minimist(process.argv.slice(2), { string: ['src', 'dest'], @@ -18,8 +19,8 @@ const watcher = chokidar.watch(PACKAGES_DIR, { ignoreInitial: true, persistent: const main = async () => { const { syncQueue, syncDebounced } = await getDebouncedSyncer(PACKAGES_DIR, DEST_ROOT); - const NODE_MODULES_TEST = /node_modules/; + await packageDocsSync(PACKAGES_DIR, DEST_ROOT); watcher .on('all', async (_event, filePath) => { From cdd17c90c3013174f243a9fefb8fe252ead36219 Mon Sep 17 00:00:00 2001 From: bh0fer Date: Sat, 27 Dec 2025 23:19:06 +0100 Subject: [PATCH 6/9] finalize prebuild step --- docusaurus.config.ts | 2 +- package.json | 1 + updateSync/packageDocsSync/index.ts | 6 +++--- updateSync/packageDocsSync/preBuild.ts | 10 ++++++++-- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/docusaurus.config.ts b/docusaurus.config.ts index 07520c3a5..785911705 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -23,6 +23,7 @@ import { taskStateOverview } from './src/siteConfig/navbarItems'; import { applyTransformers } from './src/siteConfig/transformers'; +import { withSiteConfig } from './src/siteConfig/withSiteConfig'; import { sassPluginConfig, dynamicRouterPluginConfig, @@ -40,7 +41,6 @@ import { } from './src/siteConfig/markdownPluginConfigs'; import { remarkPdfPluginConfig } from '@tdev/remark-pdf'; import { GlobExcludeDefault } from '@docusaurus/utils'; -import { withSiteConfig } from '@tdev/siteConfig/withSiteConfig'; const BUILD_LOCATION = __dirname; const GIT_COMMIT_SHA = process.env.GITHUB_SHA || Math.random().toString(36).substring(7); diff --git a/package.json b/package.json index 10fe4f21f..f412f0606 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "scripts": { "docusaurus": "docusaurus", "start": "concurrently --raw --kill-others 'docusaurus start' 'ts-node updateSync/packageDocsSync/watch.ts --src packages --dest tdev-website/docs/packages'", + "prebuild": "ts-node updateSync/packageDocsSync/preBuild.ts --src packages --dest tdev-website/docs/packages", "build": "docusaurus build", "swizzle": "docusaurus swizzle", "deploy": "docusaurus deploy", diff --git a/updateSync/packageDocsSync/index.ts b/updateSync/packageDocsSync/index.ts index f00b628dc..9f34f1ce5 100644 --- a/updateSync/packageDocsSync/index.ts +++ b/updateSync/packageDocsSync/index.ts @@ -30,7 +30,7 @@ const packageDocsSync = async (packageDir: string, destDir: string) => { const categoryFileDest = path.join(destPath, org, `_category_${ext}`); await fs.mkdir(path.dirname(categoryFileDest), { recursive: true }); await fs.copyFile(categoryFileSrc, categoryFileDest); - return `Copied ${org} _category_ file`; + return `✅ Copied ${org} _category_ file`; } } catch {} } @@ -44,12 +44,12 @@ const packageDocsSync = async (packageDir: string, destDir: string) => { const categoryFileDest = path.join(destPath, `_category_${ext}`); await fs.mkdir(path.dirname(categoryFileDest), { recursive: true }); await fs.copyFile(categoryFileSrc, categoryFileDest); - return 'Copied root _category_ file'; + return '✅ Copied root _category_ file'; } } catch {} }) ]); - console.log(result.join('\n')); + console.log(result.filter(Boolean).join('\n')); return result; }; diff --git a/updateSync/packageDocsSync/preBuild.ts b/updateSync/packageDocsSync/preBuild.ts index c160540f4..21d47c076 100644 --- a/updateSync/packageDocsSync/preBuild.ts +++ b/updateSync/packageDocsSync/preBuild.ts @@ -1,6 +1,7 @@ require('dotenv').config(); import path from 'path'; import minimist from 'minimist'; +import packageDocsSync from '.'; const argv = minimist(process.argv.slice(2), { string: ['src', 'dest'], alias: { src: 'packages', dest: 'out' }, @@ -8,10 +9,15 @@ const argv = minimist(process.argv.slice(2), { src: 'packages' } }); +const CWD = process.cwd(); +const { src, dest } = argv; + +const PACKAGES_DIR = path.resolve(CWD, src); +const DEST_ROOT = path.resolve(CWD, dest); const main = async () => { - // const DOCS_PATH = useTdevContentPath(siteConfig, 'docs'); - // await packageDocsSync(PACKAGES_DIR, DEST_ROOT); + await packageDocsSync(PACKAGES_DIR, DEST_ROOT); + console.log('✅ Pre-build docs sync completed.'); }; main().catch((err) => { From e9a7528eddde2ab2a7b917af9572e62e5de6dd92 Mon Sep 17 00:00:00 2001 From: bh0fer Date: Sat, 27 Dec 2025 23:50:20 +0100 Subject: [PATCH 7/9] ensure categories are updated in dev mode too --- packages/README.mdx | 5 --- updateSync/packageDocsSync/actions.ts | 53 ++++++++++++++++++++------ updateSync/packageDocsSync/index.ts | 29 ++------------ updateSync/packageDocsSync/preBuild.ts | 1 - updateSync/packageDocsSync/watch.ts | 15 +++++--- 5 files changed, 53 insertions(+), 50 deletions(-) delete mode 100644 packages/README.mdx diff --git a/packages/README.mdx b/packages/README.mdx deleted file mode 100644 index f68651aa2..000000000 --- a/packages/README.mdx +++ /dev/null @@ -1,5 +0,0 @@ ---- -page_id: a67a8811-3aac-47de-9265-68583cd9f80e ---- - -# Packages \ No newline at end of file diff --git a/updateSync/packageDocsSync/actions.ts b/updateSync/packageDocsSync/actions.ts index 37db9e6e0..00d8eb6f2 100644 --- a/updateSync/packageDocsSync/actions.ts +++ b/updateSync/packageDocsSync/actions.ts @@ -47,26 +47,55 @@ const resolveDir = (pkgPath: string, ...parts: string[]): string => { return path.resolve(CWD, pkgPath, ...parts); }; +const CATEGORY_MATCHER = /^_category_\.(json|ya?ml)$/; +export const categoryFileLocation = (filePath: string, packageDir: string): string | null => { + if (!filePath.startsWith(packageDir)) { + return null; + } + const relPath = path.relative(packageDir, filePath); + const parts = relPath.split(path.sep); + if (parts.length === 1 && CATEGORY_MATCHER.test(parts[0])) { + return ''; + } + if (parts.length === 2 && CATEGORY_MATCHER.test(parts[1])) { + return parts[0]; + } + return null; +}; + export const packageInfo = (filePath: string, packageDir: string): PackageInfo | null => { if (!filePath.startsWith(packageDir)) { return null; } const relPath = path.relative(packageDir, filePath); const parts = relPath.split(path.sep); - if (parts.length === 0) { + if (parts.length < 3) { return null; } - if (parts.length >= 2) { - const pkgInfo: PackageInfo = { - packageDir, - org: parts[0], - package: parts[1] - }; - const relativeSubPath = parts.slice(2).join(path.sep); - if (relativeSubPath.length > 0) { - pkgInfo.relativeSubPath = relativeSubPath; - } - return pkgInfo; + const pkgInfo: PackageInfo = { + packageDir, + org: parts[0], + package: parts[1] + }; + const relativeSubPath = parts.slice(2).join(path.sep); + if (relativeSubPath.length > 0) { + pkgInfo.relativeSubPath = relativeSubPath; + } + return pkgInfo; +}; + +export const syncCategoryFile = async (srcFolder: string, destFolder: string) => { + for (const ext of ['.json', '.yaml', '.yml']) { + const categoryFileSrc = path.join(srcFolder, `_category_${ext}`); + try { + const stat = await fs.stat(categoryFileSrc); + if (stat.isFile()) { + const categoryFileDest = path.join(destFolder, `_category_${ext}`); + await fs.mkdir(path.dirname(categoryFileDest), { recursive: true }); + await fs.copyFile(categoryFileSrc, categoryFileDest); + return `✅ Copied ${destFolder}/_category_${ext}.`; + } + } catch {} } return null; }; diff --git a/updateSync/packageDocsSync/index.ts b/updateSync/packageDocsSync/index.ts index 9f34f1ce5..bb675461f 100644 --- a/updateSync/packageDocsSync/index.ts +++ b/updateSync/packageDocsSync/index.ts @@ -1,5 +1,5 @@ import fs from 'fs/promises'; -import { getPackageDocsConfigs, syncDocsFolder } from './actions'; +import { getPackageDocsConfigs, syncCategoryFile, syncDocsFolder } from './actions'; import path from 'path'; const packageDocsSync = async (packageDir: string, destDir: string) => { @@ -22,32 +22,9 @@ const packageDocsSync = async (packageDir: string, destDir: string) => { }), ...orgs.map(async (org) => { const orgSrc = path.join(srcPath, org); - for (const ext of ['.json', '.yaml', '.yml']) { - const categoryFileSrc = path.join(orgSrc, `_category_${ext}`); - try { - const stat = await fs.stat(categoryFileSrc); - if (stat.isFile()) { - const categoryFileDest = path.join(destPath, org, `_category_${ext}`); - await fs.mkdir(path.dirname(categoryFileDest), { recursive: true }); - await fs.copyFile(categoryFileSrc, categoryFileDest); - return `✅ Copied ${org} _category_ file`; - } - } catch {} - } + return syncCategoryFile(orgSrc, path.join(destPath, org)); }), - ...['.json', '.yaml', '.yml'].map(async (ext) => { - const orgSrc = path.join(srcPath); - const categoryFileSrc = path.join(orgSrc, `_category_${ext}`); - try { - const stat = await fs.stat(categoryFileSrc); - if (stat.isFile()) { - const categoryFileDest = path.join(destPath, `_category_${ext}`); - await fs.mkdir(path.dirname(categoryFileDest), { recursive: true }); - await fs.copyFile(categoryFileSrc, categoryFileDest); - return '✅ Copied root _category_ file'; - } - } catch {} - }) + syncCategoryFile(srcPath, destPath) ]); console.log(result.filter(Boolean).join('\n')); return result; diff --git a/updateSync/packageDocsSync/preBuild.ts b/updateSync/packageDocsSync/preBuild.ts index 21d47c076..a938dc7f3 100644 --- a/updateSync/packageDocsSync/preBuild.ts +++ b/updateSync/packageDocsSync/preBuild.ts @@ -1,4 +1,3 @@ -require('dotenv').config(); import path from 'path'; import minimist from 'minimist'; import packageDocsSync from '.'; diff --git a/updateSync/packageDocsSync/watch.ts b/updateSync/packageDocsSync/watch.ts index ebb6082a8..c9a9faef3 100644 --- a/updateSync/packageDocsSync/watch.ts +++ b/updateSync/packageDocsSync/watch.ts @@ -1,7 +1,7 @@ import chokidar from 'chokidar'; import minimist from 'minimist'; import path from 'path'; -import { getDebouncedSyncer, packageInfo } from './actions'; +import { getDebouncedSyncer, categoryFileLocation, packageInfo, syncCategoryFile } from './actions'; import packageDocsSync from '.'; const argv = minimist(process.argv.slice(2), { @@ -28,12 +28,15 @@ const main = async () => { return null; } const pkgInfo = packageInfo(filePath, PACKAGES_DIR); - if (pkgInfo === null) { - return null; + if (pkgInfo) { + syncQueue.add(pkgInfo); + syncDebounced(); + } else { + const location = categoryFileLocation(filePath, PACKAGES_DIR); + if (location !== null) { + return syncCategoryFile(PACKAGES_DIR, path.join(DEST_ROOT, location)); + } } - - syncQueue.add(pkgInfo); - await syncDebounced(); }) .on('ready', () => { console.log('Watching for docs changes in packages...'); From 13d856e5cba7daf0b217be1360a851e7578190a6 Mon Sep 17 00:00:00 2001 From: bh0fer Date: Sun, 28 Dec 2025 00:32:20 +0100 Subject: [PATCH 8/9] add hints when creating new packages docs about frontmatter --- docusaurus.config.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docusaurus.config.ts b/docusaurus.config.ts index 785911705..a737dc21d 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -1,4 +1,5 @@ require('dotenv').config(); +import logger from '@docusaurus/logger'; import type { EditThisPageOption, ShowEditThisPage, @@ -230,6 +231,12 @@ const docusaurusConfig = withSiteConfig().then(async (siteConfig) => { if (needsRewrite) { await fs.writeFile(params.filePath, matter.stringify(params.fileContent, result.frontMatter), { encoding: 'utf-8' + }).catch((e) => { + if (e.code === 'EACCES') { + const parts = params.filePath.split(path.sep).slice(-3); + logger.warn(`Could not rewrite frontmatter due to insufficient file permissions. Did you create a new file in a subfolder of ./packages/${parts.slice(0, 2).join('/')} ?`); + logger.info(`Make sure to add the following frontmatter manually to the head of "${parts.join(path.sep)}":\n\n${matter.stringify('', result.frontMatter)}`); + } }); } } From 2b8ca2a6b9b7340cfd40561e268abe58d2fc62ab Mon Sep 17 00:00:00 2001 From: bh0fer Date: Sun, 28 Dec 2025 00:34:44 +0100 Subject: [PATCH 9/9] format --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f412f0606..a4d51822c 100644 --- a/package.json +++ b/package.json @@ -138,4 +138,4 @@ "engines": { "node": ">=22.15" } -} \ No newline at end of file +}