vitepress-api-references 0.0.0 → 0.1.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/README.md CHANGED
@@ -2,9 +2,81 @@
2
2
 
3
3
  [![npm][npm-src]][npm-href] [![CI][ci-src]][ci-href]
4
4
 
5
- > [!WARNING] WIP
5
+ Enable JSDoc API Reference for VitePress.
6
6
 
7
- Enable JSDoc API Reference.
7
+ ## Usage
8
+
9
+ ### VitePress integration
10
+
11
+ This repository uses the VitePress integration to generate and serve its own API reference docs. See [`docs/.vitepress/config.ts`](./docs/.vitepress/config.ts) and the generated [`docs/api`](./docs/api) directory for a working self-hosted example.
12
+
13
+ ```ts
14
+ import { defineConfig } from 'vitepress'
15
+ import { withOxContentApiDocs } from 'vitepress-api-references'
16
+
17
+ export default defineConfig(
18
+ await withOxContentApiDocs({
19
+ themeConfig: {
20
+ sidebar: [{ text: 'Guide', link: '/' }, { text: 'API References' }]
21
+ },
22
+ apiDocs: {
23
+ entryPoints: [{ path: 'src/index.ts', name: 'default' }],
24
+ outDir: 'docs/api',
25
+ basePath: '/api',
26
+ markdown: {
27
+ pathStrategy: 'typedoc',
28
+ renderStyle: 'markdown',
29
+ indexFormat: 'table'
30
+ },
31
+ nav: {
32
+ section: { text: 'API References', collapsed: false },
33
+ insert: 'replace',
34
+ replaceText: 'API References'
35
+ }
36
+ }
37
+ })
38
+ )
39
+ ```
40
+
41
+ ### Standalone markdown generation
42
+
43
+ Use `generateOxContentApiDocs` when you want to generate markdown files directly without wiring the result into VitePress. See [`standalone/generate.ts`](./standalone/generate.ts) for a runnable example.
44
+
45
+ ```ts
46
+ import { generateOxContentApiDocs } from 'vitepress-api-references'
47
+
48
+ const result = await generateOxContentApiDocs({
49
+ entryPoints: [{ path: 'src/index.ts', name: 'default' }],
50
+ outDir: 'standalone',
51
+ basePath: '/standalone',
52
+ markdown: {
53
+ pathStrategy: 'typedoc',
54
+ renderStyle: 'markdown',
55
+ indexFormat: 'table'
56
+ }
57
+ })
58
+
59
+ console.log(`Generated ${result.generatedFiles.length} files`)
60
+ ```
61
+
62
+ Run the example with:
63
+
64
+ ```sh
65
+ vp run docs:standalone
66
+ ```
67
+
68
+ ## Local API reference docs
69
+
70
+ This repository uses its own public root API as the docs source under `docs/`.
71
+
72
+ ```sh
73
+ vp run docs:build
74
+ vp run docs:dev
75
+ ```
76
+
77
+ ## Credits
78
+
79
+ Built on top of [Ox Content](https://github.com/ubugeeei-prod/ox-content), which extracts JSDoc API metadata and renders the generated markdown.
8
80
 
9
81
  ## License
10
82
 
package/dist/index.d.mts CHANGED
@@ -1,9 +1,385 @@
1
- //#region src/index.d.ts
1
+ import { JsDocsMarkdownModule, JsDocsMarkdownOptions, JsEntryPointDocsOptions, JsEntryPointSpec, JsExternalPackageSource } from "@ox-content/napi";
2
+ import { UserConfig } from "vitepress";
3
+
4
+ //#region src/types.d.ts
5
+ /**
6
+ * @author kazuya kawaguchi (a.k.a. kazupon)
7
+ * @license MIT
8
+ */
9
+ type EntryPointInput = string | {
10
+ path: string | URL;
11
+ name?: string;
12
+ };
13
+ interface ExternalPackageSourceInput {
14
+ /** Package name whose exported docs can be linked from generated references. */
15
+ package: string;
16
+ /** Entry point file or URL used to extract metadata for the external package. */
17
+ entry: string | URL;
18
+ }
19
+ interface VitePressSidebarSectionOptions {
20
+ /** Label shown for the generated top-level sidebar section. */
21
+ text: string;
22
+ /**
23
+ * Whether the generated top-level sidebar section starts collapsed in VitePress.
24
+ *
25
+ * @default undefined
26
+ */
27
+ collapsed?: boolean;
28
+ }
29
+ interface ResolvedOxContentExtractionOptions extends Omit<JsEntryPointDocsOptions, 'root' | 'tsconfig'> {
30
+ /** External package sources converted to the structure expected by ox-content. */
31
+ externalPackageSources?: JsExternalPackageSource[];
32
+ }
33
+ /** Controls which declarations and metadata ox-content extracts from entry points. */
34
+ interface OxContentExtractionOptions {
35
+ /**
36
+ * Include declarations marked as private.
37
+ *
38
+ * @default false
39
+ */
40
+ private?: boolean;
41
+ /**
42
+ * Include declarations marked as internal.
43
+ *
44
+ * @default false
45
+ */
46
+ internal?: boolean;
47
+ /**
48
+ * Preserve external documentation links and metadata from source declarations.
49
+ *
50
+ * @default false
51
+ */
52
+ externalDocs?: boolean;
53
+ /**
54
+ * Include generic type parameter declarations and descriptions in generated docs.
55
+ *
56
+ * @default false
57
+ */
58
+ typeParameters?: boolean;
59
+ /**
60
+ * External packages used to resolve cross-package references in generated docs.
61
+ *
62
+ * @default undefined
63
+ */
64
+ externalPackageSources?: ExternalPackageSourceInput[];
65
+ }
66
+ /** Markdown rendering options forwarded to ox-content after project defaults are applied. */
67
+ interface OxContentMarkdownOptions extends Omit<JsDocsMarkdownOptions, 'basePath' | 'githubUrl'> {}
68
+ type VitePressSidebarInsert = 'append' | 'prepend' | 'replace' | ((sidebar: unknown, generated: VitePressSidebarItem) => unknown);
69
+ /** Configures generated VitePress sidebar data and optional navigation artifacts. */
70
+ interface VitePressNavOptions {
71
+ /**
72
+ * Whether generated navigation integration is enabled.
73
+ *
74
+ * @default true
75
+ */
76
+ enabled?: boolean;
77
+ /**
78
+ * Optional top-level sidebar section that wraps all generated API doc items.
79
+ *
80
+ * @default undefined
81
+ */
82
+ section?: VitePressSidebarSectionOptions;
83
+ /**
84
+ * Positioning strategy used when merging generated items into an existing sidebar.
85
+ *
86
+ * @default 'append'
87
+ */
88
+ insert?: VitePressSidebarInsert;
89
+ /**
90
+ * Sidebar item text to replace when `insert` is set to `replace`.
91
+ *
92
+ * @default undefined
93
+ */
94
+ replaceText?: string;
95
+ /**
96
+ * Route key to update when the existing VitePress sidebar is a route map.
97
+ *
98
+ * @default undefined
99
+ */
100
+ sidebarRoute?: string;
101
+ /**
102
+ * Collapsed state for generated sidebar branches, or a resolver called per item.
103
+ *
104
+ * @default undefined
105
+ */
106
+ collapsed?: boolean | ((item: ApiDocsNavItem, depth: number) => boolean | undefined);
107
+ /**
108
+ * File path for generated navigation code, or `false` to skip writing it.
109
+ *
110
+ * @default undefined
111
+ */
112
+ outputFile?: string | false;
113
+ /**
114
+ * Virtual module id that exposes generated navigation data, or `false` to disable it.
115
+ *
116
+ * @default false
117
+ */
118
+ virtualModule?: string | false;
119
+ /**
120
+ * Named export used in the generated navigation code file.
121
+ *
122
+ * @default 'apiDocsNav'
123
+ */
124
+ exportName?: string;
125
+ }
126
+ /** User-facing options for generating API reference markdown and VitePress integration. */
127
+ interface OxContentApiDocsOptions {
128
+ /**
129
+ * Project root used to resolve relative paths.
130
+ *
131
+ * @default process.cwd()
132
+ */
133
+ root?: string | URL;
134
+ /**
135
+ * TypeScript configuration file used for declaration extraction.
136
+ *
137
+ * @default undefined
138
+ */
139
+ tsconfig?: string | URL;
140
+ /** Source entry points whose exported declarations become API docs pages. */
141
+ entryPoints: EntryPointInput[];
142
+ /** Directory where generated markdown and optional artifacts are written. */
143
+ outDir: string | URL;
144
+ /** Base route used when generating links between API docs pages. */
145
+ basePath: string;
146
+ /**
147
+ * GitHub repository URL used to generate source links for declarations.
148
+ *
149
+ * @default undefined
150
+ */
151
+ githubUrl?: string;
152
+ /**
153
+ * Whether generated output should be cleaned before writing.
154
+ *
155
+ * @default undefined
156
+ */
157
+ clean?: boolean;
158
+ /**
159
+ * Whether generated files and artifacts are written to disk.
160
+ *
161
+ * @default true
162
+ */
163
+ write?: boolean;
164
+ /**
165
+ * Controls which declarations and metadata are extracted from entry points.
166
+ *
167
+ * @default {}
168
+ */
169
+ extraction?: OxContentExtractionOptions;
170
+ /**
171
+ * Controls markdown page generation, grouping, sorting, and rendering details.
172
+ *
173
+ * @default {}
174
+ */
175
+ markdown?: OxContentMarkdownOptions;
176
+ /**
177
+ * Controls generated VitePress sidebar integration and optional nav artifacts.
178
+ *
179
+ * @default { enabled: true, insert: 'append', virtualModule: false }
180
+ */
181
+ nav?: VitePressNavOptions;
182
+ /**
183
+ * Whether to write docs JSON, or the output path to write it to.
184
+ *
185
+ * @default undefined
186
+ */
187
+ docsJson?: boolean | string;
188
+ /**
189
+ * Escape `<` and `>` in markdown heading lines to avoid HTML parsing in VitePress.
190
+ *
191
+ * @default false
192
+ */
193
+ escapeHeadingAngleBrackets?: boolean;
194
+ }
195
+ /** API docs options after defaults are applied and all paths are normalized. */
196
+ interface ResolvedOxContentApiDocsOptions extends Omit<OxContentApiDocsOptions, 'root' | 'tsconfig' | 'entryPoints' | 'outDir' | 'extraction' | 'nav'> {
197
+ /** Absolute project root used as the base for all relative inputs. */
198
+ root: string;
199
+ /**
200
+ * Absolute TypeScript configuration path, when configured.
201
+ *
202
+ * @default undefined
203
+ */
204
+ tsconfig?: string;
205
+ /** Entry point specs with paths resolved to absolute filesystem paths. */
206
+ entryPoints: JsEntryPointSpec[];
207
+ /** Absolute output directory for generated markdown and optional artifacts. */
208
+ outDir: string;
209
+ /** Extraction options with defaults applied and external package paths resolved. */
210
+ extraction: ResolvedOxContentExtractionOptions;
211
+ /** VitePress navigation options with default integration settings applied. */
212
+ nav: Required<Pick<VitePressNavOptions, 'enabled' | 'insert' | 'virtualModule'>> & Omit<VitePressNavOptions, 'enabled' | 'insert' | 'virtualModule'>;
213
+ }
214
+ /** Navigation item generated from API docs metadata. */
215
+ interface ApiDocsNavItem {
216
+ /** Human-readable title shown in generated navigation and sidebar items. */
217
+ title: string;
218
+ /** VitePress link path for the generated API docs page. */
219
+ path: string;
220
+ /** Nested navigation items for grouped modules or declaration hierarchies. */
221
+ children?: ApiDocsNavItem[];
222
+ }
223
+ /** Minimal VitePress sidebar item shape used by generated API docs. */
224
+ interface VitePressSidebarItem {
225
+ /** Text label displayed in the VitePress sidebar. */
226
+ text?: string;
227
+ /** VitePress route path opened when the sidebar item is selected. */
228
+ link?: string;
229
+ /** Whether this item's nested children start collapsed. */
230
+ collapsed?: boolean;
231
+ /** Nested sidebar items displayed below this item. */
232
+ items?: VitePressSidebarItem[];
233
+ }
234
+ /** Options for merging generated sidebar items into an existing VitePress sidebar. */
235
+ interface MergeVitePressSidebarOptions {
236
+ /** Positioning strategy used when inserting generated items into the sidebar. */
237
+ insert?: VitePressSidebarInsert;
238
+ /** Existing sidebar item text to replace when `insert` is set to `replace`. */
239
+ replaceText?: string;
240
+ /** Route key to update when the existing VitePress sidebar is a route map. */
241
+ sidebarRoute?: string;
242
+ }
243
+ /** Result produced by API docs generation. */
244
+ interface OxContentApiDocsResult {
245
+ /** Generated markdown and artifact contents keyed by relative output path. */
246
+ files: Record<string, string>;
247
+ /** Navigation metadata derived from generated docs. */
248
+ nav: ApiDocsNavItem[];
249
+ /** Markdown module data generated from extracted declarations. */
250
+ docs: JsDocsMarkdownModule[];
251
+ /** Absolute paths for files written to disk or planned when `write` is `false`. */
252
+ generatedFiles: string[];
253
+ /** Diagnostic messages reported while extracting docs. */
254
+ diagnostics: string[];
255
+ /** Hash representing the current generation options and watched input files. */
256
+ hash: string;
257
+ /** Normalized options used for this generation run. */
258
+ resolvedOptions: ResolvedOxContentApiDocsOptions;
259
+ }
260
+ //#endregion
261
+ //#region src/generate.d.ts
262
+ /**
263
+ * Generates API reference markdown, navigation metadata, and optional artifacts.
264
+ *
265
+ * @example
266
+ * ```ts
267
+ * import { generateOxContentApiDocs } from 'vitepress-api-references'
268
+ *
269
+ * const result = await generateOxContentApiDocs({
270
+ * entryPoints: ['src/index.ts'],
271
+ * outDir: 'docs/api',
272
+ * basePath: '/api'
273
+ * })
274
+ *
275
+ * console.log(result.generatedFiles)
276
+ * ```
277
+ *
278
+ * @param options - API docs generation options.
279
+ * @returns Generated API docs files, metadata, diagnostics, and resolved options.
280
+ */
281
+ declare function generateOxContentApiDocs(options: OxContentApiDocsOptions): Promise<OxContentApiDocsResult>;
282
+ //#endregion
283
+ //#region src/vitepress.d.ts
284
+ declare module 'vitepress' {
285
+ interface UserConfig {
286
+ apiDocs?: OxContentApiDocsOptions | false;
287
+ }
288
+ }
2
289
  /**
3
- * vitepress api references entry point
290
+ * Adds generated API docs pages, sidebar data, and watch support to a VitePress config.
291
+ *
292
+ * @example
293
+ * ```ts
294
+ * import { defineConfig } from 'vitepress'
295
+ * import { withOxContentApiDocs } from 'vitepress-api-references'
296
+ *
297
+ * export default await withOxContentApiDocs(
298
+ * defineConfig({
299
+ * title: 'My Library',
300
+ * apiDocs: {
301
+ * entryPoints: ['src/index.ts'],
302
+ * outDir: 'api',
303
+ * basePath: '/api'
304
+ * }
305
+ * })
306
+ * )
307
+ * ```
4
308
  *
5
- * @module default
309
+ * @param config - VitePress user configuration.
310
+ * @param override - Optional API docs options applied over the configured options.
311
+ * @returns Updated VitePress user configuration.
6
312
  */
7
- declare function fn(): string;
313
+ declare function withOxContentApiDocs(config: UserConfig, override?: OxContentApiDocsOptions): Promise<UserConfig>;
314
+ //#endregion
315
+ //#region src/sidebar.d.ts
316
+ /**
317
+ * Converts API docs navigation metadata into VitePress sidebar items.
318
+ *
319
+ * @example
320
+ * ```ts
321
+ * import { toVitePressSidebarItems } from 'vitepress-api-references'
322
+ *
323
+ * const sidebarItems = toVitePressSidebarItems([
324
+ * {
325
+ * title: 'API',
326
+ * path: '/api/',
327
+ * children: [{ title: 'withOxContentApiDocs', path: '/api/with-ox-content-api-docs' }]
328
+ * }
329
+ * ])
330
+ * ```
331
+ *
332
+ * @param nav - API docs navigation metadata.
333
+ * @param options - VitePress navigation options.
334
+ * @returns Generated VitePress sidebar items.
335
+ */
336
+ declare function toVitePressSidebarItems(nav: ApiDocsNavItem[], options?: VitePressNavOptions): VitePressSidebarItem[];
337
+ /**
338
+ * Creates a VitePress sidebar section from generated API docs navigation.
339
+ *
340
+ * @example
341
+ * ```ts
342
+ * import { createVitePressSidebarSection } from 'vitepress-api-references'
343
+ *
344
+ * const apiSection = createVitePressSidebarSection(apiDocsNav, {
345
+ * section: {
346
+ * text: 'API Reference',
347
+ * collapsed: false
348
+ * }
349
+ * })
350
+ * ```
351
+ *
352
+ * @param nav - API docs navigation metadata.
353
+ * @param options - VitePress navigation options.
354
+ * @returns Generated VitePress sidebar section.
355
+ */
356
+ declare function createVitePressSidebarSection(nav: ApiDocsNavItem[], options?: VitePressNavOptions): VitePressSidebarItem;
357
+ /**
358
+ * Merges generated API docs sidebar data into an existing VitePress sidebar.
359
+ *
360
+ * @example
361
+ * ```ts
362
+ * import { mergeVitePressSidebar } from 'vitepress-api-references'
363
+ *
364
+ * const sidebar = mergeVitePressSidebar(
365
+ * [{ text: 'Guide', link: '/guide/' }],
366
+ * { text: 'API Reference', items: [{ text: 'Config', link: '/api/config' }] },
367
+ * { insert: 'append' }
368
+ * )
369
+ * ```
370
+ *
371
+ * @param sidebar - Existing VitePress sidebar configuration.
372
+ * @param generated - Generated API docs sidebar section.
373
+ * @param options - Sidebar merge options.
374
+ * @returns Updated VitePress sidebar configuration.
375
+ */
376
+ declare function mergeVitePressSidebar(sidebar: unknown, generated: VitePressSidebarItem, options?: MergeVitePressSidebarOptions): unknown;
377
+ //#endregion
378
+ //#region src/index.d.ts
379
+ /**
380
+ * @author kazuya kawaguchi (a.k.a. kazupon)
381
+ * @license MIT
382
+ */
383
+
8
384
  //#endregion
9
- export { fn };
385
+ export { type ApiDocsNavItem, type MergeVitePressSidebarOptions, type OxContentApiDocsOptions, type OxContentApiDocsResult, type OxContentExtractionOptions, type OxContentMarkdownOptions, type ResolvedOxContentApiDocsOptions, type VitePressNavOptions, type VitePressSidebarItem, createVitePressSidebarSection, generateOxContentApiDocs, mergeVitePressSidebar, toVitePressSidebarItems, withOxContentApiDocs };
package/dist/index.mjs CHANGED
@@ -1,18 +1,528 @@
1
- import { createDebug } from "obug";
2
- //#region src/index.ts
1
+ import path from "node:path";
2
+ import { extractDocsFromEntryPoints, generateDocsDataJson, generateDocsMarkdown, generateDocsNavCode, generateDocsNavMetadataFromDocs } from "@ox-content/napi";
3
+ import fs from "node:fs/promises";
4
+ import { createHash } from "node:crypto";
5
+ import { fileURLToPath } from "node:url";
6
+ //#region src/files.ts
7
+ /**
8
+ * @author kazuya kawaguchi (a.k.a. kazupon)
9
+ * @license MIT
10
+ */
11
+ /**
12
+ * Writes generated content only when the target file content changes.
13
+ *
14
+ * @param filePath - Output file path.
15
+ * @param content - File content to write.
16
+ * @returns Whether the file was written.
17
+ */
18
+ async function writeGeneratedFile(filePath, content) {
19
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
20
+ try {
21
+ if (await fs.readFile(filePath, "utf8") === content) return false;
22
+ } catch (error) {
23
+ if (!isNotFoundError(error)) throw error;
24
+ }
25
+ await fs.writeFile(filePath, content);
26
+ return true;
27
+ }
28
+ /**
29
+ * Writes multiple generated files into an output directory.
30
+ *
31
+ * @param files - Generated file contents keyed by relative path.
32
+ * @param outDir - Output directory used to resolve relative paths.
33
+ * @returns Absolute paths for generated files.
34
+ */
35
+ async function writeGeneratedFiles(files, outDir) {
36
+ const generatedFiles = [];
37
+ for (const [relativePath, content] of Object.entries(files)) {
38
+ const filePath = path.resolve(outDir, relativePath);
39
+ await writeGeneratedFile(filePath, content);
40
+ generatedFiles.push(filePath);
41
+ }
42
+ return generatedFiles;
43
+ }
44
+ function isNotFoundError(error) {
45
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
46
+ }
47
+ //#endregion
48
+ //#region src/hash.ts
49
+ /**
50
+ * @author kazuya kawaguchi (a.k.a. kazupon)
51
+ * @license MIT
52
+ */
53
+ /**
54
+ * Creates a stable hash for API docs generation inputs.
55
+ *
56
+ * @param options - Resolved API docs options.
57
+ * @returns Hash string for the current generation inputs.
58
+ */
59
+ async function createGenerationHash(options) {
60
+ const hash = createHash("sha256");
61
+ hash.update(JSON.stringify(toHashableOptions(options)));
62
+ for (const filePath of collectInputFiles(options)) {
63
+ hash.update(filePath);
64
+ hash.update(await statHash(filePath));
65
+ }
66
+ return hash.digest("hex");
67
+ }
68
+ function toHashableOptions(options) {
69
+ return {
70
+ root: options.root,
71
+ tsconfig: options.tsconfig,
72
+ entryPoints: options.entryPoints,
73
+ outDir: options.outDir,
74
+ basePath: options.basePath,
75
+ githubUrl: options.githubUrl,
76
+ extraction: options.extraction,
77
+ markdown: options.markdown,
78
+ docsJson: options.docsJson,
79
+ escapeHeadingAngleBrackets: options.escapeHeadingAngleBrackets
80
+ };
81
+ }
82
+ function collectInputFiles(options) {
83
+ return [
84
+ ...options.entryPoints.map((entry) => entry.path),
85
+ ...options.tsconfig ? [options.tsconfig] : [],
86
+ ...options.extraction.externalPackageSources?.map((source) => source.entry) ?? []
87
+ ];
88
+ }
89
+ async function statHash(filePath) {
90
+ try {
91
+ const stat = await fs.stat(filePath);
92
+ return `${stat.mtimeMs}:${stat.size}`;
93
+ } catch {
94
+ return "missing";
95
+ }
96
+ }
97
+ //#endregion
98
+ //#region src/options.ts
99
+ /**
100
+ * @author kazuya kawaguchi (a.k.a. kazupon)
101
+ * @license MIT
102
+ */
103
+ /**
104
+ * Resolves user API docs options into absolute paths and default values.
105
+ *
106
+ * @param options - User-facing API docs options.
107
+ * @returns Resolved API docs options.
108
+ */
109
+ function resolveApiDocsOptions(options) {
110
+ const root = toAbsolutePath(options.root ?? process.cwd(), process.cwd());
111
+ const outDir = toAbsolutePath(options.outDir, root);
112
+ const tsconfig = options.tsconfig ? toAbsolutePath(options.tsconfig, root) : void 0;
113
+ const extraction = options.extraction ?? {};
114
+ return {
115
+ ...options,
116
+ root,
117
+ tsconfig,
118
+ outDir,
119
+ basePath: normalizeBasePath(options.basePath),
120
+ entryPoints: options.entryPoints.map((entry) => {
121
+ if (typeof entry === "string") return { path: toAbsolutePath(entry, root) };
122
+ return {
123
+ path: toAbsolutePath(entry.path, root),
124
+ name: entry.name
125
+ };
126
+ }),
127
+ extraction: {
128
+ private: extraction.private ?? false,
129
+ internal: extraction.internal ?? false,
130
+ externalDocs: extraction.externalDocs ?? false,
131
+ typeParameters: extraction.typeParameters ?? false,
132
+ externalPackageSources: extraction.externalPackageSources?.map((source) => ({
133
+ package: source.package,
134
+ entry: toAbsolutePath(source.entry, root)
135
+ }))
136
+ },
137
+ markdown: options.markdown ?? {},
138
+ nav: {
139
+ enabled: options.nav?.enabled ?? true,
140
+ insert: options.nav?.insert ?? "append",
141
+ virtualModule: options.nav?.virtualModule ?? false,
142
+ ...options.nav
143
+ },
144
+ write: options.write ?? true,
145
+ escapeHeadingAngleBrackets: options.escapeHeadingAngleBrackets ?? false
146
+ };
147
+ }
148
+ /**
149
+ * Merges base API docs options with override options.
150
+ *
151
+ * @param base - Base options from VitePress configuration.
152
+ * @param override - Override options applied on top of the base options.
153
+ * @returns Merged API docs options.
154
+ */
155
+ function mergeApiDocsOptions(base, override) {
156
+ if (!override) return base;
157
+ const section = override.nav?.section ?? base.nav?.section;
158
+ const nav = {
159
+ ...base.nav,
160
+ ...override.nav
161
+ };
162
+ return {
163
+ ...base,
164
+ ...override,
165
+ extraction: {
166
+ ...base.extraction,
167
+ ...override.extraction
168
+ },
169
+ markdown: {
170
+ ...base.markdown,
171
+ ...override.markdown
172
+ },
173
+ nav: section ? {
174
+ ...nav,
175
+ section
176
+ } : nav
177
+ };
178
+ }
179
+ function normalizeBasePath(basePath) {
180
+ const normalized = basePath.startsWith("/") ? basePath : `/${basePath}`;
181
+ return normalized.length > 1 ? normalized.replace(/\/+$/, "") : normalized;
182
+ }
183
+ function toAbsolutePath(value, baseDir) {
184
+ if (value instanceof URL) return fileURLToPath(value);
185
+ return path.isAbsolute(value) ? value : path.resolve(baseDir, value);
186
+ }
187
+ //#endregion
188
+ //#region src/generate.ts
189
+ /**
190
+ * @author kazuya kawaguchi (a.k.a. kazupon)
191
+ * @license MIT
192
+ */
193
+ /**
194
+ * Generates API reference markdown, navigation metadata, and optional artifacts.
195
+ *
196
+ * @example
197
+ * ```ts
198
+ * import { generateOxContentApiDocs } from 'vitepress-api-references'
199
+ *
200
+ * const result = await generateOxContentApiDocs({
201
+ * entryPoints: ['src/index.ts'],
202
+ * outDir: 'docs/api',
203
+ * basePath: '/api'
204
+ * })
205
+ *
206
+ * console.log(result.generatedFiles)
207
+ * ```
208
+ *
209
+ * @param options - API docs generation options.
210
+ * @returns Generated API docs files, metadata, diagnostics, and resolved options.
211
+ */
212
+ async function generateOxContentApiDocs(options) {
213
+ const resolvedOptions = resolveApiDocsOptions(options);
214
+ const extractedDocs = extractDocsFromEntryPoints(resolvedOptions.entryPoints, {
215
+ root: resolvedOptions.root,
216
+ tsconfig: resolvedOptions.tsconfig,
217
+ ...resolvedOptions.extraction
218
+ });
219
+ const docs = extractedDocs.map((module) => ({
220
+ file: module.name,
221
+ description: module.description,
222
+ sourcePath: module.sourcePath,
223
+ examples: module.examples,
224
+ tags: module.tags,
225
+ entries: module.entries.map((entry) => ({
226
+ ...entry,
227
+ tags: entry.tags ? Object.entries(entry.tags).map(([tag, value]) => ({
228
+ tag,
229
+ value
230
+ })) : void 0,
231
+ hasBody: entry.hasBody ?? false
232
+ }))
233
+ }));
234
+ const rawFiles = generateDocsMarkdown(docs, {
235
+ ...resolvedOptions.markdown,
236
+ basePath: resolvedOptions.basePath,
237
+ githubUrl: resolvedOptions.githubUrl
238
+ });
239
+ const files = resolvedOptions.escapeHeadingAngleBrackets ? escapeHeadingAngleBrackets(rawFiles) : rawFiles;
240
+ const nav = generateDocsNavMetadataFromDocs(docs, {
241
+ basePath: resolvedOptions.basePath,
242
+ pathStrategy: resolvedOptions.markdown?.pathStrategy,
243
+ groupOrder: resolvedOptions.markdown?.groupOrder,
244
+ sort: resolvedOptions.markdown?.sort,
245
+ sortEntryPoints: resolvedOptions.markdown?.sortEntryPoints,
246
+ kindSortOrder: resolvedOptions.markdown?.kindSortOrder
247
+ });
248
+ const generatedFiles = resolvedOptions.write ? await writeGeneratedFiles(files, resolvedOptions.outDir) : Object.keys(files).map((file) => path.resolve(resolvedOptions.outDir, file));
249
+ if (resolvedOptions.write) await writeOptionalArtifacts(resolvedOptions, docs, nav, generatedFiles);
250
+ return {
251
+ files,
252
+ nav,
253
+ docs,
254
+ generatedFiles,
255
+ diagnostics: extractedDocs.flatMap((module) => module.diagnostics.map((diagnostic) => diagnostic.message)),
256
+ hash: await createGenerationHash(resolvedOptions),
257
+ resolvedOptions
258
+ };
259
+ }
260
+ async function writeOptionalArtifacts(options, docs, nav, generatedFiles) {
261
+ if (options.docsJson) {
262
+ const docsJsonPath = typeof options.docsJson === "string" ? options.docsJson : "docs.json";
263
+ const outputPath = path.isAbsolute(docsJsonPath) ? docsJsonPath : path.join(options.outDir, docsJsonPath);
264
+ await writeGeneratedFile(outputPath, `${generateDocsDataJson(docs, (/* @__PURE__ */ new Date()).toISOString())}\n`);
265
+ generatedFiles.push(outputPath);
266
+ }
267
+ if (options.nav.outputFile) {
268
+ const outputPath = path.isAbsolute(options.nav.outputFile) ? options.nav.outputFile : path.join(options.outDir, options.nav.outputFile);
269
+ await writeGeneratedFile(outputPath, generateDocsNavCode(nav, options.nav.exportName ?? "apiDocsNav"));
270
+ generatedFiles.push(outputPath);
271
+ }
272
+ }
273
+ function escapeHeadingAngleBrackets(files) {
274
+ return Object.fromEntries(Object.entries(files).map(([filePath, content]) => [filePath, content.split("\n").map((line) => /^#{1,6}\s/.test(line) ? line.replaceAll("<", "&lt;").replaceAll(">", "&gt;") : line).join("\n")]));
275
+ }
276
+ //#endregion
277
+ //#region src/sidebar.ts
278
+ /**
279
+ * @author kazuya kawaguchi (a.k.a. kazupon)
280
+ * @license MIT
281
+ */
282
+ /**
283
+ * Converts API docs navigation metadata into VitePress sidebar items.
284
+ *
285
+ * @example
286
+ * ```ts
287
+ * import { toVitePressSidebarItems } from 'vitepress-api-references'
288
+ *
289
+ * const sidebarItems = toVitePressSidebarItems([
290
+ * {
291
+ * title: 'API',
292
+ * path: '/api/',
293
+ * children: [{ title: 'withOxContentApiDocs', path: '/api/with-ox-content-api-docs' }]
294
+ * }
295
+ * ])
296
+ * ```
297
+ *
298
+ * @param nav - API docs navigation metadata.
299
+ * @param options - VitePress navigation options.
300
+ * @returns Generated VitePress sidebar items.
301
+ */
302
+ function toVitePressSidebarItems(nav, options = {}) {
303
+ return nav.map((item) => toSidebarItem(item, options, 0));
304
+ }
3
305
  /**
4
- * vitepress api references entry point
306
+ * Creates a VitePress sidebar section from generated API docs navigation.
307
+ *
308
+ * @example
309
+ * ```ts
310
+ * import { createVitePressSidebarSection } from 'vitepress-api-references'
311
+ *
312
+ * const apiSection = createVitePressSidebarSection(apiDocsNav, {
313
+ * section: {
314
+ * text: 'API Reference',
315
+ * collapsed: false
316
+ * }
317
+ * })
318
+ * ```
5
319
  *
6
- * @module default
320
+ * @param nav - API docs navigation metadata.
321
+ * @param options - VitePress navigation options.
322
+ * @returns Generated VitePress sidebar section.
7
323
  */
324
+ function createVitePressSidebarSection(nav, options = {}) {
325
+ const items = toVitePressSidebarItems(nav, options);
326
+ const section = options.section;
327
+ if (!section) return { items };
328
+ return {
329
+ text: section.text,
330
+ collapsed: section.collapsed,
331
+ items
332
+ };
333
+ }
334
+ /**
335
+ * Merges generated API docs sidebar data into an existing VitePress sidebar.
336
+ *
337
+ * @example
338
+ * ```ts
339
+ * import { mergeVitePressSidebar } from 'vitepress-api-references'
340
+ *
341
+ * const sidebar = mergeVitePressSidebar(
342
+ * [{ text: 'Guide', link: '/guide/' }],
343
+ * { text: 'API Reference', items: [{ text: 'Config', link: '/api/config' }] },
344
+ * { insert: 'append' }
345
+ * )
346
+ * ```
347
+ *
348
+ * @param sidebar - Existing VitePress sidebar configuration.
349
+ * @param generated - Generated API docs sidebar section.
350
+ * @param options - Sidebar merge options.
351
+ * @returns Updated VitePress sidebar configuration.
352
+ */
353
+ function mergeVitePressSidebar(sidebar, generated, options = {}) {
354
+ const insert = options.insert ?? "append";
355
+ if (typeof insert === "function") return insert(sidebar, generated);
356
+ if (isSidebarMulti(sidebar)) {
357
+ const route = options.sidebarRoute;
358
+ if (!route) return sidebar;
359
+ return {
360
+ ...sidebar,
361
+ [route]: mergeVitePressSidebar(sidebar[route], generated, options)
362
+ };
363
+ }
364
+ const items = Array.isArray(sidebar) ? sidebar : [];
365
+ if (insert === "prepend") return [generated, ...items];
366
+ if (insert === "replace") return replaceSidebarItem(items, generated, options.replaceText);
367
+ return [...items, generated];
368
+ }
369
+ function toSidebarItem(item, options, depth) {
370
+ const children = item.children?.map((child) => toSidebarItem(child, options, depth + 1));
371
+ const sidebarItem = { text: item.title };
372
+ const collapsed = resolveCollapsed(item, options, depth);
373
+ if (collapsed !== void 0) sidebarItem.collapsed = collapsed;
374
+ if (children?.length) {
375
+ sidebarItem.items = children;
376
+ if (depth === 0 && item.path) sidebarItem.link = item.path;
377
+ } else if (item.path) sidebarItem.link = item.path;
378
+ return sidebarItem;
379
+ }
380
+ function replaceSidebarItem(items, generated, replaceText) {
381
+ let replaced = false;
382
+ const next = items.map((item) => {
383
+ if (isSidebarItem(item)) {
384
+ if (replaceText && item.text === replaceText) {
385
+ replaced = true;
386
+ return generated;
387
+ }
388
+ if (replaceText && item.items) {
389
+ const nested = replaceSidebarItem(item.items, generated, replaceText);
390
+ if (nested !== item.items) {
391
+ replaced = true;
392
+ return {
393
+ ...item,
394
+ items: nested
395
+ };
396
+ }
397
+ }
398
+ }
399
+ return item;
400
+ });
401
+ return replaced ? next : [...items, generated];
402
+ }
403
+ function resolveCollapsed(item, options, depth) {
404
+ if (typeof options.collapsed === "function") return options.collapsed(item, depth);
405
+ return options.collapsed;
406
+ }
407
+ function isSidebarItem(value) {
408
+ return typeof value === "object" && value !== null;
409
+ }
410
+ function isSidebarMulti(value) {
411
+ return typeof value === "object" && value !== null && !Array.isArray(value) && Object.values(value).every((item) => Array.isArray(item));
412
+ }
413
+ //#endregion
414
+ //#region src/watch.ts
8
415
  /**
9
416
  * @author kazuya kawaguchi (a.k.a. kazupon)
10
417
  * @license MIT
11
418
  */
12
- const debug = createDebug("vitepress-api-references");
13
- function fn() {
14
- debug("fn called");
15
- return "Hello, tsdown!";
419
+ /**
420
+ * Creates a Vite plugin that regenerates API docs during development.
421
+ *
422
+ * @param options - API docs generation options.
423
+ * @param pluginOptions - Initial plugin state options.
424
+ * @returns Vite plugin for API docs generation and reloads.
425
+ */
426
+ function createApiDocsVitePlugin(options, pluginOptions = {}) {
427
+ const resolvedOptions = resolveApiDocsOptions(options);
428
+ let lastHash = pluginOptions.initialHash;
429
+ let navItems = pluginOptions.initialNav ?? [];
430
+ let debounceTimer;
431
+ async function regenerate(force = false) {
432
+ const nextHash = await createGenerationHash(resolvedOptions);
433
+ if (!force && lastHash === nextHash) return;
434
+ const result = await generateOxContentApiDocs(options);
435
+ lastHash = result.hash;
436
+ navItems = result.nav;
437
+ }
438
+ return {
439
+ name: "vitepress-api-references:api-docs",
440
+ enforce: "post",
441
+ async buildStart() {
442
+ await regenerate();
443
+ },
444
+ configureServer(server) {
445
+ const watchedFiles = getWatchedFiles(options);
446
+ server.watcher.add(watchedFiles);
447
+ server.watcher.on("all", (_event, filePath) => {
448
+ if (!watchedFiles.includes(filePath)) return;
449
+ queueRegenerate(server, () => regenerate(true));
450
+ });
451
+ },
452
+ resolveId(id) {
453
+ if (resolvedOptions.nav.virtualModule && id === resolvedOptions.nav.virtualModule) return id;
454
+ },
455
+ load(id) {
456
+ if (resolvedOptions.nav.virtualModule && id === resolvedOptions.nav.virtualModule) return `export const navItems = ${JSON.stringify(navItems)}\n`;
457
+ }
458
+ };
459
+ function queueRegenerate(server, run) {
460
+ if (debounceTimer) clearTimeout(debounceTimer);
461
+ debounceTimer = setTimeout(() => {
462
+ run().then(() => {
463
+ server.ws.send({ type: "full-reload" });
464
+ }).catch((error) => {
465
+ console.error(error);
466
+ });
467
+ }, 100);
468
+ }
16
469
  }
470
+ function getWatchedFiles(options) {
471
+ const resolvedOptions = resolveApiDocsOptions(options);
472
+ return [...resolvedOptions.entryPoints.map((entry) => entry.path), ...resolvedOptions.extraction.externalPackageSources?.map((source) => source.entry) ?? []];
473
+ }
474
+ //#endregion
475
+ //#region src/vitepress.ts
476
+ /**
477
+ * @author kazuya kawaguchi (a.k.a. kazupon)
478
+ * @license MIT
479
+ */
480
+ /**
481
+ * Adds generated API docs pages, sidebar data, and watch support to a VitePress config.
482
+ *
483
+ * @example
484
+ * ```ts
485
+ * import { defineConfig } from 'vitepress'
486
+ * import { withOxContentApiDocs } from 'vitepress-api-references'
487
+ *
488
+ * export default await withOxContentApiDocs(
489
+ * defineConfig({
490
+ * title: 'My Library',
491
+ * apiDocs: {
492
+ * entryPoints: ['src/index.ts'],
493
+ * outDir: 'api',
494
+ * basePath: '/api'
495
+ * }
496
+ * })
497
+ * )
498
+ * ```
499
+ *
500
+ * @param config - VitePress user configuration.
501
+ * @param override - Optional API docs options applied over the configured options.
502
+ * @returns Updated VitePress user configuration.
503
+ */
504
+ async function withOxContentApiDocs(config, override) {
505
+ const configuredOptions = config.apiDocs;
506
+ if (configuredOptions === false && !override) return config;
507
+ const baseOptions = configuredOptions === false ? void 0 : configuredOptions;
508
+ const apiDocsOptions = override ? baseOptions ? mergeApiDocsOptions(baseOptions, override) : override : baseOptions;
509
+ if (!apiDocsOptions) return config;
510
+ const result = await generateOxContentApiDocs(apiDocsOptions);
511
+ const sidebarSection = createVitePressSidebarSection(result.nav, result.resolvedOptions.nav);
512
+ config.themeConfig ??= {};
513
+ config.themeConfig.sidebar = mergeVitePressSidebar(config.themeConfig.sidebar, sidebarSection, result.resolvedOptions.nav);
514
+ config.vite ??= {};
515
+ config.vite.plugins = [...Array.isArray(config.vite.plugins) ? config.vite.plugins : [], createApiDocsVitePlugin(apiDocsOptions, {
516
+ initialHash: result.hash,
517
+ initialNav: result.nav
518
+ })];
519
+ return config;
520
+ }
521
+ //#endregion
522
+ //#region src/index.ts
523
+ /**
524
+ * @author kazuya kawaguchi (a.k.a. kazupon)
525
+ * @license MIT
526
+ */
17
527
  //#endregion
18
- export { fn };
528
+ export { createVitePressSidebarSection, generateOxContentApiDocs, mergeVitePressSidebar, toVitePressSidebarItems, withOxContentApiDocs };
package/package.json CHANGED
@@ -1,7 +1,14 @@
1
1
  {
2
2
  "name": "vitepress-api-references",
3
- "version": "0.0.0",
4
- "description": "Enable JSDoc API Reference",
3
+ "version": "0.1.0",
4
+ "description": "Enable JSDoc API Reference for VitePress",
5
+ "keywords": [
6
+ "api-references",
7
+ "docs",
8
+ "documentation",
9
+ "markdown",
10
+ "vitepress"
11
+ ],
5
12
  "homepage": "https://github.com/kazupon/vitepress-api-references#readme",
6
13
  "bugs": {
7
14
  "url": "https://github.com/kazupon/vitepress-api-references/issues"
@@ -38,7 +45,11 @@
38
45
  "build": "vp pack",
39
46
  "knip": "knip",
40
47
  "dev": "vp pack --watch",
48
+ "docs:build": "vp pack && vp exec vitepress build docs",
49
+ "docs:dev": "vp pack && vp exec vitepress dev docs",
50
+ "docs:standalone": "vp pack && node standalone/generate.ts",
41
51
  "test": "vp test",
52
+ "test:fixtures": "vp exec vitepress build tests/fixtures/basic/docs",
42
53
  "check": "vp check && knip",
43
54
  "prepublishOnly": "vp run build",
44
55
  "release": "bumpp --commit \"release: v%s\" --all --push --tag",
@@ -46,18 +57,25 @@
46
57
  "prepare": "vp config"
47
58
  },
48
59
  "dependencies": {
49
- "obug": "^2.1.2"
60
+ "@ox-content/napi": "^2.63.0"
50
61
  },
51
62
  "devDependencies": {
52
- "@kazupon/eslint-plugin": "^0.7.1",
53
- "@kazupon/vp-config": "^0.2.0",
63
+ "@kazupon/vp-config": "^0.3.2",
54
64
  "@types/node": "^25.9.1",
55
- "@typescript/native-preview": "7.0.0-dev.20260509.2",
65
+ "@typescript/native-preview": "7.0.0-dev.20260527.2",
56
66
  "bumpp": "^11.1.0",
57
67
  "gh-changelogen": "^0.2.8",
58
68
  "knip": "^6.16.0",
69
+ "oxc-minify": "^0.134.0",
70
+ "pkg-pr-new": "^0.0.75",
59
71
  "typescript": "^6.0.3",
60
- "vite-plus": "catalog:"
72
+ "vite": "catalog:",
73
+ "vite-plus": "catalog:",
74
+ "vitepress": "2.0.0-alpha.17"
75
+ },
76
+ "peerDependencies": {
77
+ "vite": ">=5",
78
+ "vitepress": ">=2.0.0-alpha.17"
61
79
  },
62
80
  "engines": {
63
81
  "node": ">= 22"