| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115 |
- /**
- * Generate `docs/module-graph.md` from in-repo `peerDependencies`, the canonical
- * runtime edges. The deterministic output groups packages by directory and
- * renders both Mermaid and a dependency table; `--check` verifies freshness.
- */
- import { resolve } from 'node:path'
- import { readFileSync, writeFileSync } from 'node:fs'
- import {
- collectPackageGraph,
- escapeMermaidLabel as escLabel,
- graphNodeId as nodeId,
- type PackageGraphNode,
- } from './package-graph.ts'
- const root = resolve(import.meta.dirname, '..')
- const OUT = 'docs/module-graph.md'
- type Pkg = PackageGraphNode
- const GROUP_ORDER = [
- 'util',
- 'llm',
- 'core',
- 'bash',
- 'fs',
- 'skill',
- 'compact',
- 'subagent',
- 'web',
- 'spill',
- 'timeout',
- 'todo',
- 'cordis',
- 'hooks',
- 'session-persistence',
- 'session-query',
- 'support',
- 'ui',
- ]
- function packageLink(pkg: Pkg): string {
- return `[\`${pkg.short}\`](../${pkg.rel})`
- }
- /** Render the full docs/module-graph.md content (pure, deterministic). */
- function render(pkgs: Pkg[]): string {
- const edges: string[] = []
- for (const p of pkgs) {
- for (const d of p.deps) edges.push(` ${nodeId('pkg', p.short)} --> ${nodeId('pkg', d)}`)
- }
- const byShort = new Map(pkgs.map(pkg => [pkg.short, pkg]))
- const groups = [...new Set(pkgs.map(pkg => pkg.group))].sort((a, b) => {
- const ia = GROUP_ORDER.indexOf(a)
- const ib = GROUP_ORDER.indexOf(b)
- const na = ia === -1 ? Number.MAX_SAFE_INTEGER : ia
- const nb = ib === -1 ? Number.MAX_SAFE_INTEGER : ib
- return na - nb || a.localeCompare(b)
- })
- const groupBlocks: string[] = []
- for (const group of groups) {
- groupBlocks.push(` subgraph ${nodeId('group', group)}["packages/${escLabel(group)}"]`)
- for (const pkg of pkgs.filter(p => p.group === group).sort((a, b) => a.short.localeCompare(b.short))) {
- groupBlocks.push(` ${nodeId('pkg', pkg.short)}["${escLabel(pkg.short)}"]`)
- }
- groupBlocks.push(' end')
- }
- const rows = pkgs.map((p) => {
- const deps = p.deps.length ? p.deps.map((d) => {
- const dep = byShort.get(d)
- return dep ? packageLink(dep) : `\`${d}\``
- }).join(', ') : '—'
- return `| ${packageLink(p)} | \`${p.group}\` | ${deps} |`
- })
- return [
- '<!-- Generated by scripts/gen-module-graph.ts — do not edit by hand.',
- ' Run `pnpm run gen-module-graph` to regenerate. -->',
- '',
- '# Module dependency graph',
- '',
- 'Inter-package dependencies among the `@deepseek-ai/dsh-*` harness packages, derived from each package\'s `peerDependencies` (the canonical runtime-dependency signal) and grouped by the `packages/<group>/<pkg>` hierarchy. An edge `a --> b` means package `a` depends on package `b`. Names have the `@deepseek-ai/dsh-` prefix stripped.',
- '',
- '```mermaid',
- 'flowchart TD',
- ...groupBlocks,
- ...edges,
- '```',
- '',
- '| Package | Group | Depends on |',
- '| --- | --- | --- |',
- ...rows,
- '',
- ].join('\n')
- }
- const content = render(collectPackageGraph(root, GROUP_ORDER, 'gen-module-graph'))
- if (process.argv.includes('--check')) {
- let committed: string | null = null
- try {
- committed = readFileSync(resolve(root, OUT), 'utf8')
- } catch {
- // A missing artifact is the expected read failure. Any read failure has the
- // same remedy here—regenerate—so it is reported as stale below.
- committed = null
- }
- if (committed === content) {
- console.log(`gen-module-graph: ${OUT} is up to date.`)
- process.exit(0)
- }
- console.error(`gen-module-graph: ${OUT} is stale. Run \`pnpm run gen-module-graph\` and commit ${OUT}.`)
- process.exit(1)
- }
- writeFileSync(resolve(root, OUT), content)
- console.log(`gen-module-graph: wrote ${OUT}.`)
|