@blaaiz/docs-core 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-3ZX4WIE3.js → chunk-JHN5XQPU.js} +184 -45
- package/dist/chunk-JHN5XQPU.js.map +1 -0
- package/dist/generator.cjs +114 -44
- package/dist/generator.cjs.map +1 -1
- package/dist/generator.js +20 -3
- package/dist/generator.js.map +1 -1
- package/dist/index.cjs +182 -42
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +108 -7
- package/dist/index.d.ts +108 -7
- package/dist/index.js +1 -1
- package/dist/ui.cjs +1 -1
- package/dist/ui.cjs.map +1 -1
- package/dist/ui.js +1 -1
- package/dist/ui.js.map +1 -1
- package/package.json +1 -1
- package/skills/SKILL.md +59 -45
- package/dist/chunk-3ZX4WIE3.js.map +0 -1
package/dist/generator.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/generator/index.ts"],"names":[],"mappings":"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","file":"generator.js","sourcesContent":["/**\n * `@blaaiz/docs-core/generator` — builds the docs content tree from `docs.json`.\n *\n * One call turns the site's navigation file into everything fumadocs needs:\n * generated API pages placed in their configured groups, and `meta.json` files\n * encoding tab and group order. Authors reorganize the docs by editing\n * `docs.json` — never by moving files.\n *\n * Node-only. A site that publishes an API reference injects `createOpenAPI` and\n * `generateFiles` from its own `fumadocs-openapi` install, so this package takes\n * no dependency on it. A site of prose alone injects nothing: with no API pages\n * in `docs.json` the generator writes the `meta.json` files and `nav.json` and\n * never touches `fumadocs-openapi`.\n *\n * @packageDocumentation\n */\n\nimport { mkdir, readFile, rm, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { mergeOpenApiDocuments } from '../adapters/index.js';\nimport { parseNavigation } from '../adapters/index.js';\nimport type { NavItem, OpenApiDocument, OpenApiInfo, OpenApiServer } from '../core/index.js';\nimport { planDocsTree } from '../core/index.js';\n\n/**\n * The two fumadocs-openapi functions the generator needs, injected by the site\n * (`createOpenAPI` from `fumadocs-openapi/server`, `generateFiles` from\n * `fumadocs-openapi`). Typed loosely so this package needs no fumadocs types.\n *\n * @public\n */\nexport interface FumadocsOpenApi {\n readonly createOpenAPI: (...args: never[]) => unknown;\n readonly generateFiles: (...args: never[]) => unknown;\n}\n\n/**\n * Options for {@link generateDocsTree}.\n *\n * @public\n */\nexport interface GenerateDocsTreeOptions {\n /** The site's content directory (holds `docs.json`, spec files, and `docs/`). */\n readonly contentDir: string;\n /** Path to the navigation file. Default `<contentDir>/docs.json`. */\n readonly docsJson?: string;\n /** Output directory for the page tree. Default `<contentDir>/docs`. */\n readonly outDir?: string;\n /** Folder for generated API pages. Default `api`. */\n readonly apiDir?: string;\n /**\n * `info` block of the merged OpenAPI document. Required when `docs.json`\n * publishes API pages; ignored on a site of prose alone, which merges no\n * documents.\n */\n readonly info?: OpenApiInfo;\n /** Servers of the merged document. */\n readonly servers?: readonly OpenApiServer[];\n /** Same-origin proxy route for the try-it playground. */\n readonly proxyUrl?: string;\n /** URL base the fumadocs loader serves pages under. Default `/docs`. */\n readonly baseUrl?: string;\n /**\n * The site's fumadocs-openapi functions. Required when `docs.json` publishes\n * API pages; omit it on a site of prose alone.\n */\n readonly fumadocs?: FumadocsOpenApi;\n}\n\n/**\n * A top-navigation link: one tab, pointing at its first page.\n *\n * @public\n */\nexport interface NavLink {\n readonly text: string;\n readonly url: string;\n /** The tab's icon name from `docs.json`, for the site to resolve. */\n readonly icon?: string;\n}\n\n/**\n * What the generator produced.\n *\n * @public\n */\nexport interface GenerateDocsTreeResult {\n readonly pages: number;\n readonly metas: number;\n /**\n * Top-nav links, one per tab, each pointing at the tab's first page — written\n * to `<contentDir>/nav.json` for the site's layout. Keeps the top nav in sync\n * with `docs.json` instead of a hand-maintained list.\n */\n readonly navLinks: readonly NavLink[];\n /**\n * Page URL to HTTP method, written to `<contentDir>/api-methods.json` for the\n * sidebar method badges (`apiMethodBadges` in `@blaaiz/docs-core/ui`).\n */\n readonly methodsByUrl: Readonly<Record<string, string>>;\n /** The merged OpenAPI document (reusable for the runtime page renderer). */\n readonly document: OpenApiDocument;\n}\n\ninterface GeneratedFile {\n path: string;\n content: string;\n}\n\nfunction slugify(value: string): string {\n return value\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '');\n}\n\n/** The `METHOD path` key of the first operation a generated page renders. */\nfunction operationKeyOf(content: string): string | null {\n const match = /operations=\\{(\\[[\\s\\S]*?\\])\\}/.exec(content);\n if (!match?.[1]) {\n return null;\n }\n try {\n const ops = JSON.parse(match[1]) as { path: string; method: string }[];\n const op = ops[0];\n return op ? `${op.method.toUpperCase()} ${op.path}` : null;\n } catch {\n return null;\n }\n}\n\n/**\n * The `info` a docs-only run reports on its (empty) merged document. Nothing\n * renders it: with no API pages there is no reference view to title.\n */\nconst PROSE_ONLY_INFO: OpenApiInfo = { title: 'No API reference', version: '0.0.0' };\n\n/**\n * The fumadocs functions, checked. Called only when the navigation publishes\n * API pages, so the message can name the exact reason they are needed.\n */\nfunction requireFumadocs(value: unknown, apiPageCount: number): FumadocsOpenApi {\n const because = `docs.json publishes ${String(apiPageCount)} API page(s)`;\n if (typeof value !== 'object' || value === null) {\n throw new Error(\n `[docs-core] generateDocsTree: the 'fumadocs' option is required because ${because}. ` +\n \"Pass { createOpenAPI, generateFiles } from the site's fumadocs-openapi install.\",\n );\n }\n const candidate = value as Record<string, unknown>;\n for (const name of ['createOpenAPI', 'generateFiles'] as const) {\n if (typeof candidate[name] !== 'function') {\n throw new Error(\n `[docs-core] generateDocsTree: fumadocs.${name} must be a function because ${because}. ` +\n `Import ${name} from ${name === 'createOpenAPI' ? \"'fumadocs-openapi/server'\" : \"'fumadocs-openapi'\"}.`,\n );\n }\n }\n return value as FumadocsOpenApi;\n}\n\n/**\n * Build the docs content tree from `docs.json`.\n *\n * Reads the navigation, merges every referenced spec, generates one page per\n * declared endpoint into its configured group folder, and writes the\n * `meta.json` files that give the sidebar the navigation's exact structure and\n * order. Operations present in a spec but not declared in `docs.json` get no\n * page — the navigation decides what is published, as on Mintlify.\n *\n * A site of prose alone needs neither option: with no API pages in `docs.json`\n * the run writes the `meta.json` files, `nav.json`, and an empty\n * `api-methods.json`, and never loads `fumadocs-openapi`.\n *\n * @example\n * ```ts\n * // A site of prose alone.\n * await generateDocsTree({ contentDir: 'content' });\n * ```\n *\n * @param options - directories, document info, and the injected fumadocs functions\n * @returns counts and the merged document\n * @throws when `docs.json` publishes API pages and `fumadocs` or `info` is missing\n * @public\n */\nexport async function generateDocsTree(\n options: GenerateDocsTreeOptions,\n): Promise<GenerateDocsTreeResult> {\n const docsJson = options.docsJson ?? path.join(options.contentDir, 'docs.json');\n const outDir = options.outDir ?? path.join(options.contentDir, 'docs');\n const apiDir = options.apiDir ?? 'api';\n\n const navigation = parseNavigation(JSON.parse(await readFile(docsJson, 'utf8')));\n const plan = planDocsTree(navigation, { apiDir });\n\n // Where each planned API page lands. Both stay empty on a prose-only site.\n const placements = new Map<string, { dir: string; order: number; slug: string }>();\n const groupPages = new Map<string, { order: number; slug: string }[]>();\n let pages = 0;\n let document: OpenApiDocument;\n\n if (plan.apiPages.length === 0) {\n // A site of prose alone. Nothing to merge, nothing to generate, and — most\n // importantly — nothing removed: a hand-written folder named `api` is the\n // site's own content, not our output.\n document = mergeOpenApiDocuments([], { info: options.info ?? PROSE_ONLY_INFO });\n } else {\n const fumadocs = requireFumadocs(options.fumadocs, plan.apiPages.length);\n if (options.info === undefined) {\n throw new Error(\n `[docs-core] generateDocsTree: the 'info' option is required because docs.json ` +\n `publishes ${String(plan.apiPages.length)} API page(s). ` +\n 'Pass { title, version } for the merged OpenAPI document.',\n );\n }\n\n // Merge exactly the specs the navigation references.\n const docs = await Promise.all(\n plan.specs.map(\n async (file) =>\n JSON.parse(await readFile(path.join(options.contentDir, file), 'utf8')) as unknown,\n ),\n );\n document = mergeOpenApiDocuments(docs, {\n info: options.info,\n ...(options.servers ? { servers: options.servers } : {}),\n });\n\n // Resolve each planned page's slug from the merged document.\n for (const page of plan.apiPages) {\n const operation = document.paths[page.apiPath]?.[page.method.toLowerCase()] as\n { operationId?: string } | undefined;\n if (!operation) {\n throw new Error(\n `[docs-core] docs.json references ${page.method} ${page.apiPath}, ` +\n `but ${page.file} defines no such operation.`,\n );\n }\n const slug = operation.operationId ?? slugify(`${page.method} ${page.apiPath}`);\n placements.set(`${page.method.toUpperCase()} ${page.apiPath}`, { ...page, slug });\n const group = groupPages.get(page.dir) ?? [];\n group.push({ order: page.order, slug });\n groupPages.set(page.dir, group);\n }\n\n // Fresh API tree, then generate pages and route each into its planned folder.\n await rm(path.join(outDir, apiDir), { recursive: true, force: true });\n const createOpenAPI = fumadocs.createOpenAPI as (input: unknown) => unknown;\n const generateFiles = fumadocs.generateFiles as (input: unknown) => Promise<void>;\n await generateFiles({\n input: createOpenAPI({\n input: { blaaiz: document },\n ...(options.proxyUrl ? { proxyUrl: options.proxyUrl } : {}),\n }),\n output: outDir,\n per: 'operation',\n groupBy: 'none',\n beforeWrite(files: GeneratedFile[]) {\n for (let i = files.length - 1; i >= 0; i -= 1) {\n const file = files[i];\n if (!file) {\n continue;\n }\n const key = operationKeyOf(file.content);\n const placement = key ? placements.get(key) : undefined;\n if (!placement) {\n files.splice(i, 1); // not declared in docs.json -> not published\n continue;\n }\n file.path = path.join(placement.dir, `${placement.slug}.mdx`);\n pages += 1;\n }\n },\n });\n }\n\n // Meta files: the plan's static ones, plus page order for each API group.\n const metas = [\n ...plan.metas,\n ...plan.apiGroups.map((group) => ({\n path: `${group.dir}/meta.json`,\n content: {\n title: group.title,\n pages: (groupPages.get(group.dir) ?? [])\n .sort((a, b) => a.order - b.order)\n .map((page) => page.slug),\n },\n })),\n ];\n for (const meta of metas) {\n const target = path.join(outDir, meta.path);\n await mkdir(path.dirname(target), { recursive: true });\n await writeFile(target, `${JSON.stringify(meta.content, null, 2)}\\n`);\n }\n\n // Top-nav links: each tab points at its first page. The URL of an API page\n // needs its resolved slug, which is why this runs here, not in the planner.\n const baseUrl = (options.baseUrl ?? '/docs').replace(/\\/$/, '');\n const docUrl = (file: string): string => {\n const clean = file.replace(/\\/?index$/, '');\n return clean ? `${baseUrl}/${clean}` : baseUrl;\n };\n const firstUrl = (items: readonly NavItem[]): string | undefined => {\n for (const item of items) {\n if (item.kind === 'group') {\n const url = firstUrl(item.items);\n if (url) {\n return url;\n }\n } else if (item.kind === 'doc') {\n return docUrl(item.file);\n } else {\n const placement = placements.get(`${item.method.toUpperCase()} ${item.apiPath}`);\n if (placement) {\n return `${baseUrl}/${placement.dir}/${placement.slug}`;\n }\n }\n }\n return undefined;\n };\n const navLinks: NavLink[] = [];\n for (const tab of navigation) {\n const url = firstUrl(tab.items);\n if (url) {\n navLinks.push({ text: tab.title, url, ...(tab.icon ? { icon: tab.icon } : {}) });\n }\n }\n await writeFile(\n path.join(options.contentDir, 'nav.json'),\n `${JSON.stringify(navLinks, null, 2)}\\n`,\n );\n\n // Page URL -> HTTP method, for the sidebar method badges.\n const methodsByUrl: Record<string, string> = {};\n for (const page of plan.apiPages) {\n const placement = placements.get(`${page.method.toUpperCase()} ${page.apiPath}`);\n if (placement) {\n methodsByUrl[`${baseUrl}/${placement.dir}/${placement.slug}`] = page.method.toUpperCase();\n }\n }\n await writeFile(\n path.join(options.contentDir, 'api-methods.json'),\n `${JSON.stringify(methodsByUrl, null, 2)}\\n`,\n );\n\n return { pages, metas: metas.length, navLinks, methodsByUrl, document };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/generator/index.ts"],"names":[],"mappings":"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","file":"generator.js","sourcesContent":["/**\n * `@blaaiz/docs-core/generator` — builds the docs content tree from `docs.json`.\n *\n * One call turns the site's navigation file into everything fumadocs needs:\n * generated API pages placed in their configured groups, and `meta.json` files\n * encoding tab and group order. Authors reorganize the docs by editing\n * `docs.json` — never by moving files.\n *\n * Node-only. A site that publishes an API reference injects `createOpenAPI` and\n * `generateFiles` from its own `fumadocs-openapi` install, so this package takes\n * no dependency on it. A site of prose alone injects nothing: with no API pages\n * in `docs.json` the generator writes the `meta.json` files and `nav.json` and\n * never touches `fumadocs-openapi`.\n *\n * @packageDocumentation\n */\n\nimport { mkdir, readFile, rm, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { mergeOpenApiDocuments } from '../adapters/index.js';\nimport { parseNavigation } from '../adapters/index.js';\nimport type { NavItem, OpenApiDocument, OpenApiInfo, OpenApiServer } from '../core/index.js';\nimport { planDocsTree } from '../core/index.js';\n\n/**\n * The two fumadocs-openapi functions the generator needs, injected by the site\n * (`createOpenAPI` from `fumadocs-openapi/server`, `generateFiles` from\n * `fumadocs-openapi`). Typed loosely so this package needs no fumadocs types.\n *\n * @public\n */\nexport interface FumadocsOpenApi {\n readonly createOpenAPI: (...args: never[]) => unknown;\n readonly generateFiles: (...args: never[]) => unknown;\n}\n\n/**\n * Options for {@link generateDocsTree}.\n *\n * @public\n */\nexport interface GenerateDocsTreeOptions {\n /** The site's content directory (holds `docs.json`, spec files, and `docs/`). */\n readonly contentDir: string;\n /** Path to the navigation file. Default `<contentDir>/docs.json`. */\n readonly docsJson?: string;\n /** Output directory for the page tree. Default `<contentDir>/docs`. */\n readonly outDir?: string;\n /** Folder for generated API pages. Default `api`. */\n readonly apiDir?: string;\n /**\n * `info` block of the merged OpenAPI document. Required when `docs.json`\n * publishes API pages; ignored on a site of prose alone, which merges no\n * documents.\n */\n readonly info?: OpenApiInfo;\n /** Servers of the merged document. */\n readonly servers?: readonly OpenApiServer[];\n /** Same-origin proxy route for the try-it playground. */\n readonly proxyUrl?: string;\n /** URL base the fumadocs loader serves pages under. Default `/docs`. */\n readonly baseUrl?: string;\n /**\n * The site's fumadocs-openapi functions. Required when `docs.json` publishes\n * API pages; omit it on a site of prose alone.\n */\n readonly fumadocs?: FumadocsOpenApi;\n}\n\n/**\n * A top-navigation link: one tab, pointing at its first page.\n *\n * @public\n */\nexport interface NavLink {\n readonly text: string;\n readonly url: string;\n /** The tab's icon name from `docs.json`, for the site to resolve. */\n readonly icon?: string;\n}\n\n/**\n * What the generator produced.\n *\n * @public\n */\nexport interface GenerateDocsTreeResult {\n readonly pages: number;\n readonly metas: number;\n /**\n * Top-nav links, one per tab, each pointing at the tab's first page — written\n * to `<contentDir>/nav.json` for the site's layout. Keeps the top nav in sync\n * with `docs.json` instead of a hand-maintained list.\n */\n readonly navLinks: readonly NavLink[];\n /**\n * Page URL to HTTP method, written to `<contentDir>/api-methods.json` for the\n * sidebar method badges (`apiMethodBadges` in `@blaaiz/docs-core/ui`).\n */\n readonly methodsByUrl: Readonly<Record<string, string>>;\n /** The merged OpenAPI document (reusable for the runtime page renderer). */\n readonly document: OpenApiDocument;\n}\n\ninterface GeneratedFile {\n path: string;\n content: string;\n}\n\nfunction slugify(value: string): string {\n return value\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '');\n}\n\n/** The `METHOD path` key of the first operation a generated page renders. */\nfunction operationKeyOf(content: string): string | null {\n const match = /operations=\\{(\\[[\\s\\S]*?\\])\\}/.exec(content);\n if (!match?.[1]) {\n return null;\n }\n try {\n const ops = JSON.parse(match[1]) as { path: string; method: string }[];\n const op = ops[0];\n return op ? `${op.method.toUpperCase()} ${op.path}` : null;\n } catch {\n return null;\n }\n}\n\n/**\n * The `info` a docs-only run reports on its (empty) merged document. Nothing\n * renders it: with no API pages there is no reference view to title.\n */\nconst PROSE_ONLY_INFO: OpenApiInfo = { title: 'No API reference', version: '0.0.0' };\n\n/**\n * The fumadocs functions, checked. Called only when the navigation publishes\n * API pages, so the message can name the exact reason they are needed.\n */\nfunction requireFumadocs(value: unknown, apiPageCount: number): FumadocsOpenApi {\n const because = `docs.json publishes ${String(apiPageCount)} API page(s)`;\n if (typeof value !== 'object' || value === null) {\n throw new Error(\n `[docs-core] generateDocsTree: the 'fumadocs' option is required because ${because}. ` +\n \"Pass { createOpenAPI, generateFiles } from the site's fumadocs-openapi install.\",\n );\n }\n const candidate = value as Record<string, unknown>;\n for (const name of ['createOpenAPI', 'generateFiles'] as const) {\n if (typeof candidate[name] !== 'function') {\n throw new Error(\n `[docs-core] generateDocsTree: fumadocs.${name} must be a function because ${because}. ` +\n `Import ${name} from ${name === 'createOpenAPI' ? \"'fumadocs-openapi/server'\" : \"'fumadocs-openapi'\"}.`,\n );\n }\n }\n return value as FumadocsOpenApi;\n}\n\n/**\n * Build the docs content tree from `docs.json`.\n *\n * Reads the navigation, merges every referenced spec, generates one page per\n * declared endpoint into its configured group folder, and writes the\n * `meta.json` files that give the sidebar the navigation's exact structure and\n * order. Operations present in a spec but not declared in `docs.json` get no\n * page — the navigation decides what is published, as on Mintlify.\n *\n * A site of prose alone needs neither option: with no API pages in `docs.json`\n * the run writes the `meta.json` files, `nav.json`, and an empty\n * `api-methods.json`, and never loads `fumadocs-openapi`.\n *\n * @example\n * ```ts\n * // A site of prose alone.\n * await generateDocsTree({ contentDir: 'content' });\n * ```\n *\n * @param options - directories, document info, and the injected fumadocs functions\n * @returns counts and the merged document\n * @throws when `docs.json` publishes API pages and `fumadocs` or `info` is missing\n * @public\n */\nexport async function generateDocsTree(\n options: GenerateDocsTreeOptions,\n): Promise<GenerateDocsTreeResult> {\n const docsJson = options.docsJson ?? path.join(options.contentDir, 'docs.json');\n const outDir = options.outDir ?? path.join(options.contentDir, 'docs');\n const apiDir = options.apiDir ?? 'api';\n\n const navigation = parseNavigation(JSON.parse(await readFile(docsJson, 'utf8')));\n const plan = planDocsTree(navigation, { apiDir });\n\n // Where each planned API page lands. Both stay empty on a prose-only site.\n const placements = new Map<string, { dir: string; order: number; slug: string }>();\n const groupPages = new Map<string, { order: number; slug: string }[]>();\n let pages = 0;\n let document: OpenApiDocument;\n\n if (plan.apiPages.length === 0) {\n // A site of prose alone. Nothing to merge, nothing to generate, and — most\n // importantly — nothing removed: a hand-written folder named `api` is the\n // site's own content, not our output.\n document = mergeOpenApiDocuments([], { info: options.info ?? PROSE_ONLY_INFO });\n } else {\n const fumadocs = requireFumadocs(options.fumadocs, plan.apiPages.length);\n if (options.info === undefined) {\n throw new Error(\n `[docs-core] generateDocsTree: the 'info' option is required because docs.json ` +\n `publishes ${String(plan.apiPages.length)} API page(s). ` +\n 'Pass { title, version } for the merged OpenAPI document.',\n );\n }\n\n // Merge exactly the specs the navigation references.\n const docs = await Promise.all(\n plan.specs.map(\n async (file) =>\n JSON.parse(await readFile(path.join(options.contentDir, file), 'utf8')) as unknown,\n ),\n );\n document = mergeOpenApiDocuments(docs, {\n info: options.info,\n ...(options.servers ? { servers: options.servers } : {}),\n });\n\n // Resolve each planned page's slug from the merged document.\n for (const page of plan.apiPages) {\n const operation = document.paths[page.apiPath]?.[page.method.toLowerCase()] as\n { operationId?: string } | undefined;\n if (!operation) {\n throw new Error(\n `[docs-core] docs.json references ${page.method} ${page.apiPath}, ` +\n `but ${page.file} defines no such operation.`,\n );\n }\n const slug = operation.operationId ?? slugify(`${page.method} ${page.apiPath}`);\n placements.set(`${page.method.toUpperCase()} ${page.apiPath}`, { ...page, slug });\n const group = groupPages.get(page.dir) ?? [];\n group.push({ order: page.order, slug });\n groupPages.set(page.dir, group);\n }\n\n // Fresh API tree, then generate pages and route each into its planned folder.\n for (const group of plan.apiGroups) {\n if (group.root) {\n await rm(path.join(outDir, group.dir), { recursive: true, force: true });\n }\n }\n const createOpenAPI = fumadocs.createOpenAPI as (input: unknown) => unknown;\n const generateFiles = fumadocs.generateFiles as (input: unknown) => Promise<void>;\n await generateFiles({\n input: createOpenAPI({\n input: { blaaiz: document },\n ...(options.proxyUrl ? { proxyUrl: options.proxyUrl } : {}),\n }),\n output: outDir,\n per: 'operation',\n groupBy: 'none',\n beforeWrite(files: GeneratedFile[]) {\n for (let i = files.length - 1; i >= 0; i -= 1) {\n const file = files[i];\n if (!file) {\n continue;\n }\n const key = operationKeyOf(file.content);\n const placement = key ? placements.get(key) : undefined;\n if (!placement) {\n files.splice(i, 1); // not declared in docs.json -> not published\n continue;\n }\n file.path = path.join(placement.dir, `${placement.slug}.mdx`);\n pages += 1;\n }\n },\n });\n }\n\n // Meta files: the plan's static ones, plus each API group's entries — its\n // resolved page slugs and child folders, merged back into declared order.\n const metas = [\n ...plan.metas,\n ...plan.apiGroups.map((group) => ({\n path: `${group.dir}/meta.json`,\n content: {\n title: group.title,\n ...(group.root ? { root: true } : {}),\n ...(group.icon ? { icon: group.icon } : {}),\n pages: [\n ...(groupPages.get(group.dir) ?? []).map((page) => ({\n order: page.order,\n name: page.slug,\n })),\n ...group.children.map((child) => ({ order: child.order, name: child.folder })),\n ]\n .sort((a, b) => a.order - b.order)\n .map((entry) => entry.name),\n },\n })),\n ];\n for (const meta of metas) {\n const target = path.join(outDir, meta.path);\n await mkdir(path.dirname(target), { recursive: true });\n await writeFile(target, `${JSON.stringify(meta.content, null, 2)}\\n`);\n }\n\n // Top-nav links: each tab points at its first page. The URL of an API page\n // needs its resolved slug, which is why this runs here, not in the planner.\n const baseUrl = (options.baseUrl ?? '/docs').replace(/\\/$/, '');\n const docUrl = (file: string): string => {\n const clean = file.replace(/\\/?index$/, '');\n return clean ? `${baseUrl}/${clean}` : baseUrl;\n };\n const firstUrl = (items: readonly NavItem[]): string | undefined => {\n for (const item of items) {\n if (item.kind === 'group') {\n const url = firstUrl(item.items);\n if (url) {\n return url;\n }\n } else if (item.kind === 'doc') {\n return docUrl(item.file);\n } else {\n const placement = placements.get(`${item.method.toUpperCase()} ${item.apiPath}`);\n if (placement) {\n return `${baseUrl}/${placement.dir}/${placement.slug}`;\n }\n }\n }\n return undefined;\n };\n const navLinks: NavLink[] = [];\n for (const tab of navigation) {\n const url = firstUrl(tab.items);\n if (url) {\n navLinks.push({ text: tab.title, url, ...(tab.icon ? { icon: tab.icon } : {}) });\n }\n }\n await writeFile(\n path.join(options.contentDir, 'nav.json'),\n `${JSON.stringify(navLinks, null, 2)}\\n`,\n );\n\n // Page URL -> HTTP method, for the sidebar method badges.\n const methodsByUrl: Record<string, string> = {};\n for (const page of plan.apiPages) {\n const placement = placements.get(`${page.method.toUpperCase()} ${page.apiPath}`);\n if (placement) {\n methodsByUrl[`${baseUrl}/${placement.dir}/${placement.slug}`] = page.method.toUpperCase();\n }\n }\n await writeFile(\n path.join(options.contentDir, 'api-methods.json'),\n `${JSON.stringify(methodsByUrl, null, 2)}\\n`,\n );\n\n if (plan.apiTabDocPages.length > 0) {\n console.warn(\n `[docs-core] ${String(plan.apiTabDocPages.length)} doc page(s) are declared inside API ` +\n `tabs and cannot be placed in the generated sidebar folders: ` +\n `${plan.apiTabDocPages.join(', ')}. Each page stays reachable at its own URL; ` +\n 'to list one in the sidebar, move it into a doc tab.',\n );\n }\n\n return { pages, metas: metas.length, navLinks, methodsByUrl, document };\n}\n"]}
|
package/dist/index.cjs
CHANGED
|
@@ -1546,6 +1546,20 @@ function dirname(file) {
|
|
|
1546
1546
|
const i = file.lastIndexOf("/");
|
|
1547
1547
|
return i === -1 ? "" : file.slice(0, i);
|
|
1548
1548
|
}
|
|
1549
|
+
function firstDocFile(items) {
|
|
1550
|
+
for (const item of items) {
|
|
1551
|
+
if (item.kind === "doc") {
|
|
1552
|
+
return item.file;
|
|
1553
|
+
}
|
|
1554
|
+
if (item.kind === "group") {
|
|
1555
|
+
const found = firstDocFile(item.items);
|
|
1556
|
+
if (found !== void 0) {
|
|
1557
|
+
return found;
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
return void 0;
|
|
1562
|
+
}
|
|
1549
1563
|
function planDocsTree(navigation, options = {}) {
|
|
1550
1564
|
const apiDir = options.apiDir ?? "api";
|
|
1551
1565
|
const specs = [];
|
|
@@ -1553,6 +1567,9 @@ function planDocsTree(navigation, options = {}) {
|
|
|
1553
1567
|
const apiGroups = [];
|
|
1554
1568
|
const metas = [];
|
|
1555
1569
|
const rootPages = [];
|
|
1570
|
+
const apiTabDocPages = [];
|
|
1571
|
+
const apiTabs = navigation.filter((tab) => hasOpenApi(tab.items));
|
|
1572
|
+
const soleApiTab = apiTabs.length === 1;
|
|
1556
1573
|
for (const tab of navigation) {
|
|
1557
1574
|
if (hasOpenApi(tab.items)) {
|
|
1558
1575
|
planApiTab(tab);
|
|
@@ -1561,38 +1578,52 @@ function planDocsTree(navigation, options = {}) {
|
|
|
1561
1578
|
planDocTab(tab);
|
|
1562
1579
|
}
|
|
1563
1580
|
metas.unshift({ path: "meta.json", content: { pages: rootPages } });
|
|
1564
|
-
return { specs, apiPages, apiGroups, metas };
|
|
1581
|
+
return { specs, apiPages, apiGroups, metas, apiTabDocPages };
|
|
1565
1582
|
function planApiTab(tab) {
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1583
|
+
const tabDir = soleApiTab ? apiDir : slugify(tab.title);
|
|
1584
|
+
rootPages.push(tabDir);
|
|
1585
|
+
const at = apiGroups.length;
|
|
1586
|
+
const children = planApiItems(tab.items, tabDir);
|
|
1587
|
+
apiGroups.splice(at, 0, {
|
|
1588
|
+
dir: tabDir,
|
|
1589
|
+
title: tab.title,
|
|
1590
|
+
root: true,
|
|
1591
|
+
...tab.icon ? { icon: tab.icon } : {},
|
|
1592
|
+
children
|
|
1593
|
+
});
|
|
1594
|
+
}
|
|
1595
|
+
function planApiItems(items, dir) {
|
|
1596
|
+
const children = [];
|
|
1597
|
+
const takenFolders = /* @__PURE__ */ new Set();
|
|
1598
|
+
let order = 0;
|
|
1599
|
+
for (const item of items) {
|
|
1600
|
+
if (item.kind === "group") {
|
|
1601
|
+
const folder = slugify(item.title);
|
|
1602
|
+
if (takenFolders.has(folder)) {
|
|
1603
|
+
throw new Error(
|
|
1604
|
+
`[docs-core] Two sibling groups under '${dir}' share the folder name '${folder}'. Rename one of the groups in docs.json.`
|
|
1605
|
+
);
|
|
1606
|
+
}
|
|
1607
|
+
takenFolders.add(folder);
|
|
1608
|
+
const childDir = `${dir}/${folder}`;
|
|
1609
|
+
const at = apiGroups.length;
|
|
1610
|
+
const sub = planApiItems(item.items, childDir);
|
|
1611
|
+
apiGroups.splice(at, 0, { dir: childDir, title: item.title, children: sub });
|
|
1612
|
+
children.push({ order, folder });
|
|
1613
|
+
order += 1;
|
|
1570
1614
|
continue;
|
|
1571
1615
|
}
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
let order = 0;
|
|
1576
|
-
for (const page of item.items) {
|
|
1577
|
-
if (page.kind !== "openapi") {
|
|
1578
|
-
continue;
|
|
1579
|
-
}
|
|
1580
|
-
if (!specs.includes(page.file)) {
|
|
1581
|
-
specs.push(page.file);
|
|
1616
|
+
if (item.kind === "openapi") {
|
|
1617
|
+
if (!specs.includes(item.file)) {
|
|
1618
|
+
specs.push(item.file);
|
|
1582
1619
|
}
|
|
1583
|
-
apiPages.push({ file:
|
|
1620
|
+
apiPages.push({ file: item.file, method: item.method, apiPath: item.apiPath, dir, order });
|
|
1584
1621
|
order += 1;
|
|
1622
|
+
continue;
|
|
1585
1623
|
}
|
|
1624
|
+
apiTabDocPages.push(item.file);
|
|
1586
1625
|
}
|
|
1587
|
-
|
|
1588
|
-
path: `${apiDir}/meta.json`,
|
|
1589
|
-
content: {
|
|
1590
|
-
title: tab.title,
|
|
1591
|
-
root: true,
|
|
1592
|
-
...tab.icon ? { icon: tab.icon } : {},
|
|
1593
|
-
pages: groupDirs
|
|
1594
|
-
}
|
|
1595
|
-
});
|
|
1626
|
+
return children;
|
|
1596
1627
|
}
|
|
1597
1628
|
function planDocTab(tab) {
|
|
1598
1629
|
const directPages = tab.items.filter((item) => item.kind === "doc");
|
|
@@ -1603,36 +1634,58 @@ function planDocsTree(navigation, options = {}) {
|
|
|
1603
1634
|
}
|
|
1604
1635
|
return;
|
|
1605
1636
|
}
|
|
1606
|
-
const firstFile =
|
|
1637
|
+
const firstFile = firstDocFile(tab.items);
|
|
1607
1638
|
if (firstFile === void 0) {
|
|
1608
1639
|
return;
|
|
1609
1640
|
}
|
|
1610
1641
|
const tabDir = firstFile.split("/")[0] ?? "";
|
|
1611
1642
|
rootPages.push(tabDir);
|
|
1612
|
-
const tabPages = directPages.map((page) => basename(page.file));
|
|
1613
|
-
for (const group of groups) {
|
|
1614
|
-
const pages = group.items.filter((item) => item.kind === "doc");
|
|
1615
|
-
const groupDir = dirname(pages[0]?.file ?? "");
|
|
1616
|
-
const folder = groupDir.split("/").slice(1).join("/");
|
|
1617
|
-
if (folder === "") {
|
|
1618
|
-
tabPages.push(...pages.map((page) => basename(page.file)));
|
|
1619
|
-
continue;
|
|
1620
|
-
}
|
|
1621
|
-
tabPages.push(folder);
|
|
1622
|
-
metas.push({
|
|
1623
|
-
path: `${groupDir}/meta.json`,
|
|
1624
|
-
content: { title: group.title, pages: pages.map((page) => basename(page.file)) }
|
|
1625
|
-
});
|
|
1626
|
-
}
|
|
1627
1643
|
metas.push({
|
|
1628
1644
|
path: `${tabDir}/meta.json`,
|
|
1629
1645
|
content: {
|
|
1630
1646
|
title: tab.title,
|
|
1631
1647
|
root: true,
|
|
1632
1648
|
...tab.icon ? { icon: tab.icon } : {},
|
|
1633
|
-
pages:
|
|
1649
|
+
pages: planDocItems(tab.items, tabDir)
|
|
1650
|
+
}
|
|
1651
|
+
});
|
|
1652
|
+
}
|
|
1653
|
+
function planDocItems(items, dir) {
|
|
1654
|
+
const entries = [];
|
|
1655
|
+
for (const item of items) {
|
|
1656
|
+
if (item.kind === "doc") {
|
|
1657
|
+
entries.push(basename(item.file));
|
|
1658
|
+
continue;
|
|
1659
|
+
}
|
|
1660
|
+
if (item.kind !== "group") {
|
|
1661
|
+
continue;
|
|
1662
|
+
}
|
|
1663
|
+
const groupDir = planDocGroup(item, dir);
|
|
1664
|
+
if (groupDir === null) {
|
|
1665
|
+
continue;
|
|
1666
|
+
}
|
|
1667
|
+
if (groupDir === dir) {
|
|
1668
|
+
entries.push(...planDocItems(item.items, dir));
|
|
1669
|
+
continue;
|
|
1634
1670
|
}
|
|
1671
|
+
entries.push(groupDir.slice(dir.length + 1));
|
|
1672
|
+
}
|
|
1673
|
+
return entries;
|
|
1674
|
+
}
|
|
1675
|
+
function planDocGroup(group, dir) {
|
|
1676
|
+
const firstFile = firstDocFile(group.items);
|
|
1677
|
+
if (firstFile === void 0) {
|
|
1678
|
+
return null;
|
|
1679
|
+
}
|
|
1680
|
+
const groupDir = dirname(firstFile);
|
|
1681
|
+
if (groupDir === dir || groupDir === "") {
|
|
1682
|
+
return dir;
|
|
1683
|
+
}
|
|
1684
|
+
metas.push({
|
|
1685
|
+
path: `${groupDir}/meta.json`,
|
|
1686
|
+
content: { title: group.title, pages: planDocItems(group.items, groupDir) }
|
|
1635
1687
|
});
|
|
1688
|
+
return groupDir;
|
|
1636
1689
|
}
|
|
1637
1690
|
}
|
|
1638
1691
|
|
|
@@ -2528,6 +2581,92 @@ function createSignOut(options = {}) {
|
|
|
2528
2581
|
};
|
|
2529
2582
|
}
|
|
2530
2583
|
|
|
2584
|
+
// src/core/auth/site-auth.ts
|
|
2585
|
+
var notConfigured = () => new Response("This sign-in method is not configured for this site.", { status: 501 });
|
|
2586
|
+
var PUBLIC_SITE = {
|
|
2587
|
+
enabled: false,
|
|
2588
|
+
signInPath: "/signin",
|
|
2589
|
+
secretEnabled: false,
|
|
2590
|
+
googleEnabled: false,
|
|
2591
|
+
emailSignIn: () => Promise.resolve(notConfigured()),
|
|
2592
|
+
secretSignIn: () => Promise.resolve(notConfigured()),
|
|
2593
|
+
google: {
|
|
2594
|
+
start: () => notConfigured(),
|
|
2595
|
+
callback: () => Promise.resolve(notConfigured())
|
|
2596
|
+
}
|
|
2597
|
+
};
|
|
2598
|
+
function createSiteAuth(auth, options = {}) {
|
|
2599
|
+
if (auth === "public") {
|
|
2600
|
+
return {
|
|
2601
|
+
...PUBLIC_SITE,
|
|
2602
|
+
gate: () => Promise.resolve(null),
|
|
2603
|
+
signOut: createSignOut()
|
|
2604
|
+
};
|
|
2605
|
+
}
|
|
2606
|
+
return workspaceSiteAuth(auth, options);
|
|
2607
|
+
}
|
|
2608
|
+
function workspaceSiteAuth(auth, options) {
|
|
2609
|
+
const env = options.env ?? ((name) => process.env[name]);
|
|
2610
|
+
const signInPath = auth.signInPath ?? "/signin";
|
|
2611
|
+
const secure = auth.secureCookies ?? true;
|
|
2612
|
+
const ttlSeconds = auth.sessionTtlSeconds;
|
|
2613
|
+
const sessionSecretEnv = auth.sessionSecretEnv ?? "DOCS_SESSION_SECRET";
|
|
2614
|
+
const secret = env(sessionSecretEnv) ?? "";
|
|
2615
|
+
if (secret === "") {
|
|
2616
|
+
throw new DocsConfigError(
|
|
2617
|
+
`auth.mode is 'workspace': set the ${sessionSecretEnv} environment variable to a long random value (for example: openssl rand -hex 32).`
|
|
2618
|
+
);
|
|
2619
|
+
}
|
|
2620
|
+
const shared = {
|
|
2621
|
+
secret,
|
|
2622
|
+
allowedDomains: auth.allowedDomains,
|
|
2623
|
+
secureCookies: secure,
|
|
2624
|
+
signInPath,
|
|
2625
|
+
...ttlSeconds === void 0 ? {} : { ttlSeconds }
|
|
2626
|
+
};
|
|
2627
|
+
const emailEnabled = auth.providers.email === true;
|
|
2628
|
+
const accessSecret = auth.providers.secret ? env(auth.providers.secret.secretEnv ?? "DOCS_ACCESS_SECRET") ?? "" : "";
|
|
2629
|
+
const secretEnabled = accessSecret !== "";
|
|
2630
|
+
const googleConfig = auth.providers.google;
|
|
2631
|
+
const clientId = googleConfig ? env(googleConfig.clientIdEnv ?? "GOOGLE_CLIENT_ID") : void 0;
|
|
2632
|
+
const clientSecret = googleConfig ? env(googleConfig.clientSecretEnv ?? "GOOGLE_CLIENT_SECRET") : void 0;
|
|
2633
|
+
const googleEnabled = Boolean(googleConfig && clientId && clientSecret);
|
|
2634
|
+
const emailSignIn = emailEnabled ? createEmailSignIn(shared) : null;
|
|
2635
|
+
const secretSignIn = secretEnabled ? createSecretSignIn({ ...shared, accessSecret }) : null;
|
|
2636
|
+
const googleFor = (request) => {
|
|
2637
|
+
if (!googleEnabled || !googleConfig || !clientId || !clientSecret) {
|
|
2638
|
+
return null;
|
|
2639
|
+
}
|
|
2640
|
+
const origin = new URL(request.url).origin;
|
|
2641
|
+
const redirectPath = googleConfig.redirectPath ?? "/api/auth/google/callback";
|
|
2642
|
+
return createGoogleAuth({
|
|
2643
|
+
...shared,
|
|
2644
|
+
clientId,
|
|
2645
|
+
clientSecret,
|
|
2646
|
+
redirectUri: `${origin}${redirectPath}`
|
|
2647
|
+
});
|
|
2648
|
+
};
|
|
2649
|
+
return {
|
|
2650
|
+
enabled: true,
|
|
2651
|
+
signInPath,
|
|
2652
|
+
secretEnabled,
|
|
2653
|
+
googleEnabled,
|
|
2654
|
+
gate: createAuthGate({
|
|
2655
|
+
secret,
|
|
2656
|
+
allowedDomains: auth.allowedDomains,
|
|
2657
|
+
signInPath,
|
|
2658
|
+
publicPaths: [...options.publicPaths ?? ["/api/proxy"]]
|
|
2659
|
+
}),
|
|
2660
|
+
emailSignIn: (request) => emailSignIn?.(request) ?? Promise.resolve(notConfigured()),
|
|
2661
|
+
secretSignIn: (request) => secretSignIn?.(request) ?? Promise.resolve(notConfigured()),
|
|
2662
|
+
google: {
|
|
2663
|
+
start: (request) => googleFor(request)?.start(request) ?? notConfigured(),
|
|
2664
|
+
callback: (request) => googleFor(request)?.callback(request) ?? Promise.resolve(notConfigured())
|
|
2665
|
+
},
|
|
2666
|
+
signOut: createSignOut({ secureCookies: secure, redirectTo: signInPath })
|
|
2667
|
+
};
|
|
2668
|
+
}
|
|
2669
|
+
|
|
2531
2670
|
// src/core/proxy.ts
|
|
2532
2671
|
var DEFAULT_RATE_LIMIT_PER_MINUTE2 = 60;
|
|
2533
2672
|
var ProxyTargetError = class extends Error {
|
|
@@ -3004,6 +3143,7 @@ exports.createGoogleAuth = createGoogleAuth;
|
|
|
3004
3143
|
exports.createMarkdownRoute = createMarkdownRoute;
|
|
3005
3144
|
exports.createSecretSignIn = createSecretSignIn;
|
|
3006
3145
|
exports.createSignOut = createSignOut;
|
|
3146
|
+
exports.createSiteAuth = createSiteAuth;
|
|
3007
3147
|
exports.createTryItProxy = createTryItProxy;
|
|
3008
3148
|
exports.createTryItProxyRoute = createTryItProxyRoute;
|
|
3009
3149
|
exports.defineDocsConfig = defineDocsConfig;
|