1import { cp, mkdir, readFile, rm, writeFile } from 'node:fs/promises'2import { dirname, join } from 'node:path'3import { fileURLToPath } from 'node:url'4import { diagramTypes } from '../data/diagram-types.mjs'5import { diagramSkills, skillGroups, standardsSupport } from '../../../skills/catalog.mjs'67const root = dirname(fileURLToPath(import.meta.url))8const site = join(root, '..')9const docs = join(site, 'docs')10const typesDir = join(docs, 'create', 'types')11const styleDir = join(docs, 'style', 'types')12const staticDir = join(site, 'static')13const examplesDir = join(staticDir, 'examples')14const repo = join(site, '..', '..')15const skillsDir = join(repo, 'skills')16const publicSkillsDir = join(staticDir, 'skills')1718const completeExampleFixtures = {19 bpmn: 'example.bpmn',20 diagramsnet: 'diagramsnet-venn.xml',21 erd: 'schema.erd',22 excalidraw: 'venn.excalidraw',23 goat: 'components.goat',24 structurizr: 'gettingstarted.structurizr',25 tikz: 'periodic-table.tex',26 umlet: 'umlet.xml',27}2829const completeExamples = Object.fromEntries(await Promise.all(diagramTypes.map(async (type) => {30 const fixture = completeExampleFixtures[type.id]31 const source = fixture32 ? await readFile(join(repo, 'examples', 'diagrams', fixture), 'utf8')33 : (Array.isArray(type.example) ? type.example : []).join('\n')34 return [type.id, {engine: type.id, source, sourceKind: fixture ? 'project-example' : 'page-example'}]35})))3637const fence = (type) => type === 'json' ? 'json' : type === 'xml' ? 'xml' : type === 'yaml' ? 'yaml' : type === 'sql' ? 'sql' : type === 'clojure' ? 'clojure' : 'text'38const lines = (value) => Array.isArray(value) ? value : []39const inline = (value) => String(value ?? '').replace(/\n/g, ' ')40const editorLink = (id) => `https://diagram.zip/?type=${encodeURIComponent(id)}`41const syntaxPath = (id) => `/create/types/${id}`42const stylePath = (id) => `/style/types/${id}`43const articleFor = (label) => /^[aeiou]/i.test(label) ? 'an' : 'a'4445function frontmatterValue(content, key) {46 const block = content.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/)?.[1] ?? ''47 const value = block.match(new RegExp(`^${key}:\\s*(.+)$`, 'm'))?.[1]?.trim()48 if (!value) return undefined49 if (value.startsWith('"') && value.endsWith('"')) {50 try { return JSON.parse(value) } catch { return value.slice(1, -1) }51 }52 if (value.startsWith("'") && value.endsWith("'")) return value.slice(1, -1).replace(/''/g, "'")53 return value54}5556const skillSources = Object.fromEntries(await Promise.all(diagramSkills.map(async (entry) => {57 const content = await readFile(join(skillsDir, entry.id, 'SKILL.md'), 'utf8')58 return [entry.id, {59 ...entry,60 name: frontmatterValue(content, 'name'),61 description: frontmatterValue(content, 'description'),62 userInvokableOnly: frontmatterValue(content, 'disable-model-invocation') === 'true',63 path: `/skills/${entry.id}/SKILL.md`,64 }]65})))6667const skillCatalog = diagramSkills.map(({ id }) => skillSources[id])68const skillIndex = () => skillGroups.map((group) => {69 const entries = skillCatalog.filter((entry) => entry.group === group.id)70 return `## ${group.label}\n\n${entries.map((entry) => `- [${entry.label}](https://docs.diagram.zip${entry.path}): ${entry.description}${entry.userInvokableOnly ? ' Explicit user invocation only.' : ''}`).join('\n')}`71}).join('\n\n')7273function frontmatter(type, section, description) {74 return `---\nid: ${section}-${type.id}\nslug: /${section}/types/${type.id}\ntitle: ${type.label}\ndescription: ${description}\nsidebar_label: ${type.label}\n---\n\n`75}7677function example(type, interactive = true) {78 const content = type.showCompleteExample ? completeExamples[type.id].source.trimEnd() : lines(type.example).join('\n')79 const note = type.exampleComplete === false80 ? 'This source shows the document structure. It is not a complete document.\n\n'81 : ''82 const renderNote = completeExampleFixtures[type.id] && !type.showCompleteExample83 ? 'The rendered view uses a complete project example for this diagram type.\n\n'84 : ''85 const heading = type.exampleComplete === false ? '### Source structure' : '### Example'86 const rendered = interactive87 ? `\n${renderNote}<DiagramExample engine="${type.id}" label="${type.label}" sourceUrl="/examples/${type.id}.json" />\n`88 : ''89 return `${heading}\n\n${note}\`\`\`${fence(type.language)}\n${content}\n\`\`\`\n${rendered}`90}9192function options(items) {93 return `| Option | Values | Purpose |\n| --- | --- | --- |\n${items.map((item) => `| \`${inline(item.name)}\` | ${inline(item.values)} | ${inline(item.description)} |`).join('\n')}\n`94}9596function syntaxPage(type, interactive = true) {97 const limitations = lines(type.limitations)98 const authoringContract = lines(type.authoringContract)99 const article = articleFor(type.label)100 const origin = type.origin101 ? `## Origin\n\n${type.label} originates with [${type.origin.creator}](${type.origin.siteUrl}) at [${type.origin.site}](${type.origin.siteUrl}). ${type.origin.note}\n\n`102 : ''103 return frontmatter(type, 'create', `Create ${article} ${type.label} diagram in diagram.zip.`) +104`${interactive ? "import DiagramExample from '@site/src/components/DiagramExample';\n\n" : ''}# ${type.label}\n\n${type.summary}\n\n## Use this type\n\n${type.use}\n\n## Source format\n\nThe source format is **${type.format}**. Enter the source in the diagram.zip editor.\n\n## Syntax essentials\n\n${lines(type.syntax).map((item) => `- ${item}`).join('\n')}\n\n${example(type, interactive)}\n` +105(authoringContract.length ? `\n## Public authoring contract\n\n${authoringContract.map((item) => `- ${item}`).join('\n')}\n` : '') +106(origin) +107(limitations.length ? `## Limitations\n\n${limitations.map((item) => `- ${item}`).join('\n')}\n\n` : '') +108`## Related pages\n\n- [Style ${type.label}](${stylePath(type.id)})\n- [General presentation settings](/style/presentation)\n- [Open ${type.label} in the editor](${editorLink(type.id)})\n\n## Upstream reference\n\n[${type.label} documentation](${type.upstream})\n`109}110111function stylePage(type) {112 const limitations = lines(type.limitations)113 const article = articleFor(type.label)114 const rendererOptions = type.rendererOptions?.length115 ? `\n## Renderer options\n\n${options(type.rendererOptions)}\n`116 : ''117 return frontmatter(type, 'style', `Style a ${type.label} diagram in diagram.zip.`) +118`# Style ${type.label}\n\n${type.styleSummary}\n\nStart with the [general presentation settings](/style/presentation). Edit shared or raw presentation in Details, and keep renderer-specific styling in Source.\n\n## Source controls\n\n${lines(type.sourceStyle).map((item) => `- ${item}`).join('\n')}\n${rendererOptions}` +119(limitations.length ? `## Limitations\n\n${limitations.map((item) => `- ${item}`).join('\n')}\n\n` : '') +120`## Related pages\n\n- [Create ${article} ${type.label} diagram](${syntaxPath(type.id)})\n- [General presentation settings](/style/presentation)\n- [SVG normalization and version contracts](/style/svg-normalization)\n- [Open ${type.label} in the editor](${editorLink(type.id)})\n`121}122123const catalog = diagramTypes.map((type) => ({124 id: type.id,125 label: type.label,126 ...(type.fullName === undefined ? {} : { fullName: type.fullName }),127 category: type.category,128 format: type.format,129 language: type.language,130 upstream: type.upstream,131 create: syntaxPath(type.id),132 style: stylePath(type.id),133 editor: editorLink(type.id),134 example: `/examples/${type.id}.json`,135 ...(type.origin === undefined ? {} : { origin: type.origin }),136 ...(type.authoringContract === undefined ? {} : { authoringContract: type.authoringContract }),137}))138const coreDocPaths = [139 join(docs, 'create', 'index.md'),140 join(docs, 'style', 'presentation.md'),141 join(docs, 'style', 'svg-normalization.md'),142 join(docs, 'collaboration', 'sharing.md'),143 join(docs, 'collaboration', 'encryption.md'),144 join(docs, 'collaboration', 'working-and-saved-state.md'),145]146const fullText = async () => {147 const coreText = await Promise.all(coreDocPaths.map((path) => readFile(path, 'utf8')))148 const rendererText = diagramTypes.map((type) => `${syntaxPage(type, false)}\n${stylePage(type)}`)149 return [...coreText, ...rendererText].join('\n---\n\n')150}151152await rm(join(docs, 'create', 'types'), { recursive: true, force: true })153await rm(join(docs, 'style', 'types'), { recursive: true, force: true })154await rm(examplesDir, { recursive: true, force: true })155await rm(publicSkillsDir, { recursive: true, force: true })156await mkdir(typesDir, { recursive: true })157await mkdir(styleDir, { recursive: true })158await mkdir(staticDir, { recursive: true })159await mkdir(examplesDir, { recursive: true })160await mkdir(publicSkillsDir, { recursive: true })161162await Promise.all(diagramTypes.flatMap((type) => [163 writeFile(join(typesDir, `${type.id}.md`), syntaxPage(type)),164 writeFile(join(styleDir, `${type.id}.md`), stylePage(type)),165 writeFile(join(examplesDir, `${type.id}.json`), `${JSON.stringify(completeExamples[type.id], null, 2)}\n`),166]))167await Promise.all(diagramSkills.map(({ id }) => cp(join(skillsDir, id), join(publicSkillsDir, id), { recursive: true })))168await writeFile(join(staticDir, 'diagram-types.json'), `${JSON.stringify({ version: 1, diagramTypes: catalog }, null, 2)}\n`)169await writeFile(join(staticDir, 'diagram-skills.json'), `${JSON.stringify({ version: 1, groups: skillGroups, skills: skillCatalog, standards: standardsSupport }, null, 2)}\n`)170await writeFile(join(staticDir, 'llms.txt'), `# diagram.zip documentation\n\n> Choose the semantic story first. Use Source for notation and Details for metadata and presentation. Generated SVG and renderer internals are output, not authoring controls.\n\n- [Diagram routing skill](https://docs.diagram.zip/skills/diagramming/SKILL.md): Select a diagram family from the reader's question.\n- [Machine-readable semantic catalog](https://docs.diagram.zip/diagram-skills.json): Inspect every skill, use case, exclusion, role, and invocation constraint.\n\n${skillIndex()}\n\n## Renderer and source formats\n\n${diagramTypes.map((type) => `- [${type.label} syntax](https://docs.diagram.zip${syntaxPath(type.id)}/): ${type.fullName === undefined ? '' : `${type.label} stands for ${type.fullName}. `}Create ${articleFor(type.label)} ${type.label} diagram. [Style](https://docs.diagram.zip${stylePath(type.id)}/).${type.origin === undefined ? '' : ` Origin: [${type.origin.creator} at ${type.origin.site}](${type.origin.siteUrl}).`}`).join('\n')}\n\n## General pages\n\n- [Editor workspace](https://docs.diagram.zip/create/)\n- [Editable SVG files and local drafts](https://docs.diagram.zip/create/): Reopen validated enriched SVG files without publishing.\n- [Details JSON and presentation](https://docs.diagram.zip/style/presentation/)\n- [SVG normalization and version contracts](https://docs.diagram.zip/style/svg-normalization/)\n- [Share a diagram](https://docs.diagram.zip/collaboration/sharing/)\n- [Password encryption](https://docs.diagram.zip/collaboration/encryption/)\n- [Working state and saved state](https://docs.diagram.zip/collaboration/working-and-saved-state/)\n\n## Structured catalogs\n\n- [diagram-skills.json](https://docs.diagram.zip/diagram-skills.json)\n- [diagram-types.json](https://docs.diagram.zip/diagram-types.json)\n- [Full documentation text](https://docs.diagram.zip/llms-full.txt)\n- [Source repository](https://github.com/FelineStateMachine/diagramzip)\n`)171const llmsPath = join(staticDir, 'llms.txt')172const llmsIndex = await readFile(llmsPath, 'utf8')173await writeFile(llmsPath, llmsIndex.replace('Working state and saved state', 'Working state and published state'))174await writeFile(join(staticDir, 'llms-full.txt'), `# diagram.zip documentation\n\n${await fullText()}`)175176console.log(`Generated ${diagramTypes.length} renderer guides, ${diagramSkills.length} semantic skill packages, and machine-readable indexes.`)