import assert from 'node:assert/strict'; import {existsSync, readFileSync, readdirSync} from 'node:fs'; import {join} from 'node:path'; import {XMLValidator} from 'fast-xml-parser'; const root = process.cwd(); const read = (path) => readFileSync(join(root, path), 'utf8'); const config = read('docusaurus.config.ts'); const packageJson = JSON.parse(read('package.json')); const globalCss = read('src/css/custom.css'); const homepageCss = read('src/pages/index.module.css'); assert.match(config, /defaultMode:\s*'dark'/, 'dark must remain the default theme'); assert.match(config, /disableSwitch:\s*false/, 'theme switch must be enabled'); assert.match(globalCss, /\[data-theme=['"]light['"]\]/, 'global CSS needs an explicit light palette'); assert.match(homepageCss, /:global\(\[data-theme=['"]light['"]\]\)/, 'homepage needs a light palette'); assert.doesNotMatch(globalCss, /html,\s*body\s*\{\s*background:\s*#08101b/, 'document background must not be hard-coded dark'); for (const section of ['projects', 'teams', 'members', 'board', 'governance']) { assert.match(config, new RegExp(`id:\\s*'${section}'`), `${section} docs plugin is missing`); assert.match(config, new RegExp(`sidebarId:\\s*'${section}Sidebar'`), `${section} navbar item does not target its dedicated sidebar`); assert.ok(existsSync(join(root, `sidebars/${section}.ts`)), `${section} sidebar file is missing`); } assert.match(config, /id:\s*'architecture-high-level'/, 'Architecture High Level docs plugin is missing'); assert.match(config, /sidebarId:\s*'architectureHighLevelSidebar'/, 'Architecture High Level navbar item does not target its dedicated sidebar'); assert.match(config, /label:\s*'Architecture'/, 'Architecture navbar label is missing'); assert.doesNotMatch(config, /label:\s*'Architecture High Level'/, 'legacy Architecture High Level menu label must not remain'); assert.ok(existsSync(join(root, 'sidebars/architecture-high-level.ts')), 'Architecture High Level sidebar file is missing'); const expectedLeftNavLabels = ['Projects', 'Teams', 'Members', 'Board', 'Governance', 'Architecture']; const leftNavLabels = [...config.matchAll(/\{type:\s*'docSidebar',[^\n]+label:\s*'([^']+)',\s*position:\s*'left'\}/g)] .map((match) => match[1]); assert.deepEqual(leftNavLabels, expectedLeftNavLabels, 'Architecture must be the final left-side menu item'); assert.match(config, /baseUrl:\s*'\/corp-v1\/portal\/'/, 'Pages base URL must follow the renamed repository'); assert.match(config, /projectName:\s*'portal'/, 'Docusaurus project name must be portal'); assert.doesNotMatch(config, /sterring-commitee/, 'legacy repository name must not remain in Docusaurus config'); assert.doesNotMatch(config, /Pilot Model|docs\/pilot|routeBasePath:\s*'pilot'/, 'legacy Pilot Model navigation must not remain'); const board = read('docs/board/index.md'); for (const memberId of ['1518725627845283888', '476287310627864587', '773109855497093191', '364383883778850827']) { assert.ok(board.includes(memberId), `board page is missing Discord member ${memberId}`); } assert.match(board, /Only these Board members may approve a project kickoff or project closure/i); assert.match(board, /Only these Board members may approve changes to `corp-v1--main`/i); const lifecycle = read('docs/governance/project-lifecycle.md'); assert.match(lifecycle, /Board approval/i, 'project lifecycle must require Board approval'); assert.match(lifecycle, /central project-channel reference skill/i, 'lifecycle must govern central channel-reference skill changes'); assert.match(lifecycle, /project-adopted channel skill/i, 'lifecycle must require project skill synchronization'); for (const section of ['projects', 'teams', 'members']) { const sidebar = read(`sidebars/${section}.ts`); assert.match(sidebar, /label:\s*'Registry'/, `${section} sidebar registry label must be concise`); assert.doesNotMatch(sidebar, /label:\s*'(Projects|Teams|Members) Registry'/, `${section} sidebar must not repeat its section name`); } assert.ok(packageJson.dependencies?.['@easyops-cn/docusaurus-search-local'], 'local search dependency is missing'); assert.match(config, /@easyops-cn\/docusaurus-search-local/, 'local search theme is not configured'); assert.ok(packageJson.dependencies?.['docusaurus-plugin-drawio'], 'Draw.io plugin dependency is missing'); assert.ok(packageJson.devDependencies?.['raw-loader'], 'Draw.io raw-loader dependency is missing'); assert.match(config, /\['drawio',\s*\{lib:\s*'\/corp-v1\/portal\/drawio\/viewer\.min\.js'\}\]/, 'Draw.io plugin must use the self-hosted viewer'); assert.ok(existsSync(join(root, 'static/drawio/viewer.min.js')), 'self-hosted Draw.io viewer is missing'); const drawioLicense = read('static/drawio/LICENSE'); assert.match(drawioLicense, /MIT License/, 'vendored Draw.io viewer license is missing'); assert.match(drawioLicense, /Copyright \(c\) 2022 xiguaxigua/, 'vendored Draw.io viewer copyright notice is missing'); assert.match(config, /indexPages:\s*true/, 'custom pages must be indexed'); const searchRoutesMatch = config.match(/docsRouteBasePath:\s*\[([^\]]+)\]/); assert.ok(searchRoutesMatch, 'search route configuration is missing'); const searchRoutes = searchRoutesMatch[1]; for (const route of ['/', 'architecture-high-level', 'projects', 'teams', 'members', 'board', 'governance']) { assert.ok(searchRoutes.includes(`'${route}'`), `search does not include route base ${route}`); } const architectureSidebar = read('sidebars/architecture-high-level.ts'); const expectedArchitectureSidebar = `import type {SidebarsConfig} from '@docusaurus/plugin-content-docs'; const sidebars: SidebarsConfig = { architectureHighLevelSidebar: [ {type: 'doc', id: 'context', label: 'Context'}, {type: 'doc', id: 'overview', label: 'Container Overview'}, {type: 'doc', id: 'operating-model', label: 'Operating Model'}, {type: 'doc', id: 'overview-channels', label: 'Overview Channels'}, {type: 'doc', id: 'overview-skills', label: 'Overview Skills'}, {type: 'doc', id: 'overview-repositories', label: 'Overview Repositories'}, ], }; export default sidebars;`; assert.equal( architectureSidebar.trim(), expectedArchitectureSidebar, 'Architecture High Level sidebar must contain exactly the six requested documents in order', ); const architecturePages = { context: read('docs/architecture-high-level/context.mdx'), overview: read('docs/architecture-high-level/overview.mdx'), operatingModel: read('docs/architecture-high-level/operating-model.mdx'), channels: read('docs/architecture-high-level/overview-channels.mdx'), skills: read('docs/architecture-high-level/overview-skills.mdx'), repositories: read('docs/architecture-high-level/overview-repositories.mdx'), }; for (const [name, page] of Object.entries(architecturePages)) { assert.match(page, /!!raw-loader!\.\/diagrams\/.+\.drawio/, `${name} page must render an editable Draw.io source`); } for (const term of ['seven channel skills', 'seven engineering skills', 'Gitea Actions', 'scheduler jobs', 'corp-v1-board', 'corp-v1-guild', 'corp-v1--guild', 'UI/UX', 'one AI agent', 'Hermes Agent', 'MicroK8s', 'Penpot', 'Argo CD', 'Harbor']) { assert.ok(Object.values(architecturePages).some((page) => page.includes(term)), `architecture explanation is missing: ${term}`); } assert.match(architecturePages.channels, /two central portfolio workspaces/i, 'channels overview must distinguish Board and Guild as central workspaces'); assert.match(architecturePages.channels, /neither is an eighth project channel/i, 'Guild must not be classified as an eighth project channel'); assert.match(architecturePages.skills, /not copied as a fifteenth project skill/i, 'Guild must remain central rather than project-adopted'); assert.doesNotMatch(architecturePages.channels, /one portfolio-governance channel/i, 'superseded single-central-workspace wording must not remain'); for (const term of ['Corp v1 Board members', 'Project leads and contributors', 'Prospective project sponsors and contributors', 'Solution users and reviewers']) { assert.ok(architecturePages.context.includes(term), `system context is missing actor: ${term}`); } for (const term of ['only two external software systems', 'LLM Provider', 'ICA or GPT Codex', 'Communication Service — Discord']) { assert.ok(architecturePages.context.includes(term), `system context is missing boundary rule: ${term}`); } for (const term of ['Gitea Actions runners', 'Penpot', 'LEGO Cloud Pages', 'Harbor', 'Argo CD', 'Identity and access', 'Ingress edge', 'Gondor v1 MicroK8s']) { assert.ok(architecturePages.overview.includes(term), `container overview is missing internal container: ${term}`); } for (const term of ['corp-v1--main', 'corp-v1-channel-ui-ux', 'development-gitops-argo-cd', 'drawio-main', 'systematic-debugging', 'gondor-v1-nodes']) { assert.ok(architecturePages.skills.includes(term), `skills overview is missing: ${term}`); } for (const [name, page] of Object.entries(architecturePages)) { assert.doesNotMatch(page, /exactly six|six project channels|six channel skills|thirteen adopted skill/i, `${name} retains the superseded six-channel model`); } for (const [name, markers] of Object.entries({ 'context.drawio': ['CORP V1 — HOME-LAB SYSTEM CONTEXT', 'HUMAN ACTORS', 'TARGET SYSTEM', 'EXTERNAL SYSTEMS · ONLY TWO', 'DISCORD', 'ICA OR GPT CODEX'], 'architecture-overview.drawio': ['CORP V1 — HOME-LAB CONTAINER OVERVIEW', 'EXTERNAL · DISCORD', 'EXTERNAL · LLM PROVIDER', 'INTERNAL CONTAINERS', 'GITEA ACTIONS RUNNERS', 'HARBOR', 'ARGO CD', 'GONDOR MICROK8S'], 'operating-model.drawio': ['CORP V1 — OPERATING MODEL FLOW', 'CORP-V1-BOARD', 'BOARD APPROVAL', 'UI/UX', 'KANBAN / FOCUS', 'VERIFY & RECORD'], 'channels.drawio': ['CORP V1 — BOARD, GUILD & PROJECT CHANNELS', 'CORP-V1-BOARD', 'CORP-V1-GUILD', 'GENERAL', 'ARCHITECTURE', 'UI/UX', 'RELEASES', 'MULTIPLE FOCUSED SESSIONS'], 'skills.drawio': ['CORP V1 — SKILLS MODEL', 'CENTRAL SOURCES', 'CORP-V1--GUILD', 'DELIBERATE ADOPTION', 'PROJECT COPIES', 'FOCUSED SESSION'], 'repositories.drawio': ['CORP V1 — ORGANIZATIONS, REPOSITORIES & DELIVERY', 'PROJECT ORGANIZATION', 'SKILLS ORGANIZATION', '7 adopted channel-skill repos', 'GITEA ACTIONS'], })) { const diagram = read(`docs/architecture-high-level/diagrams/${name}`); const xmlResult = XMLValidator.validate(diagram); assert.equal(xmlResult, true, `${name} is malformed XML: ${JSON.stringify(xmlResult)}`); assert.match(diagram, /]/, `${name} is not a Draw.io source file`); for (const marker of markers) assert.ok(diagram.includes(marker), `${name} is missing: ${marker}`); assert.doesNotMatch(diagram, /Six purpose-specific|6 channel skills|6 adopted channel-skill repos/i, `${name} retains the superseded six-channel model`); } if (existsSync(join(root, 'build'))) { const filename = readdirSync(join(root, 'build')).find((name) => /^search-index(?:-[a-f0-9]+)?\.json$/.test(name)); assert.ok(filename, 'production search index is missing'); const search = readFileSync(join(root, 'build', filename), 'utf8').toLowerCase(); for (const term of ['projects registry', 'teams registry', 'members registry', 'board members', 'project lifecycle', 'corp v1 board', 'container overview', 'operating model', 'overview channels', 'overview skills', 'overview repositories']) { assert.ok(search.includes(term), `search index is missing: ${term}`); } } console.log('Portal behavior checks passed.');