feature/ci-docs
claude/smbcloud-github-app-k9641p
development
feature/apps
feature/build-release
feature/ci-docs
feature/ci-ruby-gems
feature/cicd
feature/completion
feature/dist-nuget
feature/fix-ci-sdk-pypi-rubygem
feature/fix-clippy-on-stable-channel
feature/fix-deployment-script
feature/github-email
feature/gresiq-v1
feature/project-new-structure
feature/rsync-deploy
feature/ruby-runner
feature/sdk-npm
feature/sdk-workflows
feature/smbcloud-cli-release-npm
feature/smbcloud-deploy
feature/wasm
main
release/v0.7.4
| 1 | #!/usr/bin/env node |
| 2 | /** |
| 3 | * Generate the /developer docs from the sibling `docs/` tree. |
| 4 | * |
| 5 | * The CLI / tooling documentation lives in `docs/` at the smbcloud-cli repo root |
| 6 | * (this site is `website/` in the same repo), so it is read in place — no clone, |
| 7 | * no network. This runs automatically on `predev`/`prebuild`; the generated |
| 8 | * pages are gitignored, leaving `docs/` as the single source of truth. |
| 9 | * |
| 10 | * pnpm docs:generate |
| 11 | * |
| 12 | * Hand-written index pages (content/developer/**\/index.mdx) are NOT touched. |
| 13 | * Only the files listed in MANIFEST and the generated _meta.js are written. |
| 14 | */ |
| 15 | import { readFileSync, writeFileSync, mkdirSync } from 'node:fs' |
| 16 | import { join, dirname } from 'node:path' |
| 17 | import { fileURLToPath } from 'node:url' |
| 18 | |
| 19 | const BRANCH = 'main' |
| 20 | const SRC_DIR = 'docs' |
| 21 | |
| 22 | const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..') |
| 23 | // `docs/` sits next to `website/` at the smbcloud-cli repo root. |
| 24 | const SRC = join(ROOT, '..', SRC_DIR) |
| 25 | const DEST = join(ROOT, 'content', 'developer') |
| 26 | |
| 27 | // Source file -> where it lands and how it's labelled in the sidebar. |
| 28 | // `mail-service.md` is intentionally excluded: it's a scratch notes file in the |
| 29 | // source repo, not publishable documentation. |
| 30 | const MANIFEST = { |
| 31 | cli: { |
| 32 | title: 'CLI', |
| 33 | pages: [ |
| 34 | { src: 'cli-install.md', slug: 'native', title: 'Native (Cargo)' }, |
| 35 | { src: 'cli-install-homebrew.md', slug: 'homebrew', title: 'Homebrew' }, |
| 36 | { src: 'cli-install-npm.md', slug: 'npm', title: 'npm' }, |
| 37 | { src: 'cli-install-pypi.md', slug: 'pypi', title: 'PyPI' } |
| 38 | ] |
| 39 | }, |
| 40 | contributing: { |
| 41 | title: 'Contributing', |
| 42 | pages: [ |
| 43 | { src: 'debugging.md', slug: 'debugging', title: 'VSCode Debugging' }, |
| 44 | { src: 'development.md', slug: 'development', title: 'Releasing & ADRs' }, |
| 45 | { src: 'gems.md', slug: 'gems', title: 'Publishing the gem' } |
| 46 | ] |
| 47 | } |
| 48 | } |
| 49 | |
| 50 | const blobUrl = src => |
| 51 | `https://github.com/smbcloudXYZ/smbcloud-cli/blob/${BRANCH}/${SRC_DIR}/${src}` |
| 52 | |
| 53 | /** Wrap generated markdown: header note + a source callout after the first H1. */ |
| 54 | function decorate(markdown, src) { |
| 55 | const note = `{/* AUTO-GENERATED — do not edit. Generated by scripts/generate-developer-docs.mjs from ../${SRC_DIR}/${src}. */}` |
| 56 | const callout = [ |
| 57 | "import { Callout } from 'nextra/components'", |
| 58 | '', |
| 59 | '<Callout type="info">', |
| 60 | ` Generated from [\`${SRC_DIR}/${src}\`](${blobUrl(src)}) in`, |
| 61 | ' [smbcloud-cli](https://github.com/smbcloudXYZ/smbcloud-cli). Edit it there.', |
| 62 | '</Callout>' |
| 63 | ].join('\n') |
| 64 | |
| 65 | const lines = markdown.replace(/\r\n/g, '\n').trimEnd().split('\n') |
| 66 | const h1 = lines.findIndex(l => /^#\s/.test(l)) |
| 67 | if (h1 === -1) { |
| 68 | return `${note}\n\n${callout}\n\n${lines.join('\n')}\n` |
| 69 | } |
| 70 | const before = lines.slice(0, h1 + 1).join('\n') |
| 71 | const after = lines.slice(h1 + 1).join('\n') |
| 72 | return `${note}\n\n${before}\n\n${callout}\n${after}\n` |
| 73 | } |
| 74 | |
| 75 | // Note: the folder's own index page uses `asIndexPage` frontmatter, so it must |
| 76 | // NOT be listed as an `index` key here — Nextra surfaces it via the parent. |
| 77 | function metaFile(pages) { |
| 78 | const entries = pages.map(p => ` ${p.slug}: ${JSON.stringify(p.title)},`) |
| 79 | return `export default {\n${entries.join('\n')}\n}\n` |
| 80 | } |
| 81 | |
| 82 | function main() { |
| 83 | let count = 0 |
| 84 | for (const [section, { title, pages }] of Object.entries(MANIFEST)) { |
| 85 | const dir = join(DEST, section) |
| 86 | mkdirSync(dir, { recursive: true }) |
| 87 | for (const page of pages) { |
| 88 | const raw = readFileSync(join(SRC, page.src), 'utf8') |
| 89 | writeFileSync(join(dir, `${page.slug}.mdx`), decorate(raw, page.src)) |
| 90 | count++ |
| 91 | } |
| 92 | writeFileSync(join(dir, '_meta.js'), metaFile(pages)) |
| 93 | console.log(` ${section}: ${pages.length} pages (${title})`) |
| 94 | } |
| 95 | |
| 96 | // Top-level developer nav: each section (the overview is asIndexPage). |
| 97 | const top = [] |
| 98 | for (const [section, { title }] of Object.entries(MANIFEST)) { |
| 99 | top.push(` ${section}: ${JSON.stringify(title)},`) |
| 100 | } |
| 101 | writeFileSync(join(DEST, '_meta.js'), `export default {\n${top.join('\n')}\n}\n`) |
| 102 | |
| 103 | console.log(`\nGenerated ${count} pages into content/developer/ from ${SRC}.`) |
| 104 | } |
| 105 | |
| 106 | main() |