From f93b2cc04d5f4462c6b7f2047d7f56ff29247092 Mon Sep 17 00:00:00 2001 From: bh0fer Date: Tue, 30 Jun 2026 16:04:45 +0200 Subject: [PATCH 01/13] feature: internalize material syncer --- material.config.yaml | 1 + materialSync/README.md | 25 ++++ materialSync/add_class.ts | 86 +++++++++++++ materialSync/material_add.ts | 86 +++++++++++++ materialSync/material_cleanup.ts | 88 ++++++++++++++ materialSync/material_helpers.ts | 120 ++++++++++++++++++ materialSync/material_remove.ts | 106 ++++++++++++++++ materialSync/material_restore.ts | 38 ++++++ materialSync/material_sync.ts | 202 +++++++++++++++++++++++++++++++ materialSync/package.json | 28 +++++ materialSync/tsconfig.json | 16 +++ package.json | 13 +- yarn.lock | 197 +++++++++++++++++++++++++++++- 13 files changed, 1003 insertions(+), 3 deletions(-) create mode 100644 material.config.yaml create mode 100644 materialSync/README.md create mode 100644 materialSync/add_class.ts create mode 100644 materialSync/material_add.ts create mode 100644 materialSync/material_cleanup.ts create mode 100644 materialSync/material_helpers.ts create mode 100644 materialSync/material_remove.ts create mode 100644 materialSync/material_restore.ts create mode 100644 materialSync/material_sync.ts create mode 100644 materialSync/package.json create mode 100644 materialSync/tsconfig.json diff --git a/material.config.yaml b/material.config.yaml new file mode 100644 index 000000000..defaf830c --- /dev/null +++ b/material.config.yaml @@ -0,0 +1 @@ +pages: [] diff --git a/materialSync/README.md b/materialSync/README.md new file mode 100644 index 000000000..d61b2c2a3 --- /dev/null +++ b/materialSync/README.md @@ -0,0 +1,25 @@ +# Material Sync + +TypeScript-based scripts for synchronizing teaching materials and documentation versions. + +## Scripts + +- `sync` - Sync materials according to `material.config.yaml` +- `add` - Add new material to `material.config.yaml` +- `remove` - Remove material from `material.config.yaml` +- `cleanup` - Cleanup all versioned docs - enhances dev performance. + +## Configuration + +Configuration is defined in `material.config.yaml`. Each class version has a list of source folders to sync with their destination paths and ignore patterns. + +## Usage + +```bash +# Run from project root +yarn workspace @tdev/material-sync sync + +# Or run directly from materialSync folder +cd materialSync +yarn sync +``` diff --git a/materialSync/add_class.ts b/materialSync/add_class.ts new file mode 100644 index 000000000..b92f760f1 --- /dev/null +++ b/materialSync/add_class.ts @@ -0,0 +1,86 @@ +import path from 'path'; +import fs from 'fs/promises'; +import { loadMaterialConfig, pathExists, saveMaterialConfig } from './material_helpers'; +import minimist from 'minimist'; +import { exit } from 'process'; +import crypto from 'node:crypto'; + +const repoRoot = path.resolve(__dirname, '..'); +process.chdir(repoRoot); + +const configs = loadMaterialConfig(); +const currentKlasses = Object.keys(configs); +const argv = minimist(process.argv.slice(2)); + +if (argv.help) { + console.log(` +yarn run add_class [className] + +examples: + +yarn run addClass 30Gx // adds a new class to 'versioned_docs?, 'versioned_sidebar' and to 'material.config.yaml' +yarn run addClass 30Ga,30Gx +yarn run addClass "30Ga, 30Gx" +`); + exit(0); +} + +const newKlasses = (argv._[0] ?? '') + .split(',') + .map((k) => k.trim()) + .filter((k) => k && !currentKlasses.includes(k)) + .sort(); + +const homeTemplate = (klass: string): string => { + return `--- +sidebar_position: 0.1 +sidebar_label: ${klass} +page_id: ${crypto.randomUUID()} +--- + +# ${klass} +`; +}; + +const main = async (): Promise => { + const versionedSidebarsPath = path.join(repoRoot, 'versioned_sidebars'); + if (!(await pathExists(versionedSidebarsPath))) { + await fs.mkdir(versionedSidebarsPath, { recursive: true }); + } + + for (const klass of newKlasses) { + const versionedDocsPath = path.join(repoRoot, 'versioned_docs', `version-${klass}`); + if (!(await pathExists(versionedDocsPath))) { + await fs.mkdir(versionedDocsPath, { recursive: true }); + } + const sidebarPath = path.join(versionedSidebarsPath, `version-${klass}-sidebars.json`); + if (!(await pathExists(sidebarPath))) { + await fs.writeFile( + sidebarPath, + JSON.stringify({ sidebar: [{ type: 'autogenerated', dirName: '.' }] }, null, 2) + ); + } + const homePath = path.join(versionedDocsPath, 'home.mdx'); + if (!(await pathExists(homePath))) { + await fs.writeFile(homePath, homeTemplate(klass)); + } + configs[klass] = []; + } + const currentVersions = await fs + .readFile(path.join(repoRoot, 'versions.json'), 'utf-8') + .then((data) => JSON.parse(data) as string[]); + const newVersions = [...new Set([...currentVersions, ...newKlasses].sort())]; + await fs.writeFile(path.join(repoRoot, 'versions.json'), JSON.stringify(newVersions, null, 2)); + saveMaterialConfig(configs); + + console.log( + '✅ Added new classes:', + newKlasses.join(', '), + 'Edit website/components/HompageCourses.tsx accordingly' + ); +}; + +main().catch((e: Error) => { + console.error(e); + process.exit(1); +}); diff --git a/materialSync/material_add.ts b/materialSync/material_add.ts new file mode 100644 index 000000000..4d4bcb680 --- /dev/null +++ b/materialSync/material_add.ts @@ -0,0 +1,86 @@ +import fs from 'fs'; +import path from 'path'; +import { exit } from 'process'; +import minimist from 'minimist'; +import { loadMaterialConfig, resolveMaterialConfig, saveMaterialConfig } from './material_helpers'; + +const repoRoot = path.resolve(__dirname, '..'); +process.chdir(repoRoot); + +const configs = loadMaterialConfig(); +const argv = minimist(process.argv.slice(2)); + +if (argv.help) { + console.log(` +yarn run add [source] [[--to="v1,v2"]] [[--as="destination-name"]] [[--ignore="file1,file2"]] + +examples: + +yarn run add docs/byod-basics/v24/ --to="24a,24b" // --> adds /byod-basics/v24 to 24a & 24b +yarn run add docs/byod-basics/v24/ --to="24a,24b" --as="My-Material" // --> adds /byod-basics to 24a & 24b +yarn run add byod-basics/v24/ --to="24a,24b" --as="My-Material" // same as above +yarn run add byod-basics/v24 --to="24a,24b" --as="My-Material" // same as above +yarn run add docs/byod-basics/v24/ --to="24a,24b" --as="My-Material" --ignore="_category_.json,*.txt" +`); + exit(0); +} + +const DOC_PATHS = ['docs/', 'src/pages/', 'blog/']; + +const docBasePath = (src: string): string => { + return DOC_PATHS.find((p) => src.startsWith(p)) || DOC_PATHS[0]; +}; + +/** + * Get path relative to doc base path + */ +const relative2Doc = (p: string): string => { + const base = docBasePath(p); + return base ? p.slice(base.length) : p; +}; + +let src: string = argv._[0]; + +const pathStart = DOC_PATHS.find((p) => src.startsWith(p)); + +if (!pathStart && !src.startsWith('/')) { + src = `${DOC_PATHS[0]}${src}`; +} + +const isDir = fs.lstatSync(src).isDirectory(); +if (isDir && !src.endsWith('/')) { + src = src + '/'; +} + +const klassen = argv.to ? (argv.to as string).split(',') : Object.keys(configs); +const to = argv.as || argv.name || relative2Doc(src); +let ignore: string[] = []; + +if (argv.ignore) { + ignore = (argv.ignore as string) + .split(',') + .map((p) => p.replace(/^\/+/, '')) // remove '/' at the start + .map((p) => p.replace(src, '')); // remove relative path if present +} + +klassen.forEach((klass) => { + if (!Object.keys(configs).includes(klass)) { + console.log(`⚠️ + Klasse not found, skipping: ${klass} + `); + return; + } + configs[klass] = configs[klass].filter((_config) => { + const config = resolveMaterialConfig(klass, _config); + if (config.from === src) { + console.log('ℹ️ Modify old source: ', config.from); + return false; + } + return true; + }); + + const toPath = `versioned_docs/version-${klass}/${to}`; + configs[klass].push({ from: src, to: toPath, ignore: ignore }); +}); + +saveMaterialConfig(configs); diff --git a/materialSync/material_cleanup.ts b/materialSync/material_cleanup.ts new file mode 100644 index 000000000..4297556cc --- /dev/null +++ b/materialSync/material_cleanup.ts @@ -0,0 +1,88 @@ +import fs from 'fs'; +import path from 'path'; +import minimist from 'minimist'; +import { loadMaterialConfig, resolveMaterialConfig } from './material_helpers'; + +const repoRoot = path.resolve(__dirname, '..'); +process.chdir(repoRoot); + +const configs = loadMaterialConfig(); +const argv = minimist(process.argv.slice(2)); + +if (argv.help) { + console.log(` +yarn run cleanup + +examples: + +yarn run cleanup +`); + process.exit(0); +} + +const klassen = Object.keys(configs); + +fs.rmSync('src/pages/secure', { recursive: true, force: true }); +fs.rmSync('static/secure', { recursive: true, force: true }); + +klassen.forEach((klass) => { + const config = configs[klass]; + const tmp_dir = `versioned_docs/version-${klass}/.tmp`; + const copyBack: string[] = []; + + fs.mkdirSync(tmp_dir, { recursive: true }); + + config.forEach((_config) => { + const config = resolveMaterialConfig(klass, _config); + const toPath = config?.to || `versioned_docs/version-${klass}/${config ? '' : _config}`; + const ignoreList = config?.ignore || []; + + if (ignoreList.length > 0) { + fs.mkdirSync(`${tmp_dir}/${toPath}`, { recursive: true }); + } + + ignoreList.forEach((keep) => { + if (fs.existsSync(`${toPath}/${keep}`)) { + const bkpLocation = `${tmp_dir}/${toPath}/${keep}`; + if (!fs.existsSync(path.dirname(bkpLocation))) { + fs.mkdirSync(path.dirname(bkpLocation), { recursive: true }); + } + fs.copyFileSync(`${toPath}/${keep}`, `${tmp_dir}/${toPath}/${keep}`); + } + copyBack.push(`${toPath}/${keep}`); + }); + + if (fs.existsSync(toPath)) { + let parent = path.dirname(toPath); + if (fs.lstatSync(toPath).isDirectory()) { + fs.rmSync(toPath, { recursive: true, force: true }); + } else { + fs.unlinkSync(toPath); + const categoryPath = path.join(path.dirname(toPath), '_category_.json'); + if (config?.open) { + console.log(categoryPath, fs.existsSync(categoryPath)); + } + if (config?.open && fs.existsSync(categoryPath)) { + console.log('REMOVE CAT', categoryPath); + fs.unlinkSync(categoryPath); + } + } + while (fs.readdirSync(parent).length === 0) { + fs.rmSync(parent, { recursive: true, force: true }); + parent = path.dirname(parent); + } + } + }); + + copyBack.forEach((f) => { + if (!fs.existsSync(`${tmp_dir}/${f}`)) { + return; + } + if (!fs.existsSync(path.dirname(f))) { + fs.mkdirSync(path.dirname(f), { recursive: true }); + } + fs.copyFileSync(`${tmp_dir}/${f}`, f); + }); + + fs.rmSync(tmp_dir, { recursive: true, force: true }); +}); diff --git a/materialSync/material_helpers.ts b/materialSync/material_helpers.ts new file mode 100644 index 000000000..3d8489a5d --- /dev/null +++ b/materialSync/material_helpers.ts @@ -0,0 +1,120 @@ +import fs from 'fs'; +import fsp from 'fs/promises'; +import path from 'path'; +import Rsync from 'rsync'; +import yaml from 'js-yaml'; + +type RsyncInstance = InstanceType; + +type BaseConfig = { + ignore: string[]; + open?: boolean; +}; + +export type NormalizedConfig = BaseConfig & { + from: string; + to: string; +}; + +export type SyncConfig = BaseConfig & + ( + | { + from: string; + } + | { + material?: string; + } + ) & + ( + | { + to: string; + } + | { + section: string; + } + ); + +export interface ConfigType { + [key: string]: SyncConfig[]; +} + +export const CONFIG_NAME = 'material.config.yaml'; + +const materialConfigPath = path.resolve(__dirname, '..', CONFIG_NAME); + +export const resolveMaterialConfig = (klass: string, config: SyncConfig): NormalizedConfig => { + let from: string; + let to: string; + const destinationBase = klass === 'pages' ? 'src/pages/' : `versioned_docs/version-${klass}/`; + if ('material' in config && config.material) { + from = path.join('docs', config.material); + } + if ('from' in config && config.from) { + from = config.from; + } + if ('section' in config && config.section) { + to = path.join(destinationBase, config.section); + } + if ('to' in config && config.to) { + if (config.to.startsWith(destinationBase)) { + to = config.to; + } else { + to = path.join(destinationBase, config.to); + } + } + + return { from: from!, to: to!, ignore: config.ignore, open: config.open }; +}; + +export const loadMaterialConfig = (): ConfigType => { + const source = fs.readFileSync(materialConfigPath, 'utf-8'); + return (yaml.load(source) ?? {}) as ConfigType; +}; + +export const saveMaterialConfig = (config: ConfigType): void => { + fs.writeFileSync( + materialConfigPath, + yaml.dump(config, { + noRefs: true, + lineWidth: -1, + sortKeys: false + }) + ); +}; + +/** + * Ensure rsync sync completes successfully, retrying on failure + */ +export const ensureSync = async (rsync: RsyncInstance, srcPath: string): Promise => { + let success = false; + let attempt = 0; + while (!success) { + const rs = new Promise((resolve) => { + rsync.execute((err: Error | null, code: number, cmd: string) => { + if (!err) { + console.log('✅', srcPath, cmd); + attempt = 0; + resolve(true); + } else { + console.log(`[attempt ${++attempt}]: could not sync ${srcPath} retrying ...`); + console.log(' ', cmd); + console.log(' ', err); + console.log(' ', code); + console.log(''); + resolve(false); + } + }); + }); + success = await rs; + } + return success; +}; + +export const pathExists = async (p: string): Promise => { + try { + await fsp.access(p); + return true; + } catch { + return false; + } +}; diff --git a/materialSync/material_remove.ts b/materialSync/material_remove.ts new file mode 100644 index 000000000..e44b2ac4e --- /dev/null +++ b/materialSync/material_remove.ts @@ -0,0 +1,106 @@ +import fs from 'fs'; +import path from 'path'; +import minimist from 'minimist'; +import { + loadMaterialConfig, + resolveMaterialConfig, + saveMaterialConfig, + SyncConfig +} from './material_helpers'; + +const repoRoot = path.resolve(__dirname, '..'); +process.chdir(repoRoot); + +const configs = loadMaterialConfig(); +const argv = minimist(process.argv.slice(2)); + +if (argv.help) { + console.log(` +yarn run remove [source] [[--from="v1,v2"]] + +examples: + +yarn run remove docs/byod-basics/v24/ --from="24a,24b" +`); + process.exit(0); +} + +const toRemove = argv._; +const klassen = argv.from ? (argv.from as string).split(',') : Object.keys(configs); + +const DOC_PATHS = ['docs/', 'src/pages/', 'news/']; + +const docBasePath = (src: string): string => { + return DOC_PATHS.find((p) => src.startsWith(p)) || DOC_PATHS[0]; +}; + +/** + * Get path relative to doc base path + */ +const relative2Doc = (p: string): string => { + const base = docBasePath(p); + return base ? p.slice(base.length) : p; +}; + +const ensureTrailingSlash = (p: string): string => { + if (typeof p !== 'string') { + return p; + } + if (p.endsWith('/')) { + return p; + } + return `${p}/`; +}; + +klassen.forEach((klass) => { + const klassConfig = configs[klass]; + const keepedFiles: SyncConfig[] = []; + + klassConfig.forEach((_config) => { + const config = resolveMaterialConfig(klass, _config); + const fromRel = relative2Doc(config.from); + const from = `${docBasePath(config.from)}${fromRel}`; + const to = config.to; + let keep = true; + + toRemove.forEach((rmSrc) => { + let toRmSrc = `${docBasePath(rmSrc)}${relative2Doc(rmSrc)}`; + console.log(config.from, fromRel, docBasePath(rmSrc), from, toRmSrc); + + if (fs.lstatSync(toRmSrc).isDirectory()) { + toRmSrc = ensureTrailingSlash(toRmSrc); + } + + console.log(from, toRmSrc, from === toRmSrc); + + if (from === toRmSrc) { + keep = false; + if (fs.existsSync(to)) { + console.log('- remove', to, 'from', klass); + let parent = path.dirname(to); + if (fs.lstatSync(to).isDirectory()) { + console.log('rm dir', to); + fs.rmSync(to, { recursive: true, force: true }); + } else { + fs.unlinkSync(to); + } + while (fs.readdirSync(parent).length === 0) { + fs.rmSync(parent, { recursive: true, force: true }); + parent = path.dirname(parent); + } + } else { + console.log('- unset', to, 'from', klass); + } + } + }); + + if (keep) { + keepedFiles.push(_config); + } + }); + + configs[klass] = keepedFiles; +}); + +saveMaterialConfig(configs); +console.log('done'); diff --git a/materialSync/material_restore.ts b/materialSync/material_restore.ts new file mode 100644 index 000000000..6c9a0ae3b --- /dev/null +++ b/materialSync/material_restore.ts @@ -0,0 +1,38 @@ +import fs from 'fs'; +import path from 'path'; + +const repoRoot = path.resolve(__dirname, '..'); +process.chdir(repoRoot); + +const main = (): void => { + if (fs.existsSync('_docs') && fs.lstatSync('_docs').isDirectory()) { + console.log('RENAMING docs/ to _docs/'); + if (fs.existsSync('docs')) { + fs.rmSync('docs', { recursive: true }); + } + fs.renameSync('_docs', 'docs'); + } + if (fs.existsSync('_versioned_docs') && fs.lstatSync('_versioned_docs').isDirectory()) { + console.log('RENAMING _versioned_docs/ to versioned_docs/'); + if (fs.existsSync('versioned_docs')) { + fs.rmSync('versioned_docs', { recursive: true }); + } + fs.renameSync('_versioned_docs', 'versioned_docs'); + } + if (fs.existsSync('_versioned_sidebars') && fs.lstatSync('_versioned_sidebars').isDirectory()) { + console.log('RENAMING _versioned_sidebars/ to versioned_sidebars/'); + if (fs.existsSync('versioned_sidebars')) { + fs.rmSync('versioned_sidebars', { recursive: true }); + } + fs.renameSync('_versioned_sidebars', 'versioned_sidebars'); + } + if (fs.existsSync('_versions.json')) { + console.log('RESTORE _versions.json to versions.json'); + if (fs.existsSync('versions.json')) { + fs.rmSync('versions.json', { recursive: true }); + } + fs.renameSync('_versions.json', 'versions.json'); + } +}; + +main(); diff --git a/materialSync/material_sync.ts b/materialSync/material_sync.ts new file mode 100644 index 000000000..93ea41c28 --- /dev/null +++ b/materialSync/material_sync.ts @@ -0,0 +1,202 @@ +import fs from 'fs/promises'; +import path from 'path'; +import Rsync from 'rsync'; +import { + ConfigType, + ensureSync, + loadMaterialConfig, + pathExists, + resolveMaterialConfig +} from './material_helpers'; +const repoRoot = path.resolve(__dirname, '..'); +process.chdir(repoRoot); + +const typedConfig: ConfigType = loadMaterialConfig(); + +const DOC_PATHS = ['docs/', 'src/pages/', 'news/']; + +const docBasePath = (src: string): string => { + return DOC_PATHS.find((p) => src.startsWith(p)) || DOC_PATHS[0]; +}; + +/** + * Recursively find markdown template files (starting with _) + */ +const findMdTemplate = async (src: string): Promise => { + const mdFiles: string[] = []; + const srcStat = await fs.lstat(src); + if (srcStat.isDirectory()) { + const files = await fs.readdir(src); + for (const file of files) { + const fname = path.join(src, file); + const fileStat = await fs.lstat(fname); + if (fileStat.isDirectory()) { + mdFiles.push(...(await findMdTemplate(fname))); + } else if ((file.endsWith('.md') || file.endsWith('.mdx')) && file.startsWith('_')) { + mdFiles.push(fname); + } + } + } else { + if ((src.endsWith('.md') || src.endsWith('.mdx')) && src.startsWith('_')) { + mdFiles.push(src); + } + } + return mdFiles; +}; + +/** + * Get path relative to doc base path + */ +const relative2Doc = (p: string): string => { + const base = docBasePath(p); + return base ? p.slice(base.length) : p; +}; + +const ensureStartingSlash = (p: string): string => { + if (typeof p !== 'string') { + return p; + } + if (p.startsWith('/')) { + return p; + } + return `/${p}`; +}; + +const ensureTrailingSlash = (p: string): string => { + if (typeof p !== 'string') { + return p; + } + if (p.endsWith('/')) { + return p; + } + return `${p}/`; +}; + +const main = async (): Promise => { + if (process.env.WITHOUT_DOCS) { + /** + * move docs/ to _docs/ and make sure docusaurus can still build the site. + * Can be undone by running the restore script. + */ + console.log('RENAMING docs/ to _docs/'); + await fs.rename('docs', '_docs'); + await fs.mkdir('docs'); + await fs.cp('_docs/home.md', 'docs/home.md'); + /** copy all markdown-templates - otherwise some pages might fail */ + const templates = await findMdTemplate(path.join(__dirname, '../_docs')); + await Promise.all(templates.map((file) => fs.cp(file, file.replace('/_docs/', '/docs/')))); + } + if (process.env.DOCS_ONLY) { + /* Build only the docs - can be undone by running the restore script */ + if (await pathExists('versioned_docs')) { + console.log('RENAMING versioned_docs/ to _versioned_docs/'); + await fs.rename('versioned_docs', '_versioned_docs'); + await fs.mkdir('versioned_docs'); + } + if (await pathExists('versioned_sidebars')) { + console.log('RENAMING versioned_sidebars/ to _versioned_sidebars/'); + await fs.rename('versioned_sidebars', '_versioned_sidebars'); + await fs.mkdir('versioned_sidebars'); + } + if (await pathExists('versions.json')) { + console.log('RENAMING versions.json to _versions.json'); + await fs.rename('versions.json', '_versions.json'); + await fs.writeFile('versions.json', '[\n "current"\n]'); + } + } + if (await pathExists('CNAME')) { + await fs.cp('CNAME', 'static/CNAME'); + } + for (const klass of Object.keys(typedConfig)) { + const config = typedConfig[klass]; + const gitignore: string[] = []; + const classDir = klass === 'pages' ? 'src/pages/' : `versioned_docs/version-${klass}/`; + for (const _config of config) { + const config = resolveMaterialConfig(klass, _config); + const ignore: string[] = []; + ignore.push(...(config.ignore || [])); + + let srcPath = config.from; + + if (process.env.WITHOUT_DOCS && config.from.startsWith('docs/')) { + srcPath = `_${srcPath}`; + } + + const isDir = (await fs.lstat(srcPath)).isDirectory(); + if (isDir) { + srcPath = ensureTrailingSlash(srcPath); + } + + const destParent = path.dirname(config.to); + if (!(await pathExists(destParent))) { + await fs.mkdir(destParent, { recursive: true }); + } + + if (isDir) { + const sanitizedClassDir = ensureTrailingSlash(config.to.replace(classDir, '')); + gitignore.push(`${sanitizedClassDir}*`); + const rsync = new Rsync() + .flags('v') + .source(srcPath) + .destination(config.to) + .archive() + .delete(); + if (ignore.length > 0) { + rsync.exclude(ignore.map((i) => ensureStartingSlash(i))); + for (const ifile of ignore) { + const opath = `${srcPath}${ifile}`; + const ipath = `${sanitizedClassDir}${ifile}`; + if (!(await pathExists(opath))) { + console.warn( + `⚠️ [ignore] ${klass}->${srcPath}: ignored "${ifile}" does not exist` + ); + return; + } + if ((await fs.lstat(opath)).isDirectory()) { + gitignore.push(`!${ensureTrailingSlash(ipath)}`); + } else { + gitignore.push(`!${ipath}`); + } + } + } + rsync.exclude(['.sync.*', '*.nosync.*']); + console.log('SYNC', config.to, srcPath); + await ensureSync(rsync, srcPath); + } else { + await fs.copyFile(srcPath, config.to); + gitignore.push(config.to.replace(classDir, '')); + } + + if (config.open) { + const folder = isDir ? config.to : destParent; + try { + await fs.mkdir(folder, { recursive: true }); + } catch (e) { + console.log(e); + } + const categoryPath = path.join(folder, '_category_.json'); + console.log('---------- CAT', categoryPath); + gitignore.push(categoryPath.replace(classDir, '')); + let category: Record = { + collapsible: true, + collapsed: false, + className: 'library-item marked' + }; + if (await pathExists(categoryPath)) { + category = JSON.parse(await fs.readFile(categoryPath, 'utf-8')); + category.collapsed = false; + category.collapsible = true; + category.className = 'library-item marked'; + } + await fs.writeFile(categoryPath, JSON.stringify(category, undefined, 2) + '\n'); + } + + await fs.writeFile(`${classDir}.gitignore`, gitignore.join('\n')); + } + } +}; + +main().catch((e: Error) => { + console.error(e); + process.exit(1); +}); diff --git a/materialSync/package.json b/materialSync/package.json new file mode 100644 index 000000000..95e464548 --- /dev/null +++ b/materialSync/package.json @@ -0,0 +1,28 @@ +{ + "name": "@tdev/material-sync", + "version": "1.0.0", + "description": "Material synchronization scripts for teaching documentation", + "private": true, + "scripts": { + "sync": "tsx material_sync.ts", + "sync:secure": "tsx material_sync_secure.ts", + "add": "tsx material_add.ts", + "remove": "tsx material_remove.ts", + "cleanup": "tsx material_cleanup.ts", + "restore": "tsx material_restore.ts", + "addClass": "tsx add_class.ts" + }, + "dependencies": { + }, + "devDependencies": { + "@types/js-yaml": "^4.0.9", + "@types/minimist": "^1.2.5", + "@types/node": "^22.0.0", + "@types/rsync": "^0.4.36", + "js-yaml": "^4.1.1", + "minimist": "^1.2.8", + "rsync": "^0.6.1", + "tsx": "^4.20.3", + "typescript": "^6.0.2" + } +} diff --git a/materialSync/tsconfig.json b/materialSync/tsconfig.json new file mode 100644 index 000000000..61b50cb68 --- /dev/null +++ b/materialSync/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": ".", + "module": "commonjs", + "declaration": false, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "resolveJsonModule": true, + "moduleResolution": "node" + }, + "include": ["*.ts"], + "exclude": ["node_modules", "dist"] +} diff --git a/package.json b/package.json index 0055768f4..cb4b9c149 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "name": "@tdev/core", "workspaces": [ "packages/**", + "materialSync", "." ], "version": "1.0.0", @@ -9,7 +10,7 @@ "scripts": { "docusaurus": "docusaurus", "start": "concurrently --raw --kill-others 'PACKAGE_SRC=packages PACKAGE_DEST=tdev-website/docs/packages docusaurus start' 'sleep 1s && ts-node --files updateSync/packageDocsSync/watch.ts --src packages --dest tdev-website/docs/packages'", - "prebuild": "ts-node --files updateSync/packageDocsSync/preBuild.ts --src packages --dest tdev-website/docs/packages", + "prebuild": "yarn workspace @tdev/material-sync sync && ts-node --files updateSync/packageDocsSync/preBuild.ts --src packages --dest tdev-website/docs/packages", "build": "docusaurus build", "swizzle": "docusaurus swizzle", "deploy": "docusaurus deploy", @@ -21,7 +22,15 @@ "format": "prettier --write .", "format:check": "prettier --check .", "test": "vitest --coverage", - "updateTdev": "ts-node --files updateSync/updateTdev.ts" + "updateTdev": "ts-node --files updateSync/updateTdev.ts", + "sync": "yarn workspace @tdev/material-sync sync", + "remove": "yarn workspace @tdev/material-sync run remove", + "postremove": "yarn workspace @tdev/material-sync sync", + "precleanup": "yarn workspace @tdev/material-sync restore", + "cleanup": "yarn workspace @tdev/material-sync cleanup", + "add": "yarn workspace @tdev/material-sync run add", + "addClass": "yarn workspace @tdev/material-sync run addClass", + "postadd": "yarn workspace @tdev/material-sync sync" }, "dependencies": { "@docusaurus/core": "^3.10.1", diff --git a/yarn.lock b/yarn.lock index 5c28aabd9..ec5c9e022 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2638,7 +2638,7 @@ resolved "https://registry.yarnpkg.com/@docusaurus/tsconfig/-/tsconfig-3.10.1.tgz#1db31b4a4a5c914bdffa80070a35b6365d34f2e8" integrity sha512-rYvB7yqkdqWIpAbDzQljGfM4cDBkLTbhmagZBEcsyj6oPUsz47lmW2pYdN1j+7sGFgltbAmQH62xfbrij4Eh6Q== -"@docusaurus/types@3.10.0", "@docusaurus/types@^3.10.0": +"@docusaurus/types@3.10.0": version "3.10.0" resolved "https://registry.yarnpkg.com/@docusaurus/types/-/types-3.10.0.tgz#a69232bba74b738fcf4671fd5f0f079366dd3d13" integrity sha512-F0dOt3FOoO20rRaFK7whGFQZ3ggyrWEdQc/c8/UiRuzhtg4y1w9FspXH5zpCT07uMnJKBPGh+qNazbNlCQqvSw== @@ -2894,116 +2894,246 @@ resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz#c7184a326533fcdf1b8ee0733e21c713b975575f" integrity sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ== +"@esbuild/aix-ppc64@0.28.1": + version "0.28.1" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz#7a01a8d2ec2fbb2dac78adad09b0fa781e4082be" + integrity sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ== + "@esbuild/android-arm64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz#09d9b4357780da9ea3a7dfb833a1f1ff439b4052" integrity sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A== +"@esbuild/android-arm64@0.28.1": + version "0.28.1" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz#b540a27d14e4afd058496a4dbec4d3f414db110a" + integrity sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg== + "@esbuild/android-arm@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz#9b04384fb771926dfa6d7ad04324ecb2ab9b2e28" integrity sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg== +"@esbuild/android-arm@0.28.1": + version "0.28.1" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.28.1.tgz#704bd297de6d762de54eabbeafbf55f6756abe2f" + integrity sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ== + "@esbuild/android-x64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz#29918ec2db754cedcb6c1b04de8cd6547af6461e" integrity sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA== +"@esbuild/android-x64@0.28.1": + version "0.28.1" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.28.1.tgz#d1cb166d34b0fbf0fe8ab460a5594f24a378701e" + integrity sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng== + "@esbuild/darwin-arm64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz#e495b539660e51690f3928af50a76fb0a6ccff2a" integrity sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ== +"@esbuild/darwin-arm64@0.28.1": + version "0.28.1" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz#1034b26457fc886368fe61bbd09f653f6afa8e54" + integrity sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q== + "@esbuild/darwin-x64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz#c13838fa57372839abdddc91d71542ceea2e1e22" integrity sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw== +"@esbuild/darwin-x64@0.28.1": + version "0.28.1" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz#65556a432a1e4d72032d8218c1932fcca1a49772" + integrity sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ== + "@esbuild/freebsd-arm64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz#646b989aa20bf89fd071dd5dbfad69a3542e550e" integrity sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g== +"@esbuild/freebsd-arm64@0.28.1": + version "0.28.1" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz#2e61e0592f9030d7e3dae18ee25ebc535918aef6" + integrity sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw== + "@esbuild/freebsd-x64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz#aa615cfc80af954d3458906e38ca22c18cf5c261" integrity sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ== +"@esbuild/freebsd-x64@0.28.1": + version "0.28.1" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz#c95ec289959ef8079c4dca817a1e2c4be66b9bd3" + integrity sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ== + "@esbuild/linux-arm64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz#70ac6fa14f5cb7e1f7f887bcffb680ad09922b5b" integrity sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q== +"@esbuild/linux-arm64@0.28.1": + version "0.28.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz#40b22175dda06182f3ee8141186c5ff304c4a717" + integrity sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g== + "@esbuild/linux-arm@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz#fc6fd11a8aca56c1f6f3894f2bea0479f8f626b9" integrity sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA== +"@esbuild/linux-arm@0.28.1": + version "0.28.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz#c09a0f67917592ac0de892a9be4d3814debd2a6c" + integrity sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ== + "@esbuild/linux-ia32@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz#3271f53b3f93e3d093d518d1649d6d68d346ede2" integrity sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg== +"@esbuild/linux-ia32@0.28.1": + version "0.28.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz#a580f9c676797833891e519fc7a1337c8afd8db3" + integrity sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w== + "@esbuild/linux-loong64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz#ed62e04238c57026aea831c5a130b73c0f9f26df" integrity sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg== +"@esbuild/linux-loong64@0.28.1": + version "0.28.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz#46452cf321dc7f9e91c2fa780a56bb56e79cd68b" + integrity sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg== + "@esbuild/linux-mips64el@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz#e79b8eb48bf3b106fadec1ac8240fb97b4e64cbe" integrity sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg== +"@esbuild/linux-mips64el@0.28.1": + version "0.28.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz#4211b3184dd6608f53dcb22e39f5d34ee08852c8" + integrity sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ== + "@esbuild/linux-ppc64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz#5f2203860a143b9919d383ef7573521fb154c3e4" integrity sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w== +"@esbuild/linux-ppc64@0.28.1": + version "0.28.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz#697857c2a61cb9b0b6bb6652e40c1dc5e1ca8e5d" + integrity sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ== + "@esbuild/linux-riscv64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz#07bcafd99322d5af62f618cb9e6a9b7f4bb825dc" integrity sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA== +"@esbuild/linux-riscv64@0.28.1": + version "0.28.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz#d192943eb146a40ac4c6497d0cf7be35b986bf08" + integrity sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ== + "@esbuild/linux-s390x@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz#b7ccf686751d6a3e44b8627ababc8be3ef62d8de" integrity sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A== +"@esbuild/linux-s390x@0.28.1": + version "0.28.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz#acea0356da0e0ebc08f97cf7b9c2e401e1e648dc" + integrity sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag== + "@esbuild/linux-x64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz#6d8f0c768e070e64309af8004bb94e68ab2bb3b0" integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ== +"@esbuild/linux-x64@0.28.1": + version "0.28.1" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz#6f0c3ce0cb64c534b70c4c45ecb2c16d34e35dfd" + integrity sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA== + +"@esbuild/netbsd-arm64@0.28.1": + version "0.28.1" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz#8bcd77077a0dce3378b574fedb26d2a253b73d36" + integrity sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw== + "@esbuild/netbsd-x64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz#bbe430f60d378ecb88decb219c602667387a6047" integrity sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg== +"@esbuild/netbsd-x64@0.28.1": + version "0.28.1" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz#e7fb2a01e99c830c94e6623cd9fefb4c8fb58347" + integrity sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg== + +"@esbuild/openbsd-arm64@0.28.1": + version "0.28.1" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz#c52909372db8b86e2c55e05a8940033b5660a3b2" + integrity sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q== + "@esbuild/openbsd-x64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz#99d1cf2937279560d2104821f5ccce220cb2af70" integrity sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow== +"@esbuild/openbsd-x64@0.28.1": + version "0.28.1" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz#c427b9be5a64c262ff9a7eb70b5fbbaadf446c6c" + integrity sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw== + +"@esbuild/openharmony-arm64@0.28.1": + version "0.28.1" + resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz#dc9b147baca2e6c4b3c85571741ef4860a489097" + integrity sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg== + "@esbuild/sunos-x64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz#08741512c10d529566baba837b4fe052c8f3487b" integrity sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg== +"@esbuild/sunos-x64@0.28.1": + version "0.28.1" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz#ce866d12df13c15e4c99f073a3d466f6e0649b3a" + integrity sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ== + "@esbuild/win32-arm64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz#675b7385398411240735016144ab2e99a60fc75d" integrity sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A== +"@esbuild/win32-arm64@0.28.1": + version "0.28.1" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz#7468e3692d01d629d5941e5d83817bb80f9e39b4" + integrity sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA== + "@esbuild/win32-ia32@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz#1bfc3ce98aa6ca9a0969e4d2af72144c59c1193b" integrity sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA== +"@esbuild/win32-ia32@0.28.1": + version "0.28.1" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz#a5bc0063fb2bcab6d0ed63f2a1537958bc269ec6" + integrity sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg== + "@esbuild/win32-x64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz#acad351d582d157bb145535db2a6ff53dd514b5c" integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw== +"@esbuild/win32-x64@0.28.1": + version "0.28.1" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz#10064ee44f4347b90c9a02b446bbf80a91632b12" + integrity sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A== + "@excalidraw/excalidraw@^0.18.0": version "0.18.0" resolved "https://registry.yarnpkg.com/@excalidraw/excalidraw/-/excalidraw-0.18.0.tgz#9f818e2df80a8735af54f8cc21da67997785532f" @@ -6420,6 +6550,13 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.45.tgz#2c0fafd78705e7a18b7906b5201a522719dc5190" integrity sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw== +"@types/node@^22.0.0": + version "22.20.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.20.0.tgz#431f5007396bc1a1a47b9c7df60f3e5e0b5b7304" + integrity sha512-QWlFW2wf3nTjC13/DqRnBpR4ZO36VJH/JVBkA/vcnmbTBNQIlnObqyqZE1tUR7+Ni23Lda8R1BxMfbXRpCUx5g== + dependencies: + undici-types "~6.21.0" + "@types/parse-json@^4.0.0": version "4.0.2" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239" @@ -6495,6 +6632,13 @@ resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.2.tgz#ed279a64fa438bb69f2480eda44937912bb7480a" integrity sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow== +"@types/rsync@^0.4.36": + version "0.4.36" + resolved "https://registry.yarnpkg.com/@types/rsync/-/rsync-0.4.36.tgz#97adc8c41d3373ba12ba3a74c4840cfafc583608" + integrity sha512-K2Frb12sSRvSkSTBJzzwgOED1Q2rj4+54zweCN3yCRHprdc5fO18qZm1ojNdO2QwVGo0UYIqpyYPCYSK3O7pBA== + dependencies: + "@types/node" "*" + "@types/sax@^1.2.1": version "1.2.7" resolved "https://registry.yarnpkg.com/@types/sax/-/sax-1.2.7.tgz#ba5fe7df9aa9c89b6dff7688a19023dd2963091d" @@ -9407,6 +9551,38 @@ esbuild@^0.21.3: "@esbuild/win32-ia32" "0.21.5" "@esbuild/win32-x64" "0.21.5" +esbuild@~0.28.0: + version "0.28.1" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.28.1.tgz#ef45b4634c9c9d97a296aea4114a5f9840f95578" + integrity sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw== + optionalDependencies: + "@esbuild/aix-ppc64" "0.28.1" + "@esbuild/android-arm" "0.28.1" + "@esbuild/android-arm64" "0.28.1" + "@esbuild/android-x64" "0.28.1" + "@esbuild/darwin-arm64" "0.28.1" + "@esbuild/darwin-x64" "0.28.1" + "@esbuild/freebsd-arm64" "0.28.1" + "@esbuild/freebsd-x64" "0.28.1" + "@esbuild/linux-arm" "0.28.1" + "@esbuild/linux-arm64" "0.28.1" + "@esbuild/linux-ia32" "0.28.1" + "@esbuild/linux-loong64" "0.28.1" + "@esbuild/linux-mips64el" "0.28.1" + "@esbuild/linux-ppc64" "0.28.1" + "@esbuild/linux-riscv64" "0.28.1" + "@esbuild/linux-s390x" "0.28.1" + "@esbuild/linux-x64" "0.28.1" + "@esbuild/netbsd-arm64" "0.28.1" + "@esbuild/netbsd-x64" "0.28.1" + "@esbuild/openbsd-arm64" "0.28.1" + "@esbuild/openbsd-x64" "0.28.1" + "@esbuild/openharmony-arm64" "0.28.1" + "@esbuild/sunos-x64" "0.28.1" + "@esbuild/win32-arm64" "0.28.1" + "@esbuild/win32-ia32" "0.28.1" + "@esbuild/win32-x64" "0.28.1" + escalade@^3.1.1, escalade@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" @@ -14901,6 +15077,11 @@ rslog@^1.2.3: resolved "https://registry.yarnpkg.com/rslog/-/rslog-1.3.2.tgz#55aa0cc939388487d0d026cf4370fbe6f10436ec" integrity sha512-1YyYXBvN0a2b1MSIDLwDTqqgjDzRKxUg/S/+KO6EAgbtZW1B3fdLHAMhEEtvk1patJYMqcRvlp3HQwnxj7AdGQ== +rsync@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/rsync/-/rsync-0.6.1.tgz#3681a0098bd8750448f8bf9da1fee09f7763742b" + integrity sha512-39HcwWuM67CQ9tHloazShXWUOWa2m3SGqX6XQhQMSj0VCQMkSI9PodoxM7/+hKf2p4v2umbhfoarYqd1gwII/w== + rtlcss@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/rtlcss/-/rtlcss-4.3.0.tgz#f8efd4d5b64f640ec4af8fa25b65bacd9e07cc97" @@ -15898,6 +16079,15 @@ tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.4.0, tslib@^2.6 resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== +tsx@^4.20.3: + version "4.22.4" + resolved "https://registry.yarnpkg.com/tsx/-/tsx-4.22.4.tgz#0ab3b7fb4ec7feeee74e5b1f26337caa71e44700" + integrity sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg== + dependencies: + esbuild "~0.28.0" + optionalDependencies: + fsevents "~2.3.3" + tsyringe@^4.10.0: version "4.10.0" resolved "https://registry.yarnpkg.com/tsyringe/-/tsyringe-4.10.0.tgz#d0c95815d584464214060285eaaadd94aa03299c" @@ -15969,6 +16159,11 @@ ufo@^1.6.3: resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.6.3.tgz#799666e4e88c122a9659805e30b9dc071c3aed4f" integrity sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q== +undici-types@~6.21.0: + version "6.21.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" + integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== + undici-types@~7.19.0: version "7.19.2" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.19.2.tgz#1b67fc26d0f157a0cba3a58a5b5c1e2276b8ba2a" From cd7125057602a7bb9fb2bdf72f1e3c01ad15a92e Mon Sep 17 00:00:00 2001 From: bh0fer Date: Tue, 30 Jun 2026 16:33:33 +0200 Subject: [PATCH 02/13] derive versions from versions.json --- docusaurus.config.ts | 13 +++++++++++++ materialSync/add_class.ts | 15 +++++++-------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/docusaurus.config.ts b/docusaurus.config.ts index 4f089ea69..0ebf2b56e 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -5,6 +5,8 @@ import type { ShowEditThisPage, TdevConfig } from '@tdev/siteConfig/siteConfig'; +import type { VersionOptions } from '@docusaurus/plugin-content-docs'; + import { themes as prismThemes } from 'prism-react-renderer'; import type { Config, OnBrokenMarkdownImagesFunction } from '@docusaurus/types'; import type * as Preset from '@docusaurus/preset-classic'; @@ -98,6 +100,16 @@ const docusaurusConfig = withSiteConfig().then(async (siteConfig) => { loadedPlugins.push(excalidrawPluginConfig); } + const hasVersions = await fs.access(path.join(BUILD_LOCATION, 'versions.json')).then(() => true).catch(() => false); + const DEFAULT_VERSIONS: { [version: string]: VersionOptions } = {} ; + if (hasVersions) { + const docusaurusVersions = await fs.readFile(path.join(BUILD_LOCATION, 'versions.json'), 'utf-8').then((data) => JSON.parse(data) as string[]); + docusaurusVersions.forEach((version) => { + DEFAULT_VERSIONS[version] = { label: version, banner: 'none' }; + }); + DEFAULT_VERSIONS['current'] = { label: 'Material', banner: 'none' } + } + const config: Config = applyTransformers( { @@ -272,6 +284,7 @@ const docusaurusConfig = withSiteConfig().then(async (siteConfig) => { beforeDefaultRemarkPlugins: BEFORE_DEFAULT_REMARK_PLUGINS, ...DEFAULT_ADMONITION_CONFIG, exclude: [...new Set([...GlobExcludeDefault, '**/node_modules/**'])], + versions: Object.keys(DEFAULT_VERSIONS).length > 0 ? DEFAULT_VERSIONS : undefined, ...(siteConfig.docs || {}) } : false, diff --git a/materialSync/add_class.ts b/materialSync/add_class.ts index b92f760f1..c359059d9 100644 --- a/materialSync/add_class.ts +++ b/materialSync/add_class.ts @@ -66,18 +66,17 @@ const main = async (): Promise => { } configs[klass] = []; } - const currentVersions = await fs - .readFile(path.join(repoRoot, 'versions.json'), 'utf-8') - .then((data) => JSON.parse(data) as string[]); + const hasVersions = await pathExists(path.join(repoRoot, 'versions.json')); + const currentVersions = await (hasVersions + ? fs + .readFile(path.join(repoRoot, 'versions.json'), 'utf-8') + .then((data) => JSON.parse(data) as string[]) + : Promise.resolve([])); const newVersions = [...new Set([...currentVersions, ...newKlasses].sort())]; await fs.writeFile(path.join(repoRoot, 'versions.json'), JSON.stringify(newVersions, null, 2)); saveMaterialConfig(configs); - console.log( - '✅ Added new classes:', - newKlasses.join(', '), - 'Edit website/components/HompageCourses.tsx accordingly' - ); + console.log('✅ Added new classes:', newKlasses.join(', '), 'Edit your siteConfig.ts accordingly'); }; main().catch((e: Error) => { From a853d59b570ed82cda72d49c855b45d6bee922b3 Mon Sep 17 00:00:00 2001 From: bh0fer Date: Wed, 1 Jul 2026 12:19:06 +0200 Subject: [PATCH 03/13] add default course list --- src/components/HomepageCourses/index.tsx | 79 +++++++++++++++++++ .../HomepageCourses/styles.module.scss | 6 ++ .../HomepageCourses/useClassVersions.ts | 69 ++++++++++++++++ src/pages/index.module.css | 4 + src/pages/index.tsx | 28 +++++-- 5 files changed, 181 insertions(+), 5 deletions(-) create mode 100644 src/components/HomepageCourses/index.tsx create mode 100644 src/components/HomepageCourses/styles.module.scss create mode 100644 src/components/HomepageCourses/useClassVersions.ts diff --git a/src/components/HomepageCourses/index.tsx b/src/components/HomepageCourses/index.tsx new file mode 100644 index 000000000..7ee3f2301 --- /dev/null +++ b/src/components/HomepageCourses/index.tsx @@ -0,0 +1,79 @@ +import React from 'react'; +import styles from './styles.module.scss'; +import clsx from 'clsx'; +import Link from '@docusaurus/Link'; +import siteConfig from '@generated/docusaurus.config'; +import { useStore } from '@tdev-hooks/useStore'; +import useIsBrowser from '@docusaurus/useIsBrowser'; +import { observer } from 'mobx-react-lite'; +const { DOCS_ONLY } = siteConfig.customFields as { DOCS_ONLY?: boolean }; +import { Course, useClassVersions } from './useClassVersions'; + +// add additional courses here, e.g. for workshops or alumni +// CourseList.push({ title: 'Workshops', classes: ['LPs'] }); +const CourseList: Course[] = []; + +// const CourseComponent = (course: Course) => { +const CourseComponent = ({ course }: { course: Course }) => { + return ( +
+
+

{course.title}

+
+
+ {course.classes.map((cl, idx) => { + const isString = typeof cl === 'string'; + const to = isString ? `${cl}/home` : cl.uri; + const label = isString ? cl : cl.label; + return ( + + {label} + + ); + })} +
+
+ ); +}; + +const HomepageCourses = observer(() => { + const userStore = useStore('userStore'); + const { courseList } = useClassVersions(); + const isBrowser = useIsBrowser(); + + if (!isBrowser) { + return null; + } + + return ( +
+
+
+ {!DOCS_ONLY && ( + <> + {courseList.map((course, idx) => ( + + ))} + {CourseList.map((course, idx) => ( + + ))} + + )} +
+
+ {(DOCS_ONLY || process.env.NODE_ENV === 'development' || userStore.current?.isAdmin) && ( + + )} +
+
+
+ ); +}); + +export default HomepageCourses; diff --git a/src/components/HomepageCourses/styles.module.scss b/src/components/HomepageCourses/styles.module.scss new file mode 100644 index 000000000..e25342fad --- /dev/null +++ b/src/components/HomepageCourses/styles.module.scss @@ -0,0 +1,6 @@ +.features { + display: flex; + align-items: center; + padding: 2rem 0; + width: 100%; +} diff --git a/src/components/HomepageCourses/useClassVersions.ts b/src/components/HomepageCourses/useClassVersions.ts new file mode 100644 index 000000000..6302bb706 --- /dev/null +++ b/src/components/HomepageCourses/useClassVersions.ts @@ -0,0 +1,69 @@ +import siteConfig from '@generated/docusaurus.config'; +import type { VersionOptions } from '@docusaurus/plugin-content-docs'; + +export interface Klass { + label: string; + uri: string; +} +export interface Course { + title: string; + classes: (string | Klass)[]; +} + +const classicPreset = + ( + siteConfig.presets?.find((p) => Array.isArray(p) && p[0] === 'classic') as [ + string, + Partial<{ docs: { versions: Record } }> + ] + )?.[1] ?? {}; +const versions = Object.keys((classicPreset.docs?.versions as Record | undefined) ?? {}); +const validClasses = versions.filter((v) => v.length === 4 && /^\d{2}([A-Z][a-z]|[a-z][A-Z])$/.test(v)); +validClasses.sort(); + +export const useClassVersions = () => { + const isHS = new Date().getMonth() + 1 >= 7 && new Date().getDate() >= 12; + const gym1Year = `${new Date().getFullYear() + (isHS ? 4 : 3)}`.slice(2, 4); + const gym1Next = `${parseInt(gym1Year) + 1}`; + const gym2Year = `${parseInt(gym1Year) - 1}`; + const gym1NextClasses = validClasses.filter((v) => /G|m/.test(v.charAt(2)) && v.startsWith(gym1Next)); + const gym1Classes = validClasses.filter((v) => /G|m/.test(v.charAt(2)) && v.startsWith(gym1Year)); + const gym2Classes = validClasses.filter((v) => /G|m/.test(v.charAt(2)) && v.startsWith(gym2Year)); + const fms1NextClasses = validClasses.filter((v) => /F/.test(v.charAt(2)) && v.startsWith(gym1Next)); + const fms1Classes = validClasses.filter((v) => /F/.test(v.charAt(2)) && v.startsWith(gym2Year)); + const courseList: Course[] = []; + if (gym1NextClasses.length > 0) { + courseList.push({ + title: 'Gym 1 (Neu)', + classes: gym1NextClasses + }); + } + if (gym1Classes.length > 0) { + courseList.push({ + title: 'Gym 1', + classes: gym1Classes + }); + } + if (gym2Classes.length > 0) { + courseList.push({ + title: 'Gym 2', + classes: gym2Classes + }); + } + if (fms1NextClasses.length > 0) { + courseList.push({ + title: 'FMS (Neu)', + classes: fms1NextClasses + }); + } + if (fms1Classes.length > 0) { + courseList.push({ + title: 'FMS', + classes: fms1Classes + }); + } + return { + classes: validClasses, + courseList: courseList + }; +}; diff --git a/src/pages/index.module.css b/src/pages/index.module.css index b1fb774d7..605351e88 100644 --- a/src/pages/index.module.css +++ b/src/pages/index.module.css @@ -21,3 +21,7 @@ align-items: center; justify-content: center; } + +.main { + margin: 0; +} diff --git a/src/pages/index.tsx b/src/pages/index.tsx index a21405a08..6be67db88 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -7,10 +7,25 @@ import Heading from '@theme/Heading'; import styles from './index.module.css'; import type { Navbar } from '@docusaurus/theme-common'; +import { useClassVersions } from '@tdev-components/HomepageCourses/useClassVersions'; +import HomepageCourses from '@tdev-components/HomepageCourses'; -function HomepageHeader() { +interface HeaderProps { + hasCourses: boolean; +} +const HomepageHeader = (props: HeaderProps) => { const { siteConfig } = useDocusaurusContext(); const firstNavbarItem = (siteConfig.themeConfig?.navbar as Navbar | undefined)?.items?.[0]; + if (props.hasCourses) { + return ( +
+
+

{siteConfig.title}

+

{siteConfig.tagline}

+
+
+ ); + } return (
@@ -29,18 +44,21 @@ function HomepageHeader() {
); -} +}; const Home = (): React.ReactNode => { const { siteConfig } = useDocusaurusContext(); + const { courseList } = useClassVersions(); + const hasCourses = courseList.length > 0; + return ( - -
- + +
+ {hasCourses ? : }
); From 428655948f765d4c000682a52f1025bc0d42bab6 Mon Sep 17 00:00:00 2001 From: bh0fer Date: Wed, 1 Jul 2026 12:27:45 +0200 Subject: [PATCH 04/13] add readme --- materialSync/README.md | 29 +++++++++++++++++++++++++++++ materialSync/add_class.ts | 2 +- updateTdev.config.yaml | 2 ++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/materialSync/README.md b/materialSync/README.md index d61b2c2a3..84fb2a970 100644 --- a/materialSync/README.md +++ b/materialSync/README.md @@ -9,6 +9,35 @@ TypeScript-based scripts for synchronizing teaching materials and documentation - `remove` - Remove material from `material.config.yaml` - `cleanup` - Cleanup all versioned docs - enhances dev performance. +## Scripts for `package.json` + +You can add some convenience scripts to your `package.json` for easier usage: + +```json +{ + "scripts": { + "sync": "yarn workspace @tdev/material-sync sync", + "remove": "yarn workspace @tdev/material-sync run remove", + "postremove": "yarn workspace @tdev/material-sync sync", + "precleanup": "yarn workspace @tdev/material-sync restore", + "cleanup": "yarn workspace @tdev/material-sync cleanup", + "addClass": "yarn workspace @tdev/material-sync run addClass", + "add": "yarn workspace @tdev/material-sync run add", + "postadd": "yarn workspace @tdev/material-sync sync" + } +} +``` + +Make sure to sync the documents before a build - this can happen either in your CI/CD pipeline or in a prebuild script. For example, you can add the following to your `package.json`: + +```json +{ + "scripts": { + "prebuild": "yarn workspace @tdev/material-sync sync" + } +} +``` + ## Configuration Configuration is defined in `material.config.yaml`. Each class version has a list of source folders to sync with their destination paths and ignore patterns. diff --git a/materialSync/add_class.ts b/materialSync/add_class.ts index c359059d9..036592b3f 100644 --- a/materialSync/add_class.ts +++ b/materialSync/add_class.ts @@ -18,7 +18,7 @@ yarn run add_class [className] examples: -yarn run addClass 30Gx // adds a new class to 'versioned_docs?, 'versioned_sidebar' and to 'material.config.yaml' +yarn run addClass 30Gx // adds a new class to 'versioned_docs', 'versioned_sidebar' and to 'material.config.yaml' yarn run addClass 30Ga,30Gx yarn run addClass "30Ga, 30Gx" `); diff --git a/updateTdev.config.yaml b/updateTdev.config.yaml index d5157ed82..bdc8a3ce2 100644 --- a/updateTdev.config.yaml +++ b/updateTdev.config.yaml @@ -9,6 +9,8 @@ trackedElements: dst: packages - src: updateSync/ dst: updateSync + - src: materialSync/ + dst: materialSync - src: tsconfig.tdev.json dst: tsconfig.json - src: tsconfig.package.json From 8bd6b8ac2ad9e742ec4a071266ac91cd09e9c20c Mon Sep 17 00:00:00 2001 From: bh0fer Date: Wed, 1 Jul 2026 15:06:43 +0200 Subject: [PATCH 05/13] add "as" keyword and write docs --- materialSync/material_add.ts | 29 ++-- materialSync/material_cleanup.ts | 3 - materialSync/material_helpers.ts | 39 +++++ materialSync/material_remove.ts | 27 +--- materialSync/material_sync.ts | 36 +---- .../docs/intro/03-class-management/index.mdx | 149 ++++++++++++++++++ 6 files changed, 202 insertions(+), 81 deletions(-) create mode 100644 tdev-website/docs/intro/03-class-management/index.mdx diff --git a/materialSync/material_add.ts b/materialSync/material_add.ts index 4d4bcb680..43162a051 100644 --- a/materialSync/material_add.ts +++ b/materialSync/material_add.ts @@ -2,7 +2,13 @@ import fs from 'fs'; import path from 'path'; import { exit } from 'process'; import minimist from 'minimist'; -import { loadMaterialConfig, resolveMaterialConfig, saveMaterialConfig } from './material_helpers'; +import { + DOC_PATHS, + loadMaterialConfig, + relative2Doc, + resolveMaterialConfig, + saveMaterialConfig +} from './material_helpers'; const repoRoot = path.resolve(__dirname, '..'); process.chdir(repoRoot); @@ -18,27 +24,11 @@ examples: yarn run add docs/byod-basics/v24/ --to="24a,24b" // --> adds /byod-basics/v24 to 24a & 24b yarn run add docs/byod-basics/v24/ --to="24a,24b" --as="My-Material" // --> adds /byod-basics to 24a & 24b -yarn run add byod-basics/v24/ --to="24a,24b" --as="My-Material" // same as above -yarn run add byod-basics/v24 --to="24a,24b" --as="My-Material" // same as above yarn run add docs/byod-basics/v24/ --to="24a,24b" --as="My-Material" --ignore="_category_.json,*.txt" `); exit(0); } -const DOC_PATHS = ['docs/', 'src/pages/', 'blog/']; - -const docBasePath = (src: string): string => { - return DOC_PATHS.find((p) => src.startsWith(p)) || DOC_PATHS[0]; -}; - -/** - * Get path relative to doc base path - */ -const relative2Doc = (p: string): string => { - const base = docBasePath(p); - return base ? p.slice(base.length) : p; -}; - let src: string = argv._[0]; const pathStart = DOC_PATHS.find((p) => src.startsWith(p)); @@ -53,7 +43,7 @@ if (isDir && !src.endsWith('/')) { } const klassen = argv.to ? (argv.to as string).split(',') : Object.keys(configs); -const to = argv.as || argv.name || relative2Doc(src); +const asPath = argv.as || argv.name || relative2Doc(src); let ignore: string[] = []; if (argv.ignore) { @@ -79,8 +69,7 @@ klassen.forEach((klass) => { return true; }); - const toPath = `versioned_docs/version-${klass}/${to}`; - configs[klass].push({ from: src, to: toPath, ignore: ignore }); + configs[klass].push({ from: src, as: asPath, ignore: ignore }); }); saveMaterialConfig(configs); diff --git a/materialSync/material_cleanup.ts b/materialSync/material_cleanup.ts index 4297556cc..3b5702d87 100644 --- a/materialSync/material_cleanup.ts +++ b/materialSync/material_cleanup.ts @@ -22,9 +22,6 @@ yarn run cleanup const klassen = Object.keys(configs); -fs.rmSync('src/pages/secure', { recursive: true, force: true }); -fs.rmSync('static/secure', { recursive: true, force: true }); - klassen.forEach((klass) => { const config = configs[klass]; const tmp_dir = `versioned_docs/version-${klass}/.tmp`; diff --git a/materialSync/material_helpers.ts b/materialSync/material_helpers.ts index 3d8489a5d..cc2405729 100644 --- a/materialSync/material_helpers.ts +++ b/materialSync/material_helpers.ts @@ -32,6 +32,9 @@ export type SyncConfig = BaseConfig & | { section: string; } + | { + as: string; + } ); export interface ConfigType { @@ -55,6 +58,9 @@ export const resolveMaterialConfig = (klass: string, config: SyncConfig): Normal if ('section' in config && config.section) { to = path.join(destinationBase, config.section); } + if ('as' in config && config.as) { + to = path.join(destinationBase, config.as); + } if ('to' in config && config.to) { if (config.to.startsWith(destinationBase)) { to = config.to; @@ -82,6 +88,39 @@ export const saveMaterialConfig = (config: ConfigType): void => { ); }; +export const DOC_PATHS = ['docs/', 'src/pages/', 'blog/']; + +export const docBasePath = (src: string): string => { + return DOC_PATHS.find((p) => src.startsWith(p)) || DOC_PATHS[0]; +}; +/** + * Get path relative to doc base path + */ +export const relative2Doc = (p: string): string => { + const base = docBasePath(p); + return base ? p.slice(base.length) : p; +}; + +export const ensureTrailingSlash = (p: string): string => { + if (typeof p !== 'string') { + return p; + } + if (p.endsWith('/')) { + return p; + } + return `${p}/`; +}; + +export const ensureStartingSlash = (p: string): string => { + if (typeof p !== 'string') { + return p; + } + if (p.startsWith('/')) { + return p; + } + return `/${p}`; +}; + /** * Ensure rsync sync completes successfully, retrying on failure */ diff --git a/materialSync/material_remove.ts b/materialSync/material_remove.ts index e44b2ac4e..baf6a4fb1 100644 --- a/materialSync/material_remove.ts +++ b/materialSync/material_remove.ts @@ -2,7 +2,10 @@ import fs from 'fs'; import path from 'path'; import minimist from 'minimist'; import { + docBasePath, + ensureTrailingSlash, loadMaterialConfig, + relative2Doc, resolveMaterialConfig, saveMaterialConfig, SyncConfig @@ -28,30 +31,6 @@ yarn run remove docs/byod-basics/v24/ --from="24a,24b" const toRemove = argv._; const klassen = argv.from ? (argv.from as string).split(',') : Object.keys(configs); -const DOC_PATHS = ['docs/', 'src/pages/', 'news/']; - -const docBasePath = (src: string): string => { - return DOC_PATHS.find((p) => src.startsWith(p)) || DOC_PATHS[0]; -}; - -/** - * Get path relative to doc base path - */ -const relative2Doc = (p: string): string => { - const base = docBasePath(p); - return base ? p.slice(base.length) : p; -}; - -const ensureTrailingSlash = (p: string): string => { - if (typeof p !== 'string') { - return p; - } - if (p.endsWith('/')) { - return p; - } - return `${p}/`; -}; - klassen.forEach((klass) => { const klassConfig = configs[klass]; const keepedFiles: SyncConfig[] = []; diff --git a/materialSync/material_sync.ts b/materialSync/material_sync.ts index 93ea41c28..38f8b0758 100644 --- a/materialSync/material_sync.ts +++ b/materialSync/material_sync.ts @@ -3,7 +3,9 @@ import path from 'path'; import Rsync from 'rsync'; import { ConfigType, + ensureStartingSlash, ensureSync, + ensureTrailingSlash, loadMaterialConfig, pathExists, resolveMaterialConfig @@ -13,12 +15,6 @@ process.chdir(repoRoot); const typedConfig: ConfigType = loadMaterialConfig(); -const DOC_PATHS = ['docs/', 'src/pages/', 'news/']; - -const docBasePath = (src: string): string => { - return DOC_PATHS.find((p) => src.startsWith(p)) || DOC_PATHS[0]; -}; - /** * Recursively find markdown template files (starting with _) */ @@ -44,34 +40,6 @@ const findMdTemplate = async (src: string): Promise => { return mdFiles; }; -/** - * Get path relative to doc base path - */ -const relative2Doc = (p: string): string => { - const base = docBasePath(p); - return base ? p.slice(base.length) : p; -}; - -const ensureStartingSlash = (p: string): string => { - if (typeof p !== 'string') { - return p; - } - if (p.startsWith('/')) { - return p; - } - return `/${p}`; -}; - -const ensureTrailingSlash = (p: string): string => { - if (typeof p !== 'string') { - return p; - } - if (p.endsWith('/')) { - return p; - } - return `${p}/`; -}; - const main = async (): Promise => { if (process.env.WITHOUT_DOCS) { /** diff --git a/tdev-website/docs/intro/03-class-management/index.mdx b/tdev-website/docs/intro/03-class-management/index.mdx new file mode 100644 index 000000000..1cb186e96 --- /dev/null +++ b/tdev-website/docs/intro/03-class-management/index.mdx @@ -0,0 +1,149 @@ +--- +page_id: 9a836e52-2ffc-47aa-9144-b3c247be6bbd +--- + +# Klassenmanagement + +Grundsätzlich werden jegliche Inhalte im Ordner `/docs` erstellt, abgelegt und mit git versioniert. + +Um den Klassen individuell zusammengestellte Inhalte zu präsentieren, wird im `material.config.yaml` festgelegt, welche Inhalte welcher Klasse angezeigt werden. Das Standard-Format sieht wie folgt aus: + +```yaml showLineNumbers +28Gj: + - from: docs/OF-BYOD-Basics/ + to: versioned_docs/version-28Gb/01-BYOD + - from: docs/OF-Word/ + to: versioned_docs/version-28Gb/03-Word + ignore: + - _category_.yml +29Ga: + - from: docs/OF-BYOD-Basics-29/ + to: versioned_docs/version-29Ga/02-BYOD + open: true +``` + +`from` +: Pfad zum Quellordner oder der Quelldatei, die verteilt werden soll. Der Pfad ist relativ zum Projektstammverzeichnis. +`to` +: Pfad zum Zielordner, in dem die Inhalte für die jeweilige Klasse abgelegt werden. Der Pfad ist relativ zum Projektstammverzeichnis. +`ignore` +: *optional* +: Liste von Dateien oder Ordnern, die nicht kopiert werden sollen. Der Pfad ist relativ zum Quellordner. +: ⚠️ aktuell können nur direkt im Quellordner liegende Dateien oder Ordner ignoriert werden. Unterordner werden nicht unterstützt. +`open` +: *optional* +: Gibt an, ob der Ordner in der Navigation standardmässig geöffnet sein soll. In diesem Fall wird ein Datei `_category_.json` im Zielordner erstellt, in dem die Einstellung gespeichert wird. + + +Obige Konfiguration kann auch abgekürzt wie folgt geschrieben werden: + +```yaml showLineNumbers +28Gj: + - material: OF-BYOD-Basics + as: 01-BYOD + - material: OF-Word + as: 03-Word + ignore: + - _category_.yml +29Ga: + - material: OF-BYOD-Basics-29 + as: 02-BYOD + open: true +``` + +`material` +: Pfad zum Quellordner, relativ zum Ordner `/docs`. Der Ordnername wird automatisch dem Pfad `/docs` vorangestellt. +`as` +: Pfad zum Zielordner, relativ zum Ordner `versioned_docs/version-/`. +: *alias* `section` (kann gleichbedeutend zu `as` verwendet werden) + +## CLI + +Um das Material entsprechend der Konfiguration im `material.config.yaml` in die jeweiligen Klassenordner zu kopieren, muss folgender Befehl ausgeführt werden: + +```bash +yarn run sync +``` + +:::details[Scripts in package.json] +Folgende Einträge können in der `package.json` hinzugefügt werden, um die Nutzung zu vereinfachen: +```json +{ + "scripts": { + "sync": "yarn workspace @tdev/material-sync sync", + "remove": "yarn workspace @tdev/material-sync run remove", + "postremove": "yarn workspace @tdev/material-sync sync", + "precleanup": "yarn workspace @tdev/material-sync restore", + "cleanup": "yarn workspace @tdev/material-sync cleanup", + "addClass": "yarn workspace @tdev/material-sync run addClass", + "add": "yarn workspace @tdev/material-sync run add", + "postadd": "yarn workspace @tdev/material-sync sync" + } +} +``` + +Zudem muss sichergestellt werden, dass die Dokumente vor einem Build synchronisiert werden - dies kann entweder in der CI/CD-Pipeline oder in einem Prebuild-Skript geschehen. Beispielsweise kann folgendes in der `package.json` hinzugefügt werden: + +```json +{ + "scripts": { + "prebuild": "yarn workspace @tdev/material-sync sync" + } +} +``` +::: + +### Material Hinzufügen +Um die Inhalte für Klassen leichter zu konfigurieren, kann über die Kommandozeile wie folgt vorgegangen werden: + +```bash +# add /OF-BYOD-Basics to 28Ga & 28Gb as /OF-BYOD-Basics +yarn run add docs/OF-BYOD-Basics/ --to="28Ga,28Gb" +# add /OF-BYOD-Basics to 28Ga & 28Gb as /01-BYOD +yarn run add docs/OF-BYOD-Basics/ --to="28Ga,28Gb" --as="01-BYOD" +# ignoriert _category_.json und alle .txt-Dateien im Quellordner +yarn run add docs/OF-BYOD-Basics/ --to="28Ga,28Gb" --as="01-BYOD" --ignore="_category_.json,*.txt" +``` + +:::tip[Autovervollständigung von Pfaden] +Bash ermöglich das Autovervollständigen von Pfaden und Dateinamen mit der [[tab]]-Taste. Dies kann beim Tippen von Pfaden sehr hilfreich sein. +::: + +### Material synchronisieren + +Das Material wird entsprechend der Konfiguration im `material.config.yaml` in die jeweiligen Klassenordner kopiert. Dies kann über die Kommandozeile wie folgt ausgeführt werden: + +```bash +yarn run sync +``` + +### [Dev] Material aufräumen + +Um im Entwicklungsprozess die Aufstartzeit zu verkürzen, können die übers `material.config.yaml` konfigurierten Inhalte in den Klassenordnern gelöscht werden. Dies kann über die Kommandozeile wie folgt ausgeführt werden: + +```bash +yarn run cleanup +``` + +### Material aus der Konfiguration entfernen +Um Inhalte aus der Konfiguration zu entfernen, kann über die Kommandozeile wie folgt vorgegangen werden: + +```bash +yarn run remove docs/OF-BYOD-Basics/ --from="28Ga,28Gb" +``` + +Alternativ können auch die entsprechenden Zeilen aus dem `material.config.yaml` gelöscht werden. + +## Neue Klasse hinzufügen + +Um eine neue Klasse hinzuzufügen, kann über die Kommandozeile wie folgt vorgegangen werden: + +```bash +yarn run addClass 30Ga +``` + +oder für mehrere Klassen gleichzeitig: + +```bash +yarn run addClass 30Ga,30Gb,30Gc +``` \ No newline at end of file From d60a3f9efa902ba0cd0de9964b2a3e5e42f25981 Mon Sep 17 00:00:00 2001 From: bh0fer Date: Wed, 1 Jul 2026 15:16:34 +0200 Subject: [PATCH 06/13] refactor naming --- materialSync/package.json | 14 +++++++------- materialSync/{material_add.ts => src/add.ts} | 7 +++---- materialSync/{ => src}/add_class.ts | 16 +++++++--------- .../{material_cleanup.ts => src/cleanup.ts} | 5 ++--- .../{material_helpers.ts => src/helpers.ts} | 4 +++- .../{material_remove.ts => src/remove.ts} | 6 +++--- .../{material_restore.ts => src/restore.ts} | 5 ++--- materialSync/{material_sync.ts => src/sync.ts} | 8 ++++---- materialSync/tsconfig.json | 2 +- 9 files changed, 32 insertions(+), 35 deletions(-) rename materialSync/{material_add.ts => src/add.ts} (94%) rename materialSync/{ => src}/add_class.ts (79%) rename materialSync/{material_cleanup.ts => src/cleanup.ts} (94%) rename materialSync/{material_helpers.ts => src/helpers.ts} (96%) rename materialSync/{material_remove.ts => src/remove.ts} (95%) rename materialSync/{material_restore.ts => src/restore.ts} (93%) rename materialSync/{material_sync.ts => src/sync.ts} (97%) diff --git a/materialSync/package.json b/materialSync/package.json index 95e464548..733e5663f 100644 --- a/materialSync/package.json +++ b/materialSync/package.json @@ -4,13 +4,13 @@ "description": "Material synchronization scripts for teaching documentation", "private": true, "scripts": { - "sync": "tsx material_sync.ts", - "sync:secure": "tsx material_sync_secure.ts", - "add": "tsx material_add.ts", - "remove": "tsx material_remove.ts", - "cleanup": "tsx material_cleanup.ts", - "restore": "tsx material_restore.ts", - "addClass": "tsx add_class.ts" + "sync": "tsx src/sync.ts", + "sync:secure": "tsx src/sync_secure.ts", + "add": "tsx src/add.ts", + "remove": "tsx src/remove.ts", + "cleanup": "tsx src/cleanup.ts", + "restore": "tsx src/restore.ts", + "addClass": "tsx src/add_class.ts" }, "dependencies": { }, diff --git a/materialSync/material_add.ts b/materialSync/src/add.ts similarity index 94% rename from materialSync/material_add.ts rename to materialSync/src/add.ts index 43162a051..acef34c3a 100644 --- a/materialSync/material_add.ts +++ b/materialSync/src/add.ts @@ -1,17 +1,16 @@ import fs from 'fs'; -import path from 'path'; import { exit } from 'process'; import minimist from 'minimist'; import { DOC_PATHS, loadMaterialConfig, relative2Doc, + REPO_ROOT, resolveMaterialConfig, saveMaterialConfig -} from './material_helpers'; +} from './helpers'; -const repoRoot = path.resolve(__dirname, '..'); -process.chdir(repoRoot); +process.chdir(REPO_ROOT); const configs = loadMaterialConfig(); const argv = minimist(process.argv.slice(2)); diff --git a/materialSync/add_class.ts b/materialSync/src/add_class.ts similarity index 79% rename from materialSync/add_class.ts rename to materialSync/src/add_class.ts index 036592b3f..53dadbb55 100644 --- a/materialSync/add_class.ts +++ b/materialSync/src/add_class.ts @@ -1,12 +1,10 @@ import path from 'path'; import fs from 'fs/promises'; -import { loadMaterialConfig, pathExists, saveMaterialConfig } from './material_helpers'; +import { loadMaterialConfig, pathExists, REPO_ROOT, saveMaterialConfig } from './helpers'; import minimist from 'minimist'; import { exit } from 'process'; import crypto from 'node:crypto'; - -const repoRoot = path.resolve(__dirname, '..'); -process.chdir(repoRoot); +process.chdir(REPO_ROOT); const configs = loadMaterialConfig(); const currentKlasses = Object.keys(configs); @@ -43,13 +41,13 @@ page_id: ${crypto.randomUUID()} }; const main = async (): Promise => { - const versionedSidebarsPath = path.join(repoRoot, 'versioned_sidebars'); + const versionedSidebarsPath = path.join(REPO_ROOT, 'versioned_sidebars'); if (!(await pathExists(versionedSidebarsPath))) { await fs.mkdir(versionedSidebarsPath, { recursive: true }); } for (const klass of newKlasses) { - const versionedDocsPath = path.join(repoRoot, 'versioned_docs', `version-${klass}`); + const versionedDocsPath = path.join(REPO_ROOT, 'versioned_docs', `version-${klass}`); if (!(await pathExists(versionedDocsPath))) { await fs.mkdir(versionedDocsPath, { recursive: true }); } @@ -66,14 +64,14 @@ const main = async (): Promise => { } configs[klass] = []; } - const hasVersions = await pathExists(path.join(repoRoot, 'versions.json')); + const hasVersions = await pathExists(path.join(REPO_ROOT, 'versions.json')); const currentVersions = await (hasVersions ? fs - .readFile(path.join(repoRoot, 'versions.json'), 'utf-8') + .readFile(path.join(REPO_ROOT, 'versions.json'), 'utf-8') .then((data) => JSON.parse(data) as string[]) : Promise.resolve([])); const newVersions = [...new Set([...currentVersions, ...newKlasses].sort())]; - await fs.writeFile(path.join(repoRoot, 'versions.json'), JSON.stringify(newVersions, null, 2)); + await fs.writeFile(path.join(REPO_ROOT, 'versions.json'), JSON.stringify(newVersions, null, 2)); saveMaterialConfig(configs); console.log('✅ Added new classes:', newKlasses.join(', '), 'Edit your siteConfig.ts accordingly'); diff --git a/materialSync/material_cleanup.ts b/materialSync/src/cleanup.ts similarity index 94% rename from materialSync/material_cleanup.ts rename to materialSync/src/cleanup.ts index 3b5702d87..6cfa4a098 100644 --- a/materialSync/material_cleanup.ts +++ b/materialSync/src/cleanup.ts @@ -1,10 +1,9 @@ import fs from 'fs'; import path from 'path'; import minimist from 'minimist'; -import { loadMaterialConfig, resolveMaterialConfig } from './material_helpers'; +import { loadMaterialConfig, REPO_ROOT, resolveMaterialConfig } from './helpers'; -const repoRoot = path.resolve(__dirname, '..'); -process.chdir(repoRoot); +process.chdir(REPO_ROOT); const configs = loadMaterialConfig(); const argv = minimist(process.argv.slice(2)); diff --git a/materialSync/material_helpers.ts b/materialSync/src/helpers.ts similarity index 96% rename from materialSync/material_helpers.ts rename to materialSync/src/helpers.ts index cc2405729..b6705842b 100644 --- a/materialSync/material_helpers.ts +++ b/materialSync/src/helpers.ts @@ -3,6 +3,8 @@ import fsp from 'fs/promises'; import path from 'path'; import Rsync from 'rsync'; import yaml from 'js-yaml'; +export const REPO_ROOT = path.resolve(__dirname, '..', '..'); +console.log('REPO_ROOT', REPO_ROOT); type RsyncInstance = InstanceType; @@ -43,7 +45,7 @@ export interface ConfigType { export const CONFIG_NAME = 'material.config.yaml'; -const materialConfigPath = path.resolve(__dirname, '..', CONFIG_NAME); +const materialConfigPath = path.resolve(REPO_ROOT, CONFIG_NAME); export const resolveMaterialConfig = (klass: string, config: SyncConfig): NormalizedConfig => { let from: string; diff --git a/materialSync/material_remove.ts b/materialSync/src/remove.ts similarity index 95% rename from materialSync/material_remove.ts rename to materialSync/src/remove.ts index baf6a4fb1..08014b5cd 100644 --- a/materialSync/material_remove.ts +++ b/materialSync/src/remove.ts @@ -6,13 +6,13 @@ import { ensureTrailingSlash, loadMaterialConfig, relative2Doc, + REPO_ROOT, resolveMaterialConfig, saveMaterialConfig, SyncConfig -} from './material_helpers'; +} from './helpers'; -const repoRoot = path.resolve(__dirname, '..'); -process.chdir(repoRoot); +process.chdir(REPO_ROOT); const configs = loadMaterialConfig(); const argv = minimist(process.argv.slice(2)); diff --git a/materialSync/material_restore.ts b/materialSync/src/restore.ts similarity index 93% rename from materialSync/material_restore.ts rename to materialSync/src/restore.ts index 6c9a0ae3b..a820a233a 100644 --- a/materialSync/material_restore.ts +++ b/materialSync/src/restore.ts @@ -1,8 +1,7 @@ import fs from 'fs'; -import path from 'path'; +import { REPO_ROOT } from './helpers'; -const repoRoot = path.resolve(__dirname, '..'); -process.chdir(repoRoot); +process.chdir(REPO_ROOT); const main = (): void => { if (fs.existsSync('_docs') && fs.lstatSync('_docs').isDirectory()) { diff --git a/materialSync/material_sync.ts b/materialSync/src/sync.ts similarity index 97% rename from materialSync/material_sync.ts rename to materialSync/src/sync.ts index 38f8b0758..fa33f100c 100644 --- a/materialSync/material_sync.ts +++ b/materialSync/src/sync.ts @@ -8,10 +8,10 @@ import { ensureTrailingSlash, loadMaterialConfig, pathExists, + REPO_ROOT, resolveMaterialConfig -} from './material_helpers'; -const repoRoot = path.resolve(__dirname, '..'); -process.chdir(repoRoot); +} from './helpers'; +process.chdir(REPO_ROOT); const typedConfig: ConfigType = loadMaterialConfig(); @@ -51,7 +51,7 @@ const main = async (): Promise => { await fs.mkdir('docs'); await fs.cp('_docs/home.md', 'docs/home.md'); /** copy all markdown-templates - otherwise some pages might fail */ - const templates = await findMdTemplate(path.join(__dirname, '../_docs')); + const templates = await findMdTemplate(path.join(REPO_ROOT, '_docs')); await Promise.all(templates.map((file) => fs.cp(file, file.replace('/_docs/', '/docs/')))); } if (process.env.DOCS_ONLY) { diff --git a/materialSync/tsconfig.json b/materialSync/tsconfig.json index 61b50cb68..f4876fb96 100644 --- a/materialSync/tsconfig.json +++ b/materialSync/tsconfig.json @@ -11,6 +11,6 @@ "resolveJsonModule": true, "moduleResolution": "node" }, - "include": ["*.ts"], + "include": ["src/*.ts"], "exclude": ["node_modules", "dist"] } From 517ba1f0e164cd65c8f386c3d8cf19e1790a3ec0 Mon Sep 17 00:00:00 2001 From: bh0fer Date: Wed, 1 Jul 2026 15:21:34 +0200 Subject: [PATCH 07/13] move material sync to tdev package --- materialSync/README.md | 54 ------- package.json | 1 - packages/tdev/material-sync/README.md | 149 ++++++++++++++++++ .../tdev/material-sync}/package.json | 0 .../tdev/material-sync}/src/add.ts | 0 .../tdev/material-sync}/src/add_class.ts | 0 .../tdev/material-sync}/src/cleanup.ts | 0 .../tdev/material-sync}/src/helpers.ts | 3 +- .../tdev/material-sync}/src/remove.ts | 0 .../tdev/material-sync}/src/restore.ts | 0 .../tdev/material-sync}/src/sync.ts | 0 .../tdev/material-sync}/tsconfig.json | 3 +- updateTdev.config.yaml | 2 - 13 files changed, 152 insertions(+), 60 deletions(-) delete mode 100644 materialSync/README.md create mode 100644 packages/tdev/material-sync/README.md rename {materialSync => packages/tdev/material-sync}/package.json (100%) rename {materialSync => packages/tdev/material-sync}/src/add.ts (100%) rename {materialSync => packages/tdev/material-sync}/src/add_class.ts (100%) rename {materialSync => packages/tdev/material-sync}/src/cleanup.ts (100%) rename {materialSync => packages/tdev/material-sync}/src/helpers.ts (97%) rename {materialSync => packages/tdev/material-sync}/src/remove.ts (100%) rename {materialSync => packages/tdev/material-sync}/src/restore.ts (100%) rename {materialSync => packages/tdev/material-sync}/src/sync.ts (100%) rename {materialSync => packages/tdev/material-sync}/tsconfig.json (83%) diff --git a/materialSync/README.md b/materialSync/README.md deleted file mode 100644 index 84fb2a970..000000000 --- a/materialSync/README.md +++ /dev/null @@ -1,54 +0,0 @@ -# Material Sync - -TypeScript-based scripts for synchronizing teaching materials and documentation versions. - -## Scripts - -- `sync` - Sync materials according to `material.config.yaml` -- `add` - Add new material to `material.config.yaml` -- `remove` - Remove material from `material.config.yaml` -- `cleanup` - Cleanup all versioned docs - enhances dev performance. - -## Scripts for `package.json` - -You can add some convenience scripts to your `package.json` for easier usage: - -```json -{ - "scripts": { - "sync": "yarn workspace @tdev/material-sync sync", - "remove": "yarn workspace @tdev/material-sync run remove", - "postremove": "yarn workspace @tdev/material-sync sync", - "precleanup": "yarn workspace @tdev/material-sync restore", - "cleanup": "yarn workspace @tdev/material-sync cleanup", - "addClass": "yarn workspace @tdev/material-sync run addClass", - "add": "yarn workspace @tdev/material-sync run add", - "postadd": "yarn workspace @tdev/material-sync sync" - } -} -``` - -Make sure to sync the documents before a build - this can happen either in your CI/CD pipeline or in a prebuild script. For example, you can add the following to your `package.json`: - -```json -{ - "scripts": { - "prebuild": "yarn workspace @tdev/material-sync sync" - } -} -``` - -## Configuration - -Configuration is defined in `material.config.yaml`. Each class version has a list of source folders to sync with their destination paths and ignore patterns. - -## Usage - -```bash -# Run from project root -yarn workspace @tdev/material-sync sync - -# Or run directly from materialSync folder -cd materialSync -yarn sync -``` diff --git a/package.json b/package.json index cb4b9c149..0b64b9e63 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,6 @@ "name": "@tdev/core", "workspaces": [ "packages/**", - "materialSync", "." ], "version": "1.0.0", diff --git a/packages/tdev/material-sync/README.md b/packages/tdev/material-sync/README.md new file mode 100644 index 000000000..1cb186e96 --- /dev/null +++ b/packages/tdev/material-sync/README.md @@ -0,0 +1,149 @@ +--- +page_id: 9a836e52-2ffc-47aa-9144-b3c247be6bbd +--- + +# Klassenmanagement + +Grundsätzlich werden jegliche Inhalte im Ordner `/docs` erstellt, abgelegt und mit git versioniert. + +Um den Klassen individuell zusammengestellte Inhalte zu präsentieren, wird im `material.config.yaml` festgelegt, welche Inhalte welcher Klasse angezeigt werden. Das Standard-Format sieht wie folgt aus: + +```yaml showLineNumbers +28Gj: + - from: docs/OF-BYOD-Basics/ + to: versioned_docs/version-28Gb/01-BYOD + - from: docs/OF-Word/ + to: versioned_docs/version-28Gb/03-Word + ignore: + - _category_.yml +29Ga: + - from: docs/OF-BYOD-Basics-29/ + to: versioned_docs/version-29Ga/02-BYOD + open: true +``` + +`from` +: Pfad zum Quellordner oder der Quelldatei, die verteilt werden soll. Der Pfad ist relativ zum Projektstammverzeichnis. +`to` +: Pfad zum Zielordner, in dem die Inhalte für die jeweilige Klasse abgelegt werden. Der Pfad ist relativ zum Projektstammverzeichnis. +`ignore` +: *optional* +: Liste von Dateien oder Ordnern, die nicht kopiert werden sollen. Der Pfad ist relativ zum Quellordner. +: ⚠️ aktuell können nur direkt im Quellordner liegende Dateien oder Ordner ignoriert werden. Unterordner werden nicht unterstützt. +`open` +: *optional* +: Gibt an, ob der Ordner in der Navigation standardmässig geöffnet sein soll. In diesem Fall wird ein Datei `_category_.json` im Zielordner erstellt, in dem die Einstellung gespeichert wird. + + +Obige Konfiguration kann auch abgekürzt wie folgt geschrieben werden: + +```yaml showLineNumbers +28Gj: + - material: OF-BYOD-Basics + as: 01-BYOD + - material: OF-Word + as: 03-Word + ignore: + - _category_.yml +29Ga: + - material: OF-BYOD-Basics-29 + as: 02-BYOD + open: true +``` + +`material` +: Pfad zum Quellordner, relativ zum Ordner `/docs`. Der Ordnername wird automatisch dem Pfad `/docs` vorangestellt. +`as` +: Pfad zum Zielordner, relativ zum Ordner `versioned_docs/version-/`. +: *alias* `section` (kann gleichbedeutend zu `as` verwendet werden) + +## CLI + +Um das Material entsprechend der Konfiguration im `material.config.yaml` in die jeweiligen Klassenordner zu kopieren, muss folgender Befehl ausgeführt werden: + +```bash +yarn run sync +``` + +:::details[Scripts in package.json] +Folgende Einträge können in der `package.json` hinzugefügt werden, um die Nutzung zu vereinfachen: +```json +{ + "scripts": { + "sync": "yarn workspace @tdev/material-sync sync", + "remove": "yarn workspace @tdev/material-sync run remove", + "postremove": "yarn workspace @tdev/material-sync sync", + "precleanup": "yarn workspace @tdev/material-sync restore", + "cleanup": "yarn workspace @tdev/material-sync cleanup", + "addClass": "yarn workspace @tdev/material-sync run addClass", + "add": "yarn workspace @tdev/material-sync run add", + "postadd": "yarn workspace @tdev/material-sync sync" + } +} +``` + +Zudem muss sichergestellt werden, dass die Dokumente vor einem Build synchronisiert werden - dies kann entweder in der CI/CD-Pipeline oder in einem Prebuild-Skript geschehen. Beispielsweise kann folgendes in der `package.json` hinzugefügt werden: + +```json +{ + "scripts": { + "prebuild": "yarn workspace @tdev/material-sync sync" + } +} +``` +::: + +### Material Hinzufügen +Um die Inhalte für Klassen leichter zu konfigurieren, kann über die Kommandozeile wie folgt vorgegangen werden: + +```bash +# add /OF-BYOD-Basics to 28Ga & 28Gb as /OF-BYOD-Basics +yarn run add docs/OF-BYOD-Basics/ --to="28Ga,28Gb" +# add /OF-BYOD-Basics to 28Ga & 28Gb as /01-BYOD +yarn run add docs/OF-BYOD-Basics/ --to="28Ga,28Gb" --as="01-BYOD" +# ignoriert _category_.json und alle .txt-Dateien im Quellordner +yarn run add docs/OF-BYOD-Basics/ --to="28Ga,28Gb" --as="01-BYOD" --ignore="_category_.json,*.txt" +``` + +:::tip[Autovervollständigung von Pfaden] +Bash ermöglich das Autovervollständigen von Pfaden und Dateinamen mit der [[tab]]-Taste. Dies kann beim Tippen von Pfaden sehr hilfreich sein. +::: + +### Material synchronisieren + +Das Material wird entsprechend der Konfiguration im `material.config.yaml` in die jeweiligen Klassenordner kopiert. Dies kann über die Kommandozeile wie folgt ausgeführt werden: + +```bash +yarn run sync +``` + +### [Dev] Material aufräumen + +Um im Entwicklungsprozess die Aufstartzeit zu verkürzen, können die übers `material.config.yaml` konfigurierten Inhalte in den Klassenordnern gelöscht werden. Dies kann über die Kommandozeile wie folgt ausgeführt werden: + +```bash +yarn run cleanup +``` + +### Material aus der Konfiguration entfernen +Um Inhalte aus der Konfiguration zu entfernen, kann über die Kommandozeile wie folgt vorgegangen werden: + +```bash +yarn run remove docs/OF-BYOD-Basics/ --from="28Ga,28Gb" +``` + +Alternativ können auch die entsprechenden Zeilen aus dem `material.config.yaml` gelöscht werden. + +## Neue Klasse hinzufügen + +Um eine neue Klasse hinzuzufügen, kann über die Kommandozeile wie folgt vorgegangen werden: + +```bash +yarn run addClass 30Ga +``` + +oder für mehrere Klassen gleichzeitig: + +```bash +yarn run addClass 30Ga,30Gb,30Gc +``` \ No newline at end of file diff --git a/materialSync/package.json b/packages/tdev/material-sync/package.json similarity index 100% rename from materialSync/package.json rename to packages/tdev/material-sync/package.json diff --git a/materialSync/src/add.ts b/packages/tdev/material-sync/src/add.ts similarity index 100% rename from materialSync/src/add.ts rename to packages/tdev/material-sync/src/add.ts diff --git a/materialSync/src/add_class.ts b/packages/tdev/material-sync/src/add_class.ts similarity index 100% rename from materialSync/src/add_class.ts rename to packages/tdev/material-sync/src/add_class.ts diff --git a/materialSync/src/cleanup.ts b/packages/tdev/material-sync/src/cleanup.ts similarity index 100% rename from materialSync/src/cleanup.ts rename to packages/tdev/material-sync/src/cleanup.ts diff --git a/materialSync/src/helpers.ts b/packages/tdev/material-sync/src/helpers.ts similarity index 97% rename from materialSync/src/helpers.ts rename to packages/tdev/material-sync/src/helpers.ts index b6705842b..cb9e39d2b 100644 --- a/materialSync/src/helpers.ts +++ b/packages/tdev/material-sync/src/helpers.ts @@ -3,8 +3,7 @@ import fsp from 'fs/promises'; import path from 'path'; import Rsync from 'rsync'; import yaml from 'js-yaml'; -export const REPO_ROOT = path.resolve(__dirname, '..', '..'); -console.log('REPO_ROOT', REPO_ROOT); +export const REPO_ROOT = path.resolve(__dirname, '..', '..', '..', '..'); type RsyncInstance = InstanceType; diff --git a/materialSync/src/remove.ts b/packages/tdev/material-sync/src/remove.ts similarity index 100% rename from materialSync/src/remove.ts rename to packages/tdev/material-sync/src/remove.ts diff --git a/materialSync/src/restore.ts b/packages/tdev/material-sync/src/restore.ts similarity index 100% rename from materialSync/src/restore.ts rename to packages/tdev/material-sync/src/restore.ts diff --git a/materialSync/src/sync.ts b/packages/tdev/material-sync/src/sync.ts similarity index 100% rename from materialSync/src/sync.ts rename to packages/tdev/material-sync/src/sync.ts diff --git a/materialSync/tsconfig.json b/packages/tdev/material-sync/tsconfig.json similarity index 83% rename from materialSync/tsconfig.json rename to packages/tdev/material-sync/tsconfig.json index f4876fb96..5f09a804c 100644 --- a/materialSync/tsconfig.json +++ b/packages/tdev/material-sync/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../tsconfig.json", + "extends": "../../../tsconfig.json", "compilerOptions": { "outDir": "./dist", "rootDir": ".", @@ -9,6 +9,7 @@ "esModuleInterop": true, "allowSyntheticDefaultImports": true, "resolveJsonModule": true, + "ignoreDeprecations": "6.0", "moduleResolution": "node" }, "include": ["src/*.ts"], diff --git a/updateTdev.config.yaml b/updateTdev.config.yaml index bdc8a3ce2..d5157ed82 100644 --- a/updateTdev.config.yaml +++ b/updateTdev.config.yaml @@ -9,8 +9,6 @@ trackedElements: dst: packages - src: updateSync/ dst: updateSync - - src: materialSync/ - dst: materialSync - src: tsconfig.tdev.json dst: tsconfig.json - src: tsconfig.package.json From b06720f962131729caf9a9f46a077f1607d80384 Mon Sep 17 00:00:00 2001 From: bh0fer Date: Wed, 1 Jul 2026 15:29:05 +0200 Subject: [PATCH 08/13] make material-sync an es module --- packages/tdev/material-sync/package.json | 2 +- packages/tdev/material-sync/src/add.ts | 6 +++--- packages/tdev/material-sync/src/add_class.ts | 8 ++++---- packages/tdev/material-sync/src/cleanup.ts | 6 +++--- packages/tdev/material-sync/src/helpers.ts | 10 +++++++--- packages/tdev/material-sync/src/remove.ts | 6 +++--- packages/tdev/material-sync/src/restore.ts | 4 ++-- packages/tdev/material-sync/src/sync.ts | 6 +++--- packages/tdev/material-sync/tsconfig.json | 5 ++--- 9 files changed, 28 insertions(+), 25 deletions(-) diff --git a/packages/tdev/material-sync/package.json b/packages/tdev/material-sync/package.json index 733e5663f..b0fee4cc3 100644 --- a/packages/tdev/material-sync/package.json +++ b/packages/tdev/material-sync/package.json @@ -2,10 +2,10 @@ "name": "@tdev/material-sync", "version": "1.0.0", "description": "Material synchronization scripts for teaching documentation", + "type": "module", "private": true, "scripts": { "sync": "tsx src/sync.ts", - "sync:secure": "tsx src/sync_secure.ts", "add": "tsx src/add.ts", "remove": "tsx src/remove.ts", "cleanup": "tsx src/cleanup.ts", diff --git a/packages/tdev/material-sync/src/add.ts b/packages/tdev/material-sync/src/add.ts index acef34c3a..032b1c6a9 100644 --- a/packages/tdev/material-sync/src/add.ts +++ b/packages/tdev/material-sync/src/add.ts @@ -1,5 +1,5 @@ -import fs from 'fs'; -import { exit } from 'process'; +import fs from 'node:fs'; +import { exit } from 'node:process'; import minimist from 'minimist'; import { DOC_PATHS, @@ -8,7 +8,7 @@ import { REPO_ROOT, resolveMaterialConfig, saveMaterialConfig -} from './helpers'; +} from './helpers.js'; process.chdir(REPO_ROOT); diff --git a/packages/tdev/material-sync/src/add_class.ts b/packages/tdev/material-sync/src/add_class.ts index 53dadbb55..b0bdfaa7d 100644 --- a/packages/tdev/material-sync/src/add_class.ts +++ b/packages/tdev/material-sync/src/add_class.ts @@ -1,8 +1,8 @@ -import path from 'path'; -import fs from 'fs/promises'; -import { loadMaterialConfig, pathExists, REPO_ROOT, saveMaterialConfig } from './helpers'; +import path from 'node:path'; +import fs from 'node:fs/promises'; +import { loadMaterialConfig, pathExists, REPO_ROOT, saveMaterialConfig } from './helpers.js'; import minimist from 'minimist'; -import { exit } from 'process'; +import { exit } from 'node:process'; import crypto from 'node:crypto'; process.chdir(REPO_ROOT); diff --git a/packages/tdev/material-sync/src/cleanup.ts b/packages/tdev/material-sync/src/cleanup.ts index 6cfa4a098..3ff0e283f 100644 --- a/packages/tdev/material-sync/src/cleanup.ts +++ b/packages/tdev/material-sync/src/cleanup.ts @@ -1,7 +1,7 @@ -import fs from 'fs'; -import path from 'path'; +import fs from 'node:fs'; +import path from 'node:path'; import minimist from 'minimist'; -import { loadMaterialConfig, REPO_ROOT, resolveMaterialConfig } from './helpers'; +import { loadMaterialConfig, REPO_ROOT, resolveMaterialConfig } from './helpers.js'; process.chdir(REPO_ROOT); diff --git a/packages/tdev/material-sync/src/helpers.ts b/packages/tdev/material-sync/src/helpers.ts index cb9e39d2b..a14901a1f 100644 --- a/packages/tdev/material-sync/src/helpers.ts +++ b/packages/tdev/material-sync/src/helpers.ts @@ -1,8 +1,12 @@ -import fs from 'fs'; -import fsp from 'fs/promises'; -import path from 'path'; +import fs from 'node:fs'; +import fsp from 'node:fs/promises'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; import Rsync from 'rsync'; import yaml from 'js-yaml'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + export const REPO_ROOT = path.resolve(__dirname, '..', '..', '..', '..'); type RsyncInstance = InstanceType; diff --git a/packages/tdev/material-sync/src/remove.ts b/packages/tdev/material-sync/src/remove.ts index 08014b5cd..eecdec193 100644 --- a/packages/tdev/material-sync/src/remove.ts +++ b/packages/tdev/material-sync/src/remove.ts @@ -1,5 +1,5 @@ -import fs from 'fs'; -import path from 'path'; +import fs from 'node:fs'; +import path from 'node:path'; import minimist from 'minimist'; import { docBasePath, @@ -10,7 +10,7 @@ import { resolveMaterialConfig, saveMaterialConfig, SyncConfig -} from './helpers'; +} from './helpers.js'; process.chdir(REPO_ROOT); diff --git a/packages/tdev/material-sync/src/restore.ts b/packages/tdev/material-sync/src/restore.ts index a820a233a..456677d18 100644 --- a/packages/tdev/material-sync/src/restore.ts +++ b/packages/tdev/material-sync/src/restore.ts @@ -1,5 +1,5 @@ -import fs from 'fs'; -import { REPO_ROOT } from './helpers'; +import fs from 'node:fs'; +import { REPO_ROOT } from './helpers.js'; process.chdir(REPO_ROOT); diff --git a/packages/tdev/material-sync/src/sync.ts b/packages/tdev/material-sync/src/sync.ts index fa33f100c..bb352dde8 100644 --- a/packages/tdev/material-sync/src/sync.ts +++ b/packages/tdev/material-sync/src/sync.ts @@ -1,5 +1,5 @@ -import fs from 'fs/promises'; -import path from 'path'; +import fs from 'node:fs/promises'; +import path from 'node:path'; import Rsync from 'rsync'; import { ConfigType, @@ -10,7 +10,7 @@ import { pathExists, REPO_ROOT, resolveMaterialConfig -} from './helpers'; +} from './helpers.js'; process.chdir(REPO_ROOT); const typedConfig: ConfigType = loadMaterialConfig(); diff --git a/packages/tdev/material-sync/tsconfig.json b/packages/tdev/material-sync/tsconfig.json index 5f09a804c..7ac893f8a 100644 --- a/packages/tdev/material-sync/tsconfig.json +++ b/packages/tdev/material-sync/tsconfig.json @@ -3,14 +3,13 @@ "compilerOptions": { "outDir": "./dist", "rootDir": ".", - "module": "commonjs", + "module": "NodeNext", "declaration": false, "skipLibCheck": true, "esModuleInterop": true, "allowSyntheticDefaultImports": true, "resolveJsonModule": true, - "ignoreDeprecations": "6.0", - "moduleResolution": "node" + "moduleResolution": "NodeNext" }, "include": ["src/*.ts"], "exclude": ["node_modules", "dist"] From a3565bd59851324f21a6798e6209ef2ae9aacadd Mon Sep 17 00:00:00 2001 From: bh0fer Date: Wed, 1 Jul 2026 15:47:00 +0200 Subject: [PATCH 09/13] add gitkeep --- .../docs/intro/03-class-management/.gitkeep | 1 + .../docs/intro/03-class-management/index.mdx | 149 ------------------ 2 files changed, 1 insertion(+), 149 deletions(-) create mode 100644 tdev-website/docs/intro/03-class-management/.gitkeep delete mode 100644 tdev-website/docs/intro/03-class-management/index.mdx diff --git a/tdev-website/docs/intro/03-class-management/.gitkeep b/tdev-website/docs/intro/03-class-management/.gitkeep new file mode 100644 index 000000000..de4975dff --- /dev/null +++ b/tdev-website/docs/intro/03-class-management/.gitkeep @@ -0,0 +1 @@ +# content will be synced from packages/tdev/material-sync diff --git a/tdev-website/docs/intro/03-class-management/index.mdx b/tdev-website/docs/intro/03-class-management/index.mdx deleted file mode 100644 index 1cb186e96..000000000 --- a/tdev-website/docs/intro/03-class-management/index.mdx +++ /dev/null @@ -1,149 +0,0 @@ ---- -page_id: 9a836e52-2ffc-47aa-9144-b3c247be6bbd ---- - -# Klassenmanagement - -Grundsätzlich werden jegliche Inhalte im Ordner `/docs` erstellt, abgelegt und mit git versioniert. - -Um den Klassen individuell zusammengestellte Inhalte zu präsentieren, wird im `material.config.yaml` festgelegt, welche Inhalte welcher Klasse angezeigt werden. Das Standard-Format sieht wie folgt aus: - -```yaml showLineNumbers -28Gj: - - from: docs/OF-BYOD-Basics/ - to: versioned_docs/version-28Gb/01-BYOD - - from: docs/OF-Word/ - to: versioned_docs/version-28Gb/03-Word - ignore: - - _category_.yml -29Ga: - - from: docs/OF-BYOD-Basics-29/ - to: versioned_docs/version-29Ga/02-BYOD - open: true -``` - -`from` -: Pfad zum Quellordner oder der Quelldatei, die verteilt werden soll. Der Pfad ist relativ zum Projektstammverzeichnis. -`to` -: Pfad zum Zielordner, in dem die Inhalte für die jeweilige Klasse abgelegt werden. Der Pfad ist relativ zum Projektstammverzeichnis. -`ignore` -: *optional* -: Liste von Dateien oder Ordnern, die nicht kopiert werden sollen. Der Pfad ist relativ zum Quellordner. -: ⚠️ aktuell können nur direkt im Quellordner liegende Dateien oder Ordner ignoriert werden. Unterordner werden nicht unterstützt. -`open` -: *optional* -: Gibt an, ob der Ordner in der Navigation standardmässig geöffnet sein soll. In diesem Fall wird ein Datei `_category_.json` im Zielordner erstellt, in dem die Einstellung gespeichert wird. - - -Obige Konfiguration kann auch abgekürzt wie folgt geschrieben werden: - -```yaml showLineNumbers -28Gj: - - material: OF-BYOD-Basics - as: 01-BYOD - - material: OF-Word - as: 03-Word - ignore: - - _category_.yml -29Ga: - - material: OF-BYOD-Basics-29 - as: 02-BYOD - open: true -``` - -`material` -: Pfad zum Quellordner, relativ zum Ordner `/docs`. Der Ordnername wird automatisch dem Pfad `/docs` vorangestellt. -`as` -: Pfad zum Zielordner, relativ zum Ordner `versioned_docs/version-/`. -: *alias* `section` (kann gleichbedeutend zu `as` verwendet werden) - -## CLI - -Um das Material entsprechend der Konfiguration im `material.config.yaml` in die jeweiligen Klassenordner zu kopieren, muss folgender Befehl ausgeführt werden: - -```bash -yarn run sync -``` - -:::details[Scripts in package.json] -Folgende Einträge können in der `package.json` hinzugefügt werden, um die Nutzung zu vereinfachen: -```json -{ - "scripts": { - "sync": "yarn workspace @tdev/material-sync sync", - "remove": "yarn workspace @tdev/material-sync run remove", - "postremove": "yarn workspace @tdev/material-sync sync", - "precleanup": "yarn workspace @tdev/material-sync restore", - "cleanup": "yarn workspace @tdev/material-sync cleanup", - "addClass": "yarn workspace @tdev/material-sync run addClass", - "add": "yarn workspace @tdev/material-sync run add", - "postadd": "yarn workspace @tdev/material-sync sync" - } -} -``` - -Zudem muss sichergestellt werden, dass die Dokumente vor einem Build synchronisiert werden - dies kann entweder in der CI/CD-Pipeline oder in einem Prebuild-Skript geschehen. Beispielsweise kann folgendes in der `package.json` hinzugefügt werden: - -```json -{ - "scripts": { - "prebuild": "yarn workspace @tdev/material-sync sync" - } -} -``` -::: - -### Material Hinzufügen -Um die Inhalte für Klassen leichter zu konfigurieren, kann über die Kommandozeile wie folgt vorgegangen werden: - -```bash -# add /OF-BYOD-Basics to 28Ga & 28Gb as /OF-BYOD-Basics -yarn run add docs/OF-BYOD-Basics/ --to="28Ga,28Gb" -# add /OF-BYOD-Basics to 28Ga & 28Gb as /01-BYOD -yarn run add docs/OF-BYOD-Basics/ --to="28Ga,28Gb" --as="01-BYOD" -# ignoriert _category_.json und alle .txt-Dateien im Quellordner -yarn run add docs/OF-BYOD-Basics/ --to="28Ga,28Gb" --as="01-BYOD" --ignore="_category_.json,*.txt" -``` - -:::tip[Autovervollständigung von Pfaden] -Bash ermöglich das Autovervollständigen von Pfaden und Dateinamen mit der [[tab]]-Taste. Dies kann beim Tippen von Pfaden sehr hilfreich sein. -::: - -### Material synchronisieren - -Das Material wird entsprechend der Konfiguration im `material.config.yaml` in die jeweiligen Klassenordner kopiert. Dies kann über die Kommandozeile wie folgt ausgeführt werden: - -```bash -yarn run sync -``` - -### [Dev] Material aufräumen - -Um im Entwicklungsprozess die Aufstartzeit zu verkürzen, können die übers `material.config.yaml` konfigurierten Inhalte in den Klassenordnern gelöscht werden. Dies kann über die Kommandozeile wie folgt ausgeführt werden: - -```bash -yarn run cleanup -``` - -### Material aus der Konfiguration entfernen -Um Inhalte aus der Konfiguration zu entfernen, kann über die Kommandozeile wie folgt vorgegangen werden: - -```bash -yarn run remove docs/OF-BYOD-Basics/ --from="28Ga,28Gb" -``` - -Alternativ können auch die entsprechenden Zeilen aus dem `material.config.yaml` gelöscht werden. - -## Neue Klasse hinzufügen - -Um eine neue Klasse hinzuzufügen, kann über die Kommandozeile wie folgt vorgegangen werden: - -```bash -yarn run addClass 30Ga -``` - -oder für mehrere Klassen gleichzeitig: - -```bash -yarn run addClass 30Ga,30Gb,30Gc -``` \ No newline at end of file From c12a58764db010994a9b80495ac40f03110bcea3 Mon Sep 17 00:00:00 2001 From: bh0fer Date: Wed, 1 Jul 2026 15:47:37 +0200 Subject: [PATCH 10/13] sync readme to intro --- material.config.yaml | 3 +++ .../material-sync/{README.md => README.mdx} | 0 packages/tdev/material-sync/src/helpers.ts | 6 +----- packages/tdev/material-sync/src/sync.ts | 17 ++++++++++++----- 4 files changed, 16 insertions(+), 10 deletions(-) rename packages/tdev/material-sync/{README.md => README.mdx} (100%) diff --git a/material.config.yaml b/material.config.yaml index defaf830c..3469a95f4 100644 --- a/material.config.yaml +++ b/material.config.yaml @@ -1 +1,4 @@ pages: [] +docs: + - from: packages/tdev/material-sync/README.mdx + to: tdev-website/docs/intro/03-class-management/index.mdx diff --git a/packages/tdev/material-sync/README.md b/packages/tdev/material-sync/README.mdx similarity index 100% rename from packages/tdev/material-sync/README.md rename to packages/tdev/material-sync/README.mdx diff --git a/packages/tdev/material-sync/src/helpers.ts b/packages/tdev/material-sync/src/helpers.ts index a14901a1f..1b9fe6481 100644 --- a/packages/tdev/material-sync/src/helpers.ts +++ b/packages/tdev/material-sync/src/helpers.ts @@ -67,11 +67,7 @@ export const resolveMaterialConfig = (klass: string, config: SyncConfig): Normal to = path.join(destinationBase, config.as); } if ('to' in config && config.to) { - if (config.to.startsWith(destinationBase)) { - to = config.to; - } else { - to = path.join(destinationBase, config.to); - } + to = config.to; } return { from: from!, to: to!, ignore: config.ignore, open: config.open }; diff --git a/packages/tdev/material-sync/src/sync.ts b/packages/tdev/material-sync/src/sync.ts index bb352dde8..be806352d 100644 --- a/packages/tdev/material-sync/src/sync.ts +++ b/packages/tdev/material-sync/src/sync.ts @@ -78,9 +78,16 @@ const main = async (): Promise => { for (const klass of Object.keys(typedConfig)) { const config = typedConfig[klass]; const gitignore: string[] = []; - const classDir = klass === 'pages' ? 'src/pages/' : `versioned_docs/version-${klass}/`; + const classDir = + klass === 'pages' + ? 'src/pages/' + : klass === 'docs' + ? undefined + : `versioned_docs/version-${klass}/`; for (const _config of config) { const config = resolveMaterialConfig(klass, _config); + const resolvedClassDir = classDir ? classDir : config.to.split('/')[0] + '/'; + console.log(JSON.stringify(config, undefined, 2)); const ignore: string[] = []; ignore.push(...(config.ignore || [])); @@ -101,7 +108,7 @@ const main = async (): Promise => { } if (isDir) { - const sanitizedClassDir = ensureTrailingSlash(config.to.replace(classDir, '')); + const sanitizedClassDir = ensureTrailingSlash(config.to.replace(resolvedClassDir, '')); gitignore.push(`${sanitizedClassDir}*`); const rsync = new Rsync() .flags('v') @@ -132,7 +139,7 @@ const main = async (): Promise => { await ensureSync(rsync, srcPath); } else { await fs.copyFile(srcPath, config.to); - gitignore.push(config.to.replace(classDir, '')); + gitignore.push(config.to.replace(resolvedClassDir, '')); } if (config.open) { @@ -144,7 +151,7 @@ const main = async (): Promise => { } const categoryPath = path.join(folder, '_category_.json'); console.log('---------- CAT', categoryPath); - gitignore.push(categoryPath.replace(classDir, '')); + gitignore.push(categoryPath.replace(resolvedClassDir, '')); let category: Record = { collapsible: true, collapsed: false, @@ -159,7 +166,7 @@ const main = async (): Promise => { await fs.writeFile(categoryPath, JSON.stringify(category, undefined, 2) + '\n'); } - await fs.writeFile(`${classDir}.gitignore`, gitignore.join('\n')); + await fs.writeFile(`${resolvedClassDir}.gitignore`, gitignore.join('\n')); } } }; From 499822dbf7d4ed37be144a6cf8eca6cd96320bb7 Mon Sep 17 00:00:00 2001 From: bh0fer Date: Wed, 1 Jul 2026 16:03:53 +0200 Subject: [PATCH 11/13] create matconfig if needed --- packages/tdev/material-sync/src/helpers.ts | 27 ++++++++++++++++++++-- packages/tdev/material-sync/src/sync.ts | 1 - tdev-website/.gitignore | 1 + 3 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 tdev-website/.gitignore diff --git a/packages/tdev/material-sync/src/helpers.ts b/packages/tdev/material-sync/src/helpers.ts index 1b9fe6481..fd16ee3c1 100644 --- a/packages/tdev/material-sync/src/helpers.ts +++ b/packages/tdev/material-sync/src/helpers.ts @@ -74,8 +74,23 @@ export const resolveMaterialConfig = (klass: string, config: SyncConfig): Normal }; export const loadMaterialConfig = (): ConfigType => { - const source = fs.readFileSync(materialConfigPath, 'utf-8'); - return (yaml.load(source) ?? {}) as ConfigType; + const hasFile = pathExistsSync(materialConfigPath); + if (hasFile) { + const source = fs.readFileSync(materialConfigPath, 'utf-8'); + return (yaml.load(source) ?? {}) as ConfigType; + } + fs.writeFileSync( + materialConfigPath, + yaml.dump( + { pages: [] }, + { + noRefs: true, + lineWidth: -1, + sortKeys: false + } + ) + ); + return { pages: [] }; }; export const saveMaterialConfig = (config: ConfigType): void => { @@ -158,3 +173,11 @@ export const pathExists = async (p: string): Promise => { return false; } }; +export const pathExistsSync = (p: string): boolean => { + try { + fs.accessSync(p, fs.constants.R_OK); + return true; + } catch { + return false; + } +}; diff --git a/packages/tdev/material-sync/src/sync.ts b/packages/tdev/material-sync/src/sync.ts index be806352d..1334c1e01 100644 --- a/packages/tdev/material-sync/src/sync.ts +++ b/packages/tdev/material-sync/src/sync.ts @@ -87,7 +87,6 @@ const main = async (): Promise => { for (const _config of config) { const config = resolveMaterialConfig(klass, _config); const resolvedClassDir = classDir ? classDir : config.to.split('/')[0] + '/'; - console.log(JSON.stringify(config, undefined, 2)); const ignore: string[] = []; ignore.push(...(config.ignore || [])); diff --git a/tdev-website/.gitignore b/tdev-website/.gitignore new file mode 100644 index 000000000..39ee8c1da --- /dev/null +++ b/tdev-website/.gitignore @@ -0,0 +1 @@ +docs/intro/03-class-management/index.mdx \ No newline at end of file From c7f8a260806e09f7d9cfb8cba2af5f6af774062a Mon Sep 17 00:00:00 2001 From: bh0fer Date: Wed, 1 Jul 2026 16:22:16 +0200 Subject: [PATCH 12/13] fix prettier ignore --- .prettierignore | 3 ++- material.config.yaml | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.prettierignore b/.prettierignore index 6bc82425a..b62681558 100644 --- a/.prettierignore +++ b/.prettierignore @@ -29,4 +29,5 @@ static/katex/katex.min.css *.mp3 *.jpeg *.jpg -*.png \ No newline at end of file +*.png +material.config.yaml \ No newline at end of file diff --git a/material.config.yaml b/material.config.yaml index 3469a95f4..ebd43a64c 100644 --- a/material.config.yaml +++ b/material.config.yaml @@ -1,4 +1,4 @@ pages: [] docs: - - from: packages/tdev/material-sync/README.mdx - to: tdev-website/docs/intro/03-class-management/index.mdx + - from: packages/tdev/material-sync/README.mdx + to: tdev-website/docs/intro/03-class-management/index.mdx From 0e2a5b256ddf13abc85df10428da66653cf10777 Mon Sep 17 00:00:00 2001 From: bh0fer Date: Wed, 1 Jul 2026 16:42:54 +0200 Subject: [PATCH 13/13] update packages --- package.json | 4 ++-- yarn.lock | 13 +++++++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 0b64b9e63..8be708723 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "micromark-extension-math": "^3.1.0", "micromatch": "^4.0.8", "minimist": "^1.2.8", - "mobx": "^6.15.0", + "mobx": "^6.16.1", "mobx-react-lite": "^4.1.1", "mobx-utils": "^6.1.1", "qrcode.react": "^4.2.0", @@ -119,7 +119,7 @@ "remark-directive": "^4.0.0", "remark-frontmatter": "^5.0.0", "ts-node": "^10.9.2", - "typescript": "^6.0.2", + "typescript": "^6.0.3", "vfile": "^6.0.3", "vitest": "^2.0.5" }, diff --git a/yarn.lock b/yarn.lock index ec5c9e022..9d3a3b69c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12905,10 +12905,10 @@ mobx-utils@^6.1.1: resolved "https://registry.yarnpkg.com/mobx-utils/-/mobx-utils-6.1.1.tgz#61c66563e7646fb75462c189f4110a76d2e35768" integrity sha512-ZR4tOKucWAHOdMjqElRl2BEvrzK7duuDdKmsbEbt2kzgVpuLuoYLiDCjc3QwWQl8CmOlxPgaZQpZ7emwNqPkIg== -mobx@^6.15.0: - version "6.15.0" - resolved "https://registry.yarnpkg.com/mobx/-/mobx-6.15.0.tgz#78b9b82d383724eebb4b6e50c2eb4ae2da861cb5" - integrity sha512-UczzB+0nnwGotYSgllfARAqWCJ5e/skuV2K/l+Zyck/H6pJIhLXuBnz+6vn2i211o7DtbE78HQtsYEKICHGI+g== +mobx@^6.16.1: + version "6.16.1" + resolved "https://registry.yarnpkg.com/mobx/-/mobx-6.16.1.tgz#8b3166186d1298b1848395e10e64b5c43d6dc43b" + integrity sha512-syNcDdX3KT+Jq3je6eGjBhuc24Z68td2VG0zNFqRswaE433D9SNH5VRy/xrGbJsUixfppLLccXhAW9JSf6n+SQ== mri@^1.1.0: version "1.2.0" @@ -16154,6 +16154,11 @@ typescript@^6.0.2: resolved "https://registry.yarnpkg.com/typescript/-/typescript-6.0.2.tgz#0b1bfb15f68c64b97032f3d78abbf98bdbba501f" integrity sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ== +typescript@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-6.0.3.tgz#90251dc007916e972786cb94d74d15b185577d21" + integrity sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw== + ufo@^1.6.3: version "1.6.3" resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.6.3.tgz#799666e4e88c122a9659805e30b9dc071c3aed4f"