valaxy-addon-typedoc 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 云游君 YunYouJun <me@yunyoujun.cn>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # valaxy-addon-typedoc
2
+
3
+ Generate TypeScript API reference pages inside a Valaxy site. TypeDoc runs on the Node side; Press provides navigation and the normal documentation layout.
4
+
5
+ This addon requires the generated-content integration in the next Valaxy release. Until that release, use it from this repository's pnpm workspace.
6
+
7
+ ```ts
8
+ import { defineValaxyConfig } from 'valaxy'
9
+ import { addonTypeDoc } from 'valaxy-addon-typedoc'
10
+
11
+ export default defineValaxyConfig({
12
+ theme: 'press',
13
+ addons: [addonTypeDoc({
14
+ options: './typedoc.json',
15
+ watch: ['../src/**/*.ts', './tsconfig*.json'],
16
+ routeBase: '/api/',
17
+ // Set true when pages/api/index.md provides a handwritten overview.
18
+ excludeIndex: true,
19
+ })],
20
+ })
21
+ ```
22
+
23
+ ```json
24
+ {
25
+ "entryPoints": ["../src/index.ts"],
26
+ "tsconfig": "./tsconfig.typedoc.json",
27
+ "excludePrivate": true,
28
+ "excludeProtected": true,
29
+ "excludeInternal": true
30
+ }
31
+ ```
32
+
33
+ `options` and `watch` are relative to the Valaxy site. Paths in TypeDoc's JSON configuration are relative to that configuration file. Use an explicit `tsconfig` to avoid accidentally compiling the documentation app. Include source directories in `watch` so new exports and files trigger regeneration. TypeDoc program dependencies and extended TypeScript configurations are also tracked.
34
+
35
+ The addon owns the Markdown output settings and writes under `.valaxy/content/pages/`. Do not edit these generated files. The cache fingerprints source contents, configuration, dependency declarations, compiler/plugin versions and Git revision. Remove `.valaxy/content/` to force a clean generation.
36
+
37
+ In development, changes are debounced and serialized. Ordinary Markdown edits do not run TypeDoc. A failed generation keeps the last successful pages and logs an error. Production generation failures stop the build; a matching cache is valid reuse. Empty references, duplicate routes and collisions with handwritten pages are errors.
38
+
39
+ The generated Press sidebar is installed only for `routeBase`, preserving other sidebar sections. Set `sidebar: false` to manage navigation yourself. Generated pages use `sharedLocale: true`: one reference URL across interface languages. `themeConfig.apiReference` and each page's `apiSource` contain the source revision, package version when available, and whether the checkout has uncommitted changes.
40
+
41
+ Keep guides and examples handwritten. An optional `<PressApiIndex :groups="groups" />` provides a category index with client-side filtering; each group has a `title` and `items: { text, link }[]`.
42
+
43
+ Deploy the resulting site normally. HTTP redirects from a retired API domain must be configured on that domain's host; installing this addon does not change DNS or redirects.
@@ -0,0 +1,43 @@
1
+ # valaxy-addon-typedoc
2
+
3
+ 在 Valaxy 站内生成 TypeScript API 参考文档。TypeDoc 仅在 Node 侧运行,Press 提供导航和文档布局。
4
+
5
+ 本插件依赖下一版 Valaxy 的生成内容集成能力。该版本发布前,请在本仓库的 pnpm workspace 中使用。
6
+
7
+ ```ts
8
+ import { defineValaxyConfig } from 'valaxy'
9
+ import { addonTypeDoc } from 'valaxy-addon-typedoc'
10
+
11
+ export default defineValaxyConfig({
12
+ theme: 'press',
13
+ addons: [addonTypeDoc({
14
+ options: './typedoc.json',
15
+ watch: ['../src/**/*.ts', './tsconfig*.json'],
16
+ routeBase: '/api/',
17
+ // 自己编写 pages/api/index.md 总览时启用。
18
+ excludeIndex: true,
19
+ })],
20
+ })
21
+ ```
22
+
23
+ ```json
24
+ {
25
+ "entryPoints": ["../src/index.ts"],
26
+ "tsconfig": "./tsconfig.typedoc.json",
27
+ "excludePrivate": true,
28
+ "excludeProtected": true,
29
+ "excludeInternal": true
30
+ }
31
+ ```
32
+
33
+ `options`、`watch` 相对 Valaxy 站点目录;TypeDoc JSON 中的路径相对该配置文件。请显式指定 `tsconfig`,避免误用文档应用的编译配置。`watch` 应覆盖源码目录,以发现新增文件和导出;插件还会追踪 TypeScript 依赖声明及继承的编译配置。
34
+
35
+ 插件负责 Markdown 输出,生成文件位于 `.valaxy/content/pages/`,不要手动编辑。缓存键包含源码内容、配置、依赖声明、编译器和插件版本、Git 提交。删除 `.valaxy/content/` 可强制重新生成。
36
+
37
+ 开发模式会合并短时间变更并串行生成;普通 Markdown 修改不会触发 TypeDoc。生成失败时保留上次成功页面并输出错误;生产模式直接阻断构建,输入匹配的缓存可正常复用。空文档、重复路由以及与手写页面冲突均会报错。
38
+
39
+ 自动侧栏只写入 `routeBase` 分区,保留其他分区;使用 `sidebar: false` 可自行管理导航。生成页标记 `sharedLocale: true`,切换界面语言保持同一个参考 URL。`themeConfig.apiReference` 和页面的 `apiSource` 提供源码提交、可获取的包版本及未提交修改标记。
40
+
41
+ 使用教程与示例继续手写。可选的 `<PressApiIndex :groups="groups" />` 提供分类和即时筛选;每组包含 `title` 和 `items: { text, link }[]`。
42
+
43
+ 按普通 Valaxy 站点部署即可。旧 API 域名的 HTTP 重定向需在旧域名的托管平台配置,安装插件不会修改 DNS 或重定向。
package/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { addonTypeDoc } from './node'
2
+ export type { TypeDocAddonOptions } from './node'
package/node/index.ts ADDED
@@ -0,0 +1,73 @@
1
+ import type { ContentLoaderContext } from 'valaxy'
2
+ import type { Plugin } from 'vite'
3
+ import { defineValaxyAddon, loadAllContent } from 'valaxy'
4
+ import { createTypeDocLoader } from './loader'
5
+
6
+ export interface TypeDocAddonOptions {
7
+ /** TypeDoc JSON configuration, relative to the Valaxy project. */
8
+ options: string
9
+ /** Source/configuration globs relative to the Valaxy project, used for cache invalidation and watching. */
10
+ watch: string[]
11
+ /** URL directory for generated reference pages. @default '/api/' */
12
+ routeBase?: string
13
+ /** Keep a handwritten landing page at routeBase. @default false */
14
+ excludeIndex?: boolean
15
+ /** Add the generated navigation to Press sidebars. @default true */
16
+ sidebar?: boolean
17
+ }
18
+
19
+ export const addonTypeDoc = defineValaxyAddon<TypeDocAddonOptions>((settings) => {
20
+ if (!settings?.options || !settings.watch?.length)
21
+ throw new Error('[typedoc] An options file and source watch globs are required.')
22
+ return {
23
+ name: 'valaxy-addon-typedoc',
24
+ options: settings,
25
+ setup(node) {
26
+ const loader = createTypeDocLoader(settings, node.options.userRoot)
27
+ node.options.config.loaders ??= []
28
+ node.options.config.loaders.push(loader)
29
+ const plugin: Plugin = {
30
+ name: 'valaxy-addon-typedoc:watch',
31
+ apply: 'serve',
32
+ async configureServer(server) {
33
+ const ctx: ContentLoaderContext = { node, cacheDir: `${node.options.tempDir}/content`, mode: 'dev' }
34
+ let closed = false
35
+ let timer: ReturnType<typeof setTimeout> | undefined
36
+ let pending = Promise.resolve()
37
+ const update = () => {
38
+ clearTimeout(timer)
39
+ timer = setTimeout(() => {
40
+ pending = pending.then(async () => {
41
+ if (closed)
42
+ return
43
+ await node.hooks.callHook('content:before-load')
44
+ await loadAllContent([loader], ctx)
45
+ await node.hooks.callHook('content:loaded')
46
+ server.watcher.add(await loader.watchPaths())
47
+ const config = server.moduleGraph.getModuleById('/@valaxyjs/config')
48
+ if (config)
49
+ server.moduleGraph.invalidateModule(config)
50
+ // Added/deleted routes and navigation must become visible together.
51
+ server.ws.send({ type: 'full-reload' })
52
+ }).catch(error => server.config.logger.error(`[typedoc] ${error}`))
53
+ }, 200)
54
+ }
55
+ const change = (file: string) => {
56
+ if (loader.isSource(file))
57
+ update()
58
+ }
59
+ server.watcher.add(await loader.watchPaths())
60
+ server.watcher.on('add', change).on('change', change).on('unlink', change)
61
+ server.httpServer?.once('close', () => {
62
+ closed = true
63
+ clearTimeout(timer)
64
+ server.watcher.off('add', change).off('change', change).off('unlink', change)
65
+ })
66
+ },
67
+ }
68
+ node.options.config.vite ??= {}
69
+ node.options.config.vite.plugins ??= []
70
+ node.options.config.vite.plugins.push(plugin)
71
+ },
72
+ }
73
+ })
package/node/links.ts ADDED
@@ -0,0 +1,14 @@
1
+ /** Reconcile TypeDoc inheritance links with the Markdown plugin's HTML anchors. */
2
+ export function resolveSymbolAnchors(markdown: string, anchors: Map<string, Set<string>>) {
3
+ // Preserve fenced examples and inline code verbatim.
4
+ return markdown.split(/(```[\s\S]*?```|~~~[\s\S]*?~~~|`+[^`]*`+)/g).map((part, index) => {
5
+ if (index % 2)
6
+ return part
7
+ return part.replace(/\]\((\/[^\s)]*\.md)#([^\s)]+)\)/g, (link, page: string, hash: string) => {
8
+ const symbol = `api-${decodeURIComponent(hash)}`
9
+ return anchors.get(decodeURIComponent(page))?.has(symbol)
10
+ ? `](${page}#${encodeURIComponent(symbol)})`
11
+ : link
12
+ })
13
+ }).join('')
14
+ }
package/node/loader.ts ADDED
@@ -0,0 +1,207 @@
1
+ import type { ContentItem, ContentLoader, ContentLoaderContext } from 'valaxy'
2
+ import type { TypeDocAddonOptions } from './index'
3
+ import { execFileSync, spawn } from 'node:child_process'
4
+ import { createHash } from 'node:crypto'
5
+ import { createRequire } from 'node:module'
6
+ import { dirname, relative, resolve, sep } from 'node:path'
7
+ import process from 'node:process'
8
+ import { fileURLToPath } from 'node:url'
9
+ import fg from 'fast-glob'
10
+ import fs from 'fs-extra'
11
+ import { minimatch } from 'minimatch'
12
+ import { resolveSymbolAnchors } from './links'
13
+
14
+ interface NavigationItem {
15
+ title: string
16
+ path?: string | null
17
+ children?: NavigationItem[]
18
+ }
19
+
20
+ interface SidebarItem {
21
+ text: string
22
+ link?: string
23
+ collapsed?: boolean
24
+ items?: SidebarItem[]
25
+ }
26
+
27
+ interface Cache {
28
+ key: string
29
+ files: string[]
30
+ items: ContentItem[]
31
+ navigation: NavigationItem[]
32
+ source: { revision: string, dirty: boolean, version?: string }
33
+ }
34
+
35
+ const require = createRequire(import.meta.url)
36
+
37
+ export function createTypeDocLoader(settings: TypeDocAddonOptions, userRoot: string) {
38
+ const optionsFile = resolve(userRoot, settings.options)
39
+ const routeBase = `/${(settings.routeBase || '/api/').replace(/^\/+|\/+$/g, '')}/`
40
+ if (routeBase.includes('..') || routeBase.includes('\\') || routeBase.includes('#') || routeBase.includes('?'))
41
+ throw new Error('[typedoc] routeBase must be an absolute URL directory without traversal.')
42
+ const watchedRoots = settings.watch.filter(pattern => !pattern.startsWith('!')).map(pattern => resolve(userRoot, pattern.split(/[*?{[]/)[0]))
43
+ let dependencies: string[] = []
44
+ let navigation: NavigationItem[] = []
45
+ let source: Cache['source'] = { revision: 'unversioned', dirty: false }
46
+ let runs = 0
47
+
48
+ async function sourceFiles() {
49
+ const sources = await fg(settings.watch, { cwd: userRoot, absolute: true, onlyFiles: true, ignore: ['**/.valaxy/**', '**/dist/**'] })
50
+ return [...new Set([optionsFile, ...sources, ...dependencies])].sort()
51
+ }
52
+
53
+ async function fingerprint(files: string[]) {
54
+ const hash = createHash('sha256').update(JSON.stringify({ schema: 8, settings }))
55
+ for (const pkg of ['valaxy-addon-typedoc', 'typedoc', 'typedoc-plugin-markdown', 'typescript']) {
56
+ let directory = dirname(require.resolve(pkg))
57
+ while (directory !== dirname(directory)) {
58
+ const metadata = await fs.readJson(resolve(directory, 'package.json')).catch(() => undefined)
59
+ if (metadata?.name === pkg) {
60
+ hash.update(JSON.stringify(metadata))
61
+ break
62
+ }
63
+ directory = dirname(directory)
64
+ }
65
+ }
66
+ // Source links generated by TypeDoc point at this exact revision.
67
+ try {
68
+ source.revision = execFileSync('git', ['rev-parse', 'HEAD'], { cwd: userRoot, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim()
69
+ source.dirty = !!execFileSync('git', ['status', '--porcelain'], { cwd: userRoot, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim()
70
+ hash.update(JSON.stringify({ revision: source.revision, dirty: source.dirty }))
71
+ }
72
+ catch { hash.update('unversioned') }
73
+ for (const file of files) {
74
+ hash.update(file)
75
+ hash.update(await fs.readFile(file).catch(() => 'missing'))
76
+ }
77
+ return hash.digest('hex')
78
+ }
79
+
80
+ const toSidebar = (items: NavigationItem[]): SidebarItem[] => items.map(item => ({
81
+ text: item.title,
82
+ ...(item.path ? { link: `${routeBase}${item.path.replace(/(^|\/)index\.md$/, '$1').replace(/\.md(?=#|$)/, '')}` } : {}),
83
+ ...(item.children?.length ? { collapsed: true, items: toSidebar(item.children) } : {}),
84
+ }))
85
+
86
+ const loader: ContentLoader & { sourceFiles: typeof sourceFiles, watchPaths: () => Promise<string[]>, isSource: (file: string) => boolean, generationCount: () => number } = {
87
+ name: `typedoc-${routeBase.replaceAll('/', '-')}`,
88
+ strict: true,
89
+ sourceFiles,
90
+ watchPaths: async () => [...new Set([...watchedRoots, ...await sourceFiles()])],
91
+ generationCount: () => runs,
92
+ isSource(file) {
93
+ const path = relative(userRoot, file).replaceAll('\\', '/')
94
+ if (/(?:^|\/)\.valaxy\/|(?:^|\/)dist\//.test(path))
95
+ return false
96
+ const excluded = settings.watch.filter(pattern => pattern.startsWith('!'))
97
+ const included = settings.watch.filter(pattern => !pattern.startsWith('!'))
98
+ return file === optionsFile || dependencies.includes(file)
99
+ || (included.some(pattern => minimatch(path, pattern, { dot: true }))
100
+ && !excluded.some(pattern => minimatch(path, pattern.slice(1), { dot: true })))
101
+ },
102
+ async load(ctx) {
103
+ const cacheRoot = resolve(ctx.cacheDir, loader.name)
104
+ const manifest = resolve(cacheRoot, 'cache.json')
105
+ const previous = await fs.readJson(manifest).catch(() => undefined) as Cache | undefined
106
+ dependencies = previous?.files || []
107
+ const files = await sourceFiles()
108
+ const key = await fingerprint(files)
109
+ if (previous?.key === key && previous.items.length) {
110
+ navigation = previous.navigation
111
+ source = previous.source
112
+ return previous.items
113
+ }
114
+
115
+ const started = performance.now()
116
+ const output = resolve(cacheRoot, 'staging')
117
+ await fs.ensureDir(output)
118
+ const resultFile = resolve(cacheRoot, 'worker-result.json')
119
+ runs++
120
+ await new Promise<void>((accept, reject) => {
121
+ const child = spawn(process.execPath, [
122
+ fileURLToPath(new URL('./worker.mjs', import.meta.url)),
123
+ optionsFile,
124
+ output,
125
+ resolve(cacheRoot, 'navigation.json'),
126
+ resultFile,
127
+ routeBase,
128
+ ], { cwd: userRoot, stdio: ['ignore', 'pipe', 'pipe'] })
129
+ let diagnostics = ''
130
+ child.stdout.on('data', chunk => process.stdout.write(chunk))
131
+ child.stderr.on('data', (chunk) => {
132
+ diagnostics = (diagnostics + String(chunk)).slice(-8000)
133
+ process.stderr.write(chunk)
134
+ })
135
+ child.once('error', reject)
136
+ child.once('close', code => code === 0 ? accept() : reject(new Error(`[typedoc] Generation failed (${code}). ${diagnostics}`)))
137
+ })
138
+ const result = await fs.readJson(resultFile) as { files: string[], version?: string }
139
+ dependencies = result.files
140
+ source.version = result.version
141
+ const paths = await fg('**/*.md', { cwd: output })
142
+ if (!paths.length)
143
+ throw new Error('[typedoc] Refusing to publish empty API documentation.')
144
+ const sources = new Map(await Promise.all(paths.map(async page => [page, await fs.readFile(resolve(output, page), 'utf8')] as const)))
145
+ const anchors = new Map([...sources].map(([page, markdown]) => [
146
+ `${routeBase}${page}`,
147
+ new Set([...markdown.matchAll(/<a id="([^"]+)"><\/a>/g)].map(match => match[1])),
148
+ ]))
149
+ const items: ContentItem[] = []
150
+ for (const page of paths.sort()) {
151
+ if (settings.excludeIndex && page === 'index.md')
152
+ continue
153
+ const markdown = resolveSymbolAnchors(sources.get(page)!, anchors)
154
+ const title = markdown.match(/^# (.+?)(?: \{#.*\})?$/m)?.[1] || page
155
+ const revision = `${source.version ? `${source.version} · ` : ''}${source.revision.slice(0, 12)}${source.dirty ? ' (uncommitted changes)' : ''}`
156
+ const body = markdown.replace(/^(# .+\n)/m, `$1\n> Source: ${revision}\n`)
157
+ const canonical = `${routeBase}${page.replace(/(^|\/)index\.md$/, '$1').replace(/\.md$/, '')}`
158
+ const content = `---\ncanonical: ${JSON.stringify(canonical)}\ntitle: ${JSON.stringify(title)}\npageTitle: false\napi: true\napiSource: ${JSON.stringify(source)}\nsharedLocale: true\neditLink: false\nlastUpdated: false\n---\n\n${body}`
159
+ items.push({ path: `${routeBase.slice(1)}${page}`, content })
160
+ }
161
+ if (!items.length)
162
+ throw new Error('[typedoc] Refusing to replace references with an empty page set.')
163
+ navigation = await fs.readJson(resolve(cacheRoot, 'navigation.json'))
164
+ // Include parent project configuration and package metadata in dependency invalidation.
165
+ const checkedDirectories = new Set<string>()
166
+ for (const file of [...dependencies, optionsFile]) {
167
+ let directory = dirname(file)
168
+ while (directory !== dirname(directory) && !checkedDirectories.has(directory)) {
169
+ checkedDirectories.add(directory)
170
+ if (!directory.includes(`${sep}node_modules${sep}`)) {
171
+ for (const name of ['tsconfig.json', 'package.json', 'pnpm-lock.yaml']) {
172
+ const config = resolve(directory, name)
173
+ if (await fs.pathExists(config))
174
+ dependencies.push(config)
175
+ }
176
+ }
177
+ directory = dirname(directory)
178
+ }
179
+ }
180
+ dependencies = [...new Set(dependencies)]
181
+ if (await fingerprint(files) !== key)
182
+ throw new Error('[typedoc] Inputs changed during generation; previous pages were preserved. Retry the build.')
183
+ const finalFiles = await sourceFiles()
184
+ const next: Cache = { key: await fingerprint(finalFiles), files: finalFiles, items, navigation, source }
185
+ await fs.writeJson(`${manifest}.tmp`, next)
186
+ await fs.rename(`${manifest}.tmp`, manifest)
187
+ process.stdout.write(`[typedoc] Generated ${items.length} pages in ${((performance.now() - started) / 1000).toFixed(2)}s\n`)
188
+ return items
189
+ },
190
+ onLoaded(ctx: ContentLoaderContext) {
191
+ const theme = ctx.node.options.config.themeConfig as Record<string, any>
192
+ theme.apiReference = source
193
+ if (settings.sidebar === false)
194
+ return
195
+ const install = (config: Record<string, any>) => {
196
+ const sidebar = config.sidebar
197
+ config.sidebar = { ...(Array.isArray(sidebar) ? { '/': sidebar } : sidebar), [routeBase]: toSidebar(navigation) }
198
+ }
199
+ install(theme)
200
+ for (const locale of Object.values(theme.locales || {}) as Record<string, any>[]) {
201
+ locale.themeConfig ??= {}
202
+ install(locale.themeConfig)
203
+ }
204
+ },
205
+ }
206
+ return loader
207
+ }
@@ -0,0 +1,63 @@
1
+ import { writeFile } from 'node:fs/promises'
2
+ import { createRequire } from 'node:module'
3
+ import process from 'node:process'
4
+ import { Application, Converter } from 'typedoc'
5
+ import ts from 'typescript'
6
+
7
+ async function main() {
8
+ // A short-lived process releases the compiler's AST before Vite starts bundling.
9
+ const [options, out, navigationJson, resultFile, publicPath] = process.argv.slice(2)
10
+ const require = createRequire(import.meta.url)
11
+ const app = await Application.bootstrapWithPlugins({
12
+ options,
13
+ plugin: [require.resolve('typedoc-plugin-markdown')],
14
+ out,
15
+ readme: 'none',
16
+ includeVersion: true,
17
+ githubPages: false,
18
+ entryFileName: 'index.md',
19
+ hidePageHeader: true,
20
+ hideBreadcrumbs: true,
21
+ useCodeBlocks: true,
22
+ // Normal heading IDs remain compatible with the old site. Symbol links use
23
+ // a separate namespace, avoiding collisions with comment headings.
24
+ useCustomAnchors: false,
25
+ useHTMLAnchors: true,
26
+ anchorPrefix: 'api-',
27
+ navigationJson,
28
+ publicPath,
29
+ })
30
+ const files = new Set()
31
+ app.converter.on(Converter.EVENT_BEGIN, (context) => {
32
+ for (const program of context.programs) {
33
+ for (const file of program.getSourceFiles())
34
+ files.add(file.fileName)
35
+ }
36
+ })
37
+ const tsconfig = app.options.getValue('tsconfig')
38
+ if (tsconfig) {
39
+ ts.getParsedCommandLineOfConfigFile(tsconfig, {}, {
40
+ ...ts.sys,
41
+ readFile(file) {
42
+ files.add(file)
43
+ return ts.sys.readFile(file)
44
+ },
45
+ onUnRecoverableConfigFileDiagnostic: diagnostic => app.logger.error(ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n')),
46
+ })
47
+ }
48
+ const project = await app.convert()
49
+ if (!project || app.logger.hasErrors())
50
+ throw new Error('[typedoc] TypeScript conversion failed; previous pages were preserved.')
51
+ app.validate(project)
52
+ if (app.logger.hasErrors())
53
+ throw new Error('[typedoc] Reference validation failed.')
54
+ await app.generateOutputs(project)
55
+ if (app.logger.hasErrors())
56
+ throw new Error('[typedoc] Markdown generation failed.')
57
+ await writeFile(resultFile, JSON.stringify({ files: [...files], version: project.packageVersion }))
58
+ }
59
+
60
+ main().catch((error) => {
61
+ console.error(error)
62
+ process.exitCode = 1
63
+ })
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "valaxy-addon-typedoc",
3
+ "type": "module",
4
+ "version": "0.1.0",
5
+ "description": "Cached TypeDoc reference pages for Valaxy documentation sites",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/YunYouJun/valaxy.git",
10
+ "directory": "packages/valaxy-addon-typedoc"
11
+ },
12
+ "exports": {
13
+ ".": "./index.ts",
14
+ "./node": "./node/index.ts",
15
+ "./package.json": "./package.json"
16
+ },
17
+ "files": [
18
+ "README.md",
19
+ "README.zh-CN.md",
20
+ "index.ts",
21
+ "node"
22
+ ],
23
+ "engines": {
24
+ "node": ">=22.12.0"
25
+ },
26
+ "peerDependencies": {
27
+ "valaxy": ">=1.0.0-rc.13 <2"
28
+ },
29
+ "dependencies": {
30
+ "fast-glob": "^3.3.3",
31
+ "fs-extra": "^11.4.0",
32
+ "minimatch": "^10.2.4",
33
+ "typedoc": "^0.28.20",
34
+ "typedoc-plugin-markdown": "^4.13.1",
35
+ "typescript": "^5.9.3"
36
+ },
37
+ "devDependencies": {
38
+ "valaxy": "1.0.0-rc.15"
39
+ },
40
+ "publishConfig": {
41
+ "access": "public"
42
+ }
43
+ }