@pterodoc/docusaurus 0.2.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/LICENCE.md +10 -0
- package/README.md +10 -0
- package/lib/index.d.ts +19 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +276 -0
- package/lib/index.js.map +1 -0
- package/lib/model.d.ts +41 -0
- package/lib/model.d.ts.map +1 -0
- package/lib/reader.d.ts +14 -0
- package/lib/reader.d.ts.map +1 -0
- package/lib/server.d.ts +64 -0
- package/lib/server.d.ts.map +1 -0
- package/package.json +37 -0
- package/src/index.ts +31 -0
- package/src/model.ts +233 -0
- package/src/reader.ts +36 -0
- package/src/server.ts +111 -0
package/LICENCE.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)
|
|
2
|
+
|
|
3
|
+
Copyright (c) Onyx <hello@onyx.ac> (https://onyx.ac)
|
|
4
|
+
|
|
5
|
+
This work is licensed under the Creative Commons Attribution-ShareAlike 4.0
|
|
6
|
+
International License. You are free to share and adapt this work, including
|
|
7
|
+
commercially, provided you give appropriate attribution and distribute any
|
|
8
|
+
derivative works under the same license.
|
|
9
|
+
|
|
10
|
+
Full legal text: https://creativecommons.org/licenses/by-sa/4.0/legalcode
|
package/README.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# @pterodoc/docusaurus
|
|
2
|
+
|
|
3
|
+
Reads a [pterodoc](https://github.com/onyx-ac/pterodoc) site model out of Docusaurus,
|
|
4
|
+
through Docusaurus's own site loader — so resolved sidebars, versions, locales,
|
|
5
|
+
permalinks and draft flags all come from the same code that builds your site.
|
|
6
|
+
|
|
7
|
+
You probably want the [`pterodoc`](https://www.npmjs.com/package/pterodoc) package
|
|
8
|
+
instead.
|
|
9
|
+
|
|
10
|
+
Licensed CC-BY-SA-4.0.
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Everything that knows Docusaurus exists.
|
|
3
|
+
*
|
|
4
|
+
* The model vocabulary it produces belongs to `src/model`; it is re-exported
|
|
5
|
+
* here so a caller that thinks in Docusaurus terms has one import.
|
|
6
|
+
*/
|
|
7
|
+
export type { Doc, DocNeighbour, DocsInstance, DocsVersion, SidebarCategoryItem, SidebarCategoryLink, SidebarDocItem, SidebarHtmlItem, SidebarItem, SidebarLinkItem, SiteModel, } from '@pterodoc/core/model';
|
|
8
|
+
export { buildPageTree } from '@pterodoc/core/model';
|
|
9
|
+
export type { BuildPageTreeInput, PageKind, PageNode, PageTree } from '@pterodoc/core/model';
|
|
10
|
+
export { createCaptureReader, readCapture, writeCapture, serializeModel } from '@pterodoc/core/model';
|
|
11
|
+
export type { CapturedModel } from '@pterodoc/core/model';
|
|
12
|
+
export { createMemoryReader } from '@pterodoc/core/model';
|
|
13
|
+
export type { SourceReader } from '@pterodoc/core/model';
|
|
14
|
+
export { loadModel, toSiteModel } from './model';
|
|
15
|
+
export type { LoadModelOptions } from './model';
|
|
16
|
+
export { loadDocusaurusServer, SUPPORTED_RANGE } from './server';
|
|
17
|
+
export type { DocusaurusServer, LoadedSite, LoadSiteParams } from './server';
|
|
18
|
+
export { createDocusaurusReader, readerFromLoadedSite } from './reader';
|
|
19
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,YAAY,EACV,GAAG,EACH,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,mBAAmB,EACnB,mBAAmB,EACnB,cAAc,EACd,eAAe,EACf,WAAW,EACX,eAAe,EACf,SAAS,GACV,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,YAAY,EAAE,kBAAkB,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAC7F,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtG,YAAY,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,YAAY,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACjD,YAAY,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AACjE,YAAY,EAAE,gBAAgB,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC7E,OAAO,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC"}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { createMemoryReader } from '@pterodoc/core/model';
|
|
2
|
+
export { buildPageTree, createCaptureReader, createMemoryReader, readCapture, serializeModel, writeCapture } from '@pterodoc/core/model';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { DEFAULT_ADMONITION_KEYWORDS, detectFormat } from '@pterodoc/core/render';
|
|
5
|
+
import { ConfigError, resolveAliasedPath, relativeToPrefix, toPosix } from '@pterodoc/core/util';
|
|
6
|
+
import { createRequire } from 'node:module';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Loading Docusaurus's own site loader.
|
|
10
|
+
*
|
|
11
|
+
* `loadSite` runs every plugin's content lifecycle and hands back the resolved
|
|
12
|
+
* site, which is the whole reason this tool does not re-derive sidebars,
|
|
13
|
+
* versions or permalinks. It lives at a path Docusaurus does not advertise, so
|
|
14
|
+
* the import is guarded and fails with something a reader can act on.
|
|
15
|
+
*/
|
|
16
|
+
/** Docusaurus versions this tool has been checked against. */
|
|
17
|
+
const SUPPORTED_RANGE = { min: 3, maxExclusive: 4 };
|
|
18
|
+
/** Paths tried, in order, when looking for the site loader. */
|
|
19
|
+
const CANDIDATES = ['lib/server/site.js', 'server.js', 'lib/server/index.js'];
|
|
20
|
+
/**
|
|
21
|
+
* Find and load Docusaurus's site loader, resolving it from the site rather
|
|
22
|
+
* than from pterodoc so a hoisted or a nested install both work.
|
|
23
|
+
*
|
|
24
|
+
* @param siteDir Absolute path of the Docusaurus site directory.
|
|
25
|
+
* @param warn Called with a message when the version is outside the tested range.
|
|
26
|
+
*/
|
|
27
|
+
function loadDocusaurusServer(siteDir, warn = () => { }) {
|
|
28
|
+
const requireFromSite = createRequire(path.join(siteDir, 'noop.js'));
|
|
29
|
+
let corePackagePath;
|
|
30
|
+
try {
|
|
31
|
+
corePackagePath = requireFromSite.resolve('@docusaurus/core/package.json');
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
throw new ConfigError(`Could not find @docusaurus/core from ${siteDir}. pterodoc reads a Docusaurus site using Docusaurus itself, so it has to run inside the site's own project. Use --site-dir to point at it, or --model to render from a captured model instead.`);
|
|
35
|
+
}
|
|
36
|
+
const corePackage = requireFromSite(corePackagePath);
|
|
37
|
+
const version = corePackage.version ?? '0.0.0';
|
|
38
|
+
const major = Number.parseInt(version.split('.')[0] ?? '0', 10);
|
|
39
|
+
if (major < SUPPORTED_RANGE.min || major >= SUPPORTED_RANGE.maxExclusive) {
|
|
40
|
+
warn(`This is Docusaurus ${version}; pterodoc has been checked against ${SUPPORTED_RANGE.min}.x. Continuing, but the site model may not load.`);
|
|
41
|
+
}
|
|
42
|
+
const coreDir = path.dirname(corePackagePath);
|
|
43
|
+
const tried = [];
|
|
44
|
+
for (const candidate of CANDIDATES) {
|
|
45
|
+
const candidatePath = path.join(coreDir, candidate);
|
|
46
|
+
tried.push(candidatePath);
|
|
47
|
+
try {
|
|
48
|
+
const loaded = requireFromSite(candidatePath);
|
|
49
|
+
if (typeof loaded.loadSite === 'function') {
|
|
50
|
+
return { loadSite: loaded.loadSite, version };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// Try the next candidate.
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
throw new ConfigError(`Docusaurus ${version} is installed but its site loader was not where pterodoc expected it.\n` +
|
|
58
|
+
`Tried:\n${tried.map((entry) => ` ${entry}`).join('\n')}\n` +
|
|
59
|
+
'This usually means a Docusaurus release moved it. Capture a model on a working version with `pterodoc capture`, then render from it with `--model`, and please report the version.');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Normalising Docusaurus's loaded state into the site model.
|
|
64
|
+
*
|
|
65
|
+
* The mapping is deliberately thin: almost every field is copied, because the
|
|
66
|
+
* point of loading Docusaurus is to inherit its answers rather than to
|
|
67
|
+
* second-guess them.
|
|
68
|
+
*/
|
|
69
|
+
/** Name Docusaurus gives the docs plugin. */
|
|
70
|
+
const DOCS_PLUGIN = 'docusaurus-plugin-content-docs';
|
|
71
|
+
/** Read a nested property without pretending to know the whole shape. */
|
|
72
|
+
function get(source, keys, fallback) {
|
|
73
|
+
let current = source;
|
|
74
|
+
for (const key of keys) {
|
|
75
|
+
if (current === null || typeof current !== 'object')
|
|
76
|
+
return fallback;
|
|
77
|
+
current = current[key];
|
|
78
|
+
}
|
|
79
|
+
return (current ?? fallback);
|
|
80
|
+
}
|
|
81
|
+
/** Docusaurus's admonition option is `true`, or an object that may extend the defaults. */
|
|
82
|
+
function admonitionKeywords(options) {
|
|
83
|
+
const admonitions = options['admonitions'];
|
|
84
|
+
if (admonitions === false)
|
|
85
|
+
return [];
|
|
86
|
+
if (admonitions === true || admonitions === undefined)
|
|
87
|
+
return [...DEFAULT_ADMONITION_KEYWORDS];
|
|
88
|
+
const keywords = get(admonitions, ['keywords'], undefined);
|
|
89
|
+
const extendDefaults = get(admonitions, ['extendDefaults'], true);
|
|
90
|
+
if (!keywords)
|
|
91
|
+
return [...DEFAULT_ADMONITION_KEYWORDS];
|
|
92
|
+
return extendDefaults ? [...new Set([...DEFAULT_ADMONITION_KEYWORDS, ...keywords])] : [...keywords];
|
|
93
|
+
}
|
|
94
|
+
/** Turn one of Docusaurus's loaded documents into ours. */
|
|
95
|
+
function toDoc(raw, version, siteDir, siteFormat) {
|
|
96
|
+
const sourceAliased = String(raw['source'] ?? '');
|
|
97
|
+
const sourceAbsolutePath = resolveAliasedPath(sourceAliased, siteDir);
|
|
98
|
+
const permalink = String(raw['permalink'] ?? '');
|
|
99
|
+
const frontMatter = (raw['frontMatter'] ?? {});
|
|
100
|
+
const declaredFormat = frontMatter['format'];
|
|
101
|
+
const format = declaredFormat === 'md' || declaredFormat === 'mdx'
|
|
102
|
+
? declaredFormat
|
|
103
|
+
: siteFormat === 'detect'
|
|
104
|
+
? detectFormat(sourceAbsolutePath)
|
|
105
|
+
: siteFormat;
|
|
106
|
+
return {
|
|
107
|
+
id: String(raw['id'] ?? ''),
|
|
108
|
+
versionName: version.name,
|
|
109
|
+
title: String(raw['title'] ?? ''),
|
|
110
|
+
description: String(raw['description'] ?? ''),
|
|
111
|
+
sourceAliased,
|
|
112
|
+
sourceAbsolutePath,
|
|
113
|
+
sourceRelativePath: toPosix(path.relative(siteDir, sourceAbsolutePath)),
|
|
114
|
+
sourceDirName: String(raw['sourceDirName'] ?? '.'),
|
|
115
|
+
slug: String(raw['slug'] ?? ''),
|
|
116
|
+
permalink,
|
|
117
|
+
// The position in the published tree is the permalink with the version's
|
|
118
|
+
// own prefix removed, so front-matter slugs are honoured for free.
|
|
119
|
+
treePath: relativeToPrefix(permalink, version.pathPrefix) ?? '',
|
|
120
|
+
draft: raw['draft'] === true,
|
|
121
|
+
unlisted: raw['unlisted'] === true,
|
|
122
|
+
frontMatter,
|
|
123
|
+
sidebarName: raw['sidebar'] ?? undefined,
|
|
124
|
+
sidebarPosition: raw['sidebarPosition'] ?? undefined,
|
|
125
|
+
previous: raw['previous'] ?? undefined,
|
|
126
|
+
next: raw['next'] ?? undefined,
|
|
127
|
+
tags: raw['tags'] ?? [],
|
|
128
|
+
format,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
/** Turn one of Docusaurus's loaded versions into ours. */
|
|
132
|
+
function toVersion(raw, siteDir, siteFormat, includeDrafts) {
|
|
133
|
+
const name = String(raw['versionName'] ?? 'current');
|
|
134
|
+
const pathPrefix = String(raw['path'] ?? '');
|
|
135
|
+
const contentPath = String(raw['contentPath'] ?? '');
|
|
136
|
+
const published = raw['docs'] ?? [];
|
|
137
|
+
const drafts = raw['drafts'] ?? [];
|
|
138
|
+
const source = includeDrafts ? [...published, ...drafts] : published;
|
|
139
|
+
return {
|
|
140
|
+
name,
|
|
141
|
+
label: String(raw['label'] ?? name),
|
|
142
|
+
isLast: raw['isLast'] === true,
|
|
143
|
+
pathPrefix,
|
|
144
|
+
contentPath,
|
|
145
|
+
contentPathLocalized: String(raw['contentPathLocalized'] ?? contentPath),
|
|
146
|
+
banner: raw['banner'] ?? null,
|
|
147
|
+
noIndex: raw['noIndex'] === true,
|
|
148
|
+
sidebars: raw['sidebars'] ?? {},
|
|
149
|
+
docs: source.map((doc) => toDoc(doc, { name, pathPrefix }, siteDir, siteFormat)),
|
|
150
|
+
draftCount: drafts.length,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
/** Keep only the versions the caller asked for. */
|
|
154
|
+
function selectVersions(versions, selector) {
|
|
155
|
+
if (selector === 'all')
|
|
156
|
+
return versions;
|
|
157
|
+
if (Array.isArray(selector))
|
|
158
|
+
return versions.filter((version) => selector.includes(version.name));
|
|
159
|
+
const last = versions.find((version) => version.isLast);
|
|
160
|
+
return last ? [last] : versions.slice(0, 1);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Normalise a loaded Docusaurus site into the model.
|
|
164
|
+
*
|
|
165
|
+
* Exposed separately from {@link loadModel} so the plugin, which already has
|
|
166
|
+
* the loaded state, can reuse it without loading the site again.
|
|
167
|
+
*/
|
|
168
|
+
function toSiteModel(site, options) {
|
|
169
|
+
const { props } = site;
|
|
170
|
+
const siteDir = props.siteDir;
|
|
171
|
+
const siteConfig = props.siteConfig;
|
|
172
|
+
const siteFormat = get(siteConfig, ['markdown', 'format'], 'mdx');
|
|
173
|
+
const staticDirectories = get(siteConfig, ['staticDirectories'], ['static']);
|
|
174
|
+
const instances = props.plugins
|
|
175
|
+
.filter((plugin) => plugin.name === DOCS_PLUGIN)
|
|
176
|
+
.map((plugin) => {
|
|
177
|
+
const loadedVersions = get(plugin.content, ['loadedVersions'], []) ?? [];
|
|
178
|
+
const versions = loadedVersions.map((version) => toVersion(version, siteDir, siteFormat, options.includeDrafts === true));
|
|
179
|
+
return {
|
|
180
|
+
id: String(plugin.options['id'] ?? 'default'),
|
|
181
|
+
routeBasePath: String(plugin.options['routeBasePath'] ?? 'docs'),
|
|
182
|
+
contentDirName: String(plugin.options['path'] ?? 'docs'),
|
|
183
|
+
admonitionKeywords: admonitionKeywords(plugin.options),
|
|
184
|
+
breadcrumbs: plugin.options['breadcrumbs'] !== false,
|
|
185
|
+
versions: selectVersions(versions, options.versions),
|
|
186
|
+
};
|
|
187
|
+
})
|
|
188
|
+
.filter((instance) => options.instances === undefined ||
|
|
189
|
+
options.instances === 'all' ||
|
|
190
|
+
options.instances.includes(instance.id));
|
|
191
|
+
return {
|
|
192
|
+
siteDir,
|
|
193
|
+
url: String(siteConfig['url'] ?? ''),
|
|
194
|
+
baseUrl: props.baseUrl,
|
|
195
|
+
trailingSlash: siteConfig['trailingSlash'],
|
|
196
|
+
locale: props.i18n.currentLocale,
|
|
197
|
+
defaultLocale: props.i18n.defaultLocale,
|
|
198
|
+
locales: props.i18n.locales,
|
|
199
|
+
markdownFormat: siteFormat,
|
|
200
|
+
maintainCase: get(siteConfig, ['markdown', 'anchors', 'maintainCase'], false),
|
|
201
|
+
siteTitle: String(siteConfig['title'] ?? ''),
|
|
202
|
+
staticDirs: staticDirectories.map((dir) => path.resolve(siteDir, dir)),
|
|
203
|
+
docusaurusVersion: props.siteMetadata.docusaurusVersion,
|
|
204
|
+
instances,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Load a Docusaurus site and normalise it.
|
|
209
|
+
*
|
|
210
|
+
* One call loads one locale, because that is what Docusaurus's own loader
|
|
211
|
+
* does; publishing several locales means calling this once for each.
|
|
212
|
+
*/
|
|
213
|
+
async function loadModel(options) {
|
|
214
|
+
const warn = options.warn ?? (() => { });
|
|
215
|
+
const server = loadDocusaurusServer(options.siteDir, warn);
|
|
216
|
+
// Docusaurus's own CLI always runs from the site directory, and plugins rely
|
|
217
|
+
// on it: a typedoc entry point of `../client/src/index.ts`, for instance, is
|
|
218
|
+
// resolved against the working directory rather than the site. Loading from
|
|
219
|
+
// anywhere else makes those plugins look in the wrong place.
|
|
220
|
+
const previousCwd = process.cwd();
|
|
221
|
+
process.chdir(options.siteDir);
|
|
222
|
+
// Docusaurus only drops drafts and applies production behaviour when
|
|
223
|
+
// NODE_ENV says so, and publishing is a production act. Without this a draft
|
|
224
|
+
// reaches the site, which is precisely what marking it a draft asked to
|
|
225
|
+
// avoid.
|
|
226
|
+
const previousNodeEnv = process.env['NODE_ENV'];
|
|
227
|
+
process.env['NODE_ENV'] = 'production';
|
|
228
|
+
try {
|
|
229
|
+
const site = await server.loadSite({
|
|
230
|
+
siteDir: options.siteDir,
|
|
231
|
+
locale: options.locale,
|
|
232
|
+
config: options.configPath,
|
|
233
|
+
});
|
|
234
|
+
return toSiteModel(site, options);
|
|
235
|
+
}
|
|
236
|
+
finally {
|
|
237
|
+
process.chdir(previousCwd);
|
|
238
|
+
if (previousNodeEnv === undefined)
|
|
239
|
+
delete process.env['NODE_ENV'];
|
|
240
|
+
else
|
|
241
|
+
process.env['NODE_ENV'] = previousNodeEnv;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Reading a model out of a real Docusaurus site.
|
|
247
|
+
*
|
|
248
|
+
* The reader interface and the sources that need no Docusaurus live in
|
|
249
|
+
* `src/model`; this is the one that does.
|
|
250
|
+
*/
|
|
251
|
+
/** Read from a real Docusaurus site, one locale at a time. */
|
|
252
|
+
function createDocusaurusReader(options) {
|
|
253
|
+
let firstLocale;
|
|
254
|
+
return {
|
|
255
|
+
kind: 'docusaurus',
|
|
256
|
+
async locales() {
|
|
257
|
+
firstLocale ??= await loadModel(options);
|
|
258
|
+
return firstLocale.locales;
|
|
259
|
+
},
|
|
260
|
+
async read(locale) {
|
|
261
|
+
if (firstLocale && (locale === undefined || locale === firstLocale.locale)) {
|
|
262
|
+
return firstLocale;
|
|
263
|
+
}
|
|
264
|
+
const model = await loadModel({ ...options, locale });
|
|
265
|
+
firstLocale ??= model;
|
|
266
|
+
return model;
|
|
267
|
+
},
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
/** Build a model from a site the caller has already loaded. */
|
|
271
|
+
function readerFromLoadedSite(site, options) {
|
|
272
|
+
return createMemoryReader(toSiteModel(site, options));
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export { SUPPORTED_RANGE, createDocusaurusReader, loadDocusaurusServer, loadModel, readerFromLoadedSite, toSiteModel };
|
|
276
|
+
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/server.ts","../src/model.ts","../src/reader.ts"],"sourcesContent":[null,null,null],"names":[],"mappings":"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"}
|
package/lib/model.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalising Docusaurus's loaded state into the site model.
|
|
3
|
+
*
|
|
4
|
+
* The mapping is deliberately thin: almost every field is copied, because the
|
|
5
|
+
* point of loading Docusaurus is to inherit its answers rather than to
|
|
6
|
+
* second-guess them.
|
|
7
|
+
*/
|
|
8
|
+
import { type LoadedSite } from './server';
|
|
9
|
+
import type { SiteModel } from '@pterodoc/core/model';
|
|
10
|
+
/** What to load, and which parts of it to keep. */
|
|
11
|
+
export interface LoadModelOptions {
|
|
12
|
+
/** Absolute path of the Docusaurus site directory. */
|
|
13
|
+
siteDir: string;
|
|
14
|
+
/** Locale to load; one call loads exactly one locale. */
|
|
15
|
+
locale?: string | undefined;
|
|
16
|
+
/** Explicit `docusaurus.config.*` path. */
|
|
17
|
+
configPath?: string | undefined;
|
|
18
|
+
/** Docs plugin instance ids to keep, or 'all'. */
|
|
19
|
+
instances?: string[] | 'all' | undefined;
|
|
20
|
+
/** Versions to keep: 'last', 'all', or explicit names. */
|
|
21
|
+
versions?: string[] | 'all' | 'last' | undefined;
|
|
22
|
+
/** Publish draft documents. Docusaurus normally drops them from a build. */
|
|
23
|
+
includeDrafts?: boolean | undefined;
|
|
24
|
+
/** Called with warnings raised while loading. */
|
|
25
|
+
warn?: ((message: string) => void) | undefined;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Normalise a loaded Docusaurus site into the model.
|
|
29
|
+
*
|
|
30
|
+
* Exposed separately from {@link loadModel} so the plugin, which already has
|
|
31
|
+
* the loaded state, can reuse it without loading the site again.
|
|
32
|
+
*/
|
|
33
|
+
export declare function toSiteModel(site: LoadedSite, options: LoadModelOptions): SiteModel;
|
|
34
|
+
/**
|
|
35
|
+
* Load a Docusaurus site and normalise it.
|
|
36
|
+
*
|
|
37
|
+
* One call loads one locale, because that is what Docusaurus's own loader
|
|
38
|
+
* does; publishing several locales means calling this once for each.
|
|
39
|
+
*/
|
|
40
|
+
export declare function loadModel(options: LoadModelOptions): Promise<SiteModel>;
|
|
41
|
+
//# sourceMappingURL=model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,EAAwB,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AACjE,OAAO,KAAK,EAA+C,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAKnG,mDAAmD;AACnD,MAAM,WAAW,gBAAgB;IAC/B,sDAAsD;IACtD,OAAO,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,2CAA2C;IAC3C,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,kDAAkD;IAClD,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,KAAK,GAAG,SAAS,CAAC;IACzC,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,MAAM,EAAE,GAAG,KAAK,GAAG,MAAM,GAAG,SAAS,CAAC;IACjD,4EAA4E;IAC5E,aAAa,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACpC,iDAAiD;IACjD,IAAI,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;CAChD;AA4GD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,gBAAgB,GAAG,SAAS,CA8ClF;AAED;;;;;GAKG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,SAAS,CAAC,CA8B7E"}
|
package/lib/reader.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading a model out of a real Docusaurus site.
|
|
3
|
+
*
|
|
4
|
+
* The reader interface and the sources that need no Docusaurus live in
|
|
5
|
+
* `src/model`; this is the one that does.
|
|
6
|
+
*/
|
|
7
|
+
import { type LoadModelOptions } from './model';
|
|
8
|
+
import type { LoadedSite } from './server';
|
|
9
|
+
import { type SourceReader } from '@pterodoc/core/model';
|
|
10
|
+
/** Read from a real Docusaurus site, one locale at a time. */
|
|
11
|
+
export declare function createDocusaurusReader(options: LoadModelOptions): SourceReader;
|
|
12
|
+
/** Build a model from a site the caller has already loaded. */
|
|
13
|
+
export declare function readerFromLoadedSite(site: LoadedSite, options: LoadModelOptions): SourceReader;
|
|
14
|
+
//# sourceMappingURL=reader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reader.d.ts","sourceRoot":"","sources":["../src/reader.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAA0B,KAAK,gBAAgB,EAAE,MAAM,SAAS,CAAC;AACxE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,EAAsB,KAAK,YAAY,EAAkB,MAAM,sBAAsB,CAAC;AAE7F,8DAA8D;AAC9D,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,gBAAgB,GAAG,YAAY,CAkB9E;AAED,+DAA+D;AAC/D,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,gBAAgB,GAAG,YAAY,CAE9F"}
|
package/lib/server.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loading Docusaurus's own site loader.
|
|
3
|
+
*
|
|
4
|
+
* `loadSite` runs every plugin's content lifecycle and hands back the resolved
|
|
5
|
+
* site, which is the whole reason this tool does not re-derive sidebars,
|
|
6
|
+
* versions or permalinks. It lives at a path Docusaurus does not advertise, so
|
|
7
|
+
* the import is guarded and fails with something a reader can act on.
|
|
8
|
+
*/
|
|
9
|
+
/** Docusaurus versions this tool has been checked against. */
|
|
10
|
+
export declare const SUPPORTED_RANGE: {
|
|
11
|
+
min: number;
|
|
12
|
+
maxExclusive: number;
|
|
13
|
+
};
|
|
14
|
+
/** What `loadSite` gives back, narrowed to the parts we read. */
|
|
15
|
+
export interface LoadedSite {
|
|
16
|
+
props: {
|
|
17
|
+
siteDir: string;
|
|
18
|
+
siteConfig: Record<string, unknown>;
|
|
19
|
+
siteConfigPath: string;
|
|
20
|
+
baseUrl: string;
|
|
21
|
+
outDir: string;
|
|
22
|
+
generatedFilesDir: string;
|
|
23
|
+
i18n: {
|
|
24
|
+
defaultLocale: string;
|
|
25
|
+
locales: string[];
|
|
26
|
+
currentLocale: string;
|
|
27
|
+
localeConfigs: Record<string, {
|
|
28
|
+
htmlLang?: string;
|
|
29
|
+
direction?: string;
|
|
30
|
+
}>;
|
|
31
|
+
};
|
|
32
|
+
siteMetadata: {
|
|
33
|
+
docusaurusVersion: string;
|
|
34
|
+
};
|
|
35
|
+
plugins: Array<{
|
|
36
|
+
name: string;
|
|
37
|
+
options: Record<string, unknown>;
|
|
38
|
+
content: unknown;
|
|
39
|
+
}>;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/** Options `loadSite` accepts. */
|
|
43
|
+
export interface LoadSiteParams {
|
|
44
|
+
siteDir: string;
|
|
45
|
+
locale?: string | undefined;
|
|
46
|
+
config?: string | undefined;
|
|
47
|
+
outDir?: string | undefined;
|
|
48
|
+
}
|
|
49
|
+
/** The shape of the Docusaurus module we depend on. */
|
|
50
|
+
export interface DocusaurusServer {
|
|
51
|
+
/** Run the content lifecycle and return the resolved site. */
|
|
52
|
+
loadSite(params: LoadSiteParams): Promise<LoadedSite>;
|
|
53
|
+
/** The Docusaurus version that was loaded. */
|
|
54
|
+
version: string;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Find and load Docusaurus's site loader, resolving it from the site rather
|
|
58
|
+
* than from pterodoc so a hoisted or a nested install both work.
|
|
59
|
+
*
|
|
60
|
+
* @param siteDir Absolute path of the Docusaurus site directory.
|
|
61
|
+
* @param warn Called with a message when the version is outside the tested range.
|
|
62
|
+
*/
|
|
63
|
+
export declare function loadDocusaurusServer(siteDir: string, warn?: (message: string) => void): DocusaurusServer;
|
|
64
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,8DAA8D;AAC9D,eAAO,MAAM,eAAe;;;CAA8B,CAAC;AAK3D,iEAAiE;AACjE,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE;QACL,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACpC,cAAc,EAAE,MAAM,CAAC;QACvB,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;QACf,iBAAiB,EAAE,MAAM,CAAC;QAC1B,IAAI,EAAE;YACJ,aAAa,EAAE,MAAM,CAAC;YACtB,OAAO,EAAE,MAAM,EAAE,CAAC;YAClB,aAAa,EAAE,MAAM,CAAC;YACtB,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE;gBAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;gBAAC,SAAS,CAAC,EAAE,MAAM,CAAA;aAAE,CAAC,CAAC;SAC1E,CAAC;QACF,YAAY,EAAE;YAAE,iBAAiB,EAAE,MAAM,CAAA;SAAE,CAAC;QAC5C,OAAO,EAAE,KAAK,CAAC;YACb,IAAI,EAAE,MAAM,CAAC;YACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;YACjC,OAAO,EAAE,OAAO,CAAC;SAClB,CAAC,CAAC;KACJ,CAAC;CACH;AAED,kCAAkC;AAClC,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B;AAED,uDAAuD;AACvD,MAAM,WAAW,gBAAgB;IAC/B,8DAA8D;IAC9D,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IACtD,8CAA8C;IAC9C,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,IAAI,GAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAe,GACzC,gBAAgB,CAyClB"}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pterodoc/docusaurus",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Reads a pterodoc site model out of Docusaurus, using Docusaurus's own site loader.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./lib/index.js",
|
|
7
|
+
"types": "./lib/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": { "types": "./lib/index.d.ts", "default": "./lib/index.js" },
|
|
10
|
+
"./package.json": "./package.json"
|
|
11
|
+
},
|
|
12
|
+
"files": ["lib", "src", "README.md", "LICENCE.md"],
|
|
13
|
+
"engines": { "node": ">=20.11" },
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc -b --force && rollup -c",
|
|
16
|
+
"test": "node ../../scripts/tests.mjs integration"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@pterodoc/core": "0.2.0"
|
|
20
|
+
},
|
|
21
|
+
"peerDependencies": {
|
|
22
|
+
"@docusaurus/core": ">=3.6.0 <4.0.0"
|
|
23
|
+
},
|
|
24
|
+
"peerDependenciesMeta": {
|
|
25
|
+
"@docusaurus/core": { "optional": true }
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@docusaurus/core": "3.10.2",
|
|
29
|
+
"@docusaurus/preset-classic": "3.10.2",
|
|
30
|
+
"@docusaurus/types": "3.10.2",
|
|
31
|
+
"react": "^19.0.0",
|
|
32
|
+
"react-dom": "^19.0.0"
|
|
33
|
+
},
|
|
34
|
+
"repository": { "type": "git", "url": "git+https://github.com/onyx-ac/pterodoc.git" },
|
|
35
|
+
"license": "CC-BY-SA-4.0",
|
|
36
|
+
"publishConfig": { "access": "public" }
|
|
37
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Everything that knows Docusaurus exists.
|
|
3
|
+
*
|
|
4
|
+
* The model vocabulary it produces belongs to `src/model`; it is re-exported
|
|
5
|
+
* here so a caller that thinks in Docusaurus terms has one import.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export type {
|
|
9
|
+
Doc,
|
|
10
|
+
DocNeighbour,
|
|
11
|
+
DocsInstance,
|
|
12
|
+
DocsVersion,
|
|
13
|
+
SidebarCategoryItem,
|
|
14
|
+
SidebarCategoryLink,
|
|
15
|
+
SidebarDocItem,
|
|
16
|
+
SidebarHtmlItem,
|
|
17
|
+
SidebarItem,
|
|
18
|
+
SidebarLinkItem,
|
|
19
|
+
SiteModel,
|
|
20
|
+
} from '@pterodoc/core/model';
|
|
21
|
+
export { buildPageTree } from '@pterodoc/core/model';
|
|
22
|
+
export type { BuildPageTreeInput, PageKind, PageNode, PageTree } from '@pterodoc/core/model';
|
|
23
|
+
export { createCaptureReader, readCapture, writeCapture, serializeModel } from '@pterodoc/core/model';
|
|
24
|
+
export type { CapturedModel } from '@pterodoc/core/model';
|
|
25
|
+
export { createMemoryReader } from '@pterodoc/core/model';
|
|
26
|
+
export type { SourceReader } from '@pterodoc/core/model';
|
|
27
|
+
export { loadModel, toSiteModel } from './model';
|
|
28
|
+
export type { LoadModelOptions } from './model';
|
|
29
|
+
export { loadDocusaurusServer, SUPPORTED_RANGE } from './server';
|
|
30
|
+
export type { DocusaurusServer, LoadedSite, LoadSiteParams } from './server';
|
|
31
|
+
export { createDocusaurusReader, readerFromLoadedSite } from './reader';
|
package/src/model.ts
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalising Docusaurus's loaded state into the site model.
|
|
3
|
+
*
|
|
4
|
+
* The mapping is deliberately thin: almost every field is copied, because the
|
|
5
|
+
* point of loading Docusaurus is to inherit its answers rather than to
|
|
6
|
+
* second-guess them.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { detectFormat, type MarkdownFormat } from '@pterodoc/core/render';
|
|
11
|
+
import { relativeToPrefix, resolveAliasedPath, toPosix } from '@pterodoc/core/util';
|
|
12
|
+
import { DEFAULT_ADMONITION_KEYWORDS } from '@pterodoc/core/render';
|
|
13
|
+
import { loadDocusaurusServer, type LoadedSite } from './server';
|
|
14
|
+
import type { Doc, DocsInstance, DocsVersion, SidebarItem, SiteModel } from '@pterodoc/core/model';
|
|
15
|
+
|
|
16
|
+
/** Name Docusaurus gives the docs plugin. */
|
|
17
|
+
const DOCS_PLUGIN = 'docusaurus-plugin-content-docs';
|
|
18
|
+
|
|
19
|
+
/** What to load, and which parts of it to keep. */
|
|
20
|
+
export interface LoadModelOptions {
|
|
21
|
+
/** Absolute path of the Docusaurus site directory. */
|
|
22
|
+
siteDir: string;
|
|
23
|
+
/** Locale to load; one call loads exactly one locale. */
|
|
24
|
+
locale?: string | undefined;
|
|
25
|
+
/** Explicit `docusaurus.config.*` path. */
|
|
26
|
+
configPath?: string | undefined;
|
|
27
|
+
/** Docs plugin instance ids to keep, or 'all'. */
|
|
28
|
+
instances?: string[] | 'all' | undefined;
|
|
29
|
+
/** Versions to keep: 'last', 'all', or explicit names. */
|
|
30
|
+
versions?: string[] | 'all' | 'last' | undefined;
|
|
31
|
+
/** Publish draft documents. Docusaurus normally drops them from a build. */
|
|
32
|
+
includeDrafts?: boolean | undefined;
|
|
33
|
+
/** Called with warnings raised while loading. */
|
|
34
|
+
warn?: ((message: string) => void) | undefined;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Read a nested property without pretending to know the whole shape. */
|
|
38
|
+
function get<T>(source: unknown, keys: string[], fallback: T): T {
|
|
39
|
+
let current: unknown = source;
|
|
40
|
+
for (const key of keys) {
|
|
41
|
+
if (current === null || typeof current !== 'object') return fallback;
|
|
42
|
+
current = (current as Record<string, unknown>)[key];
|
|
43
|
+
}
|
|
44
|
+
return (current ?? fallback) as T;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Docusaurus's admonition option is `true`, or an object that may extend the defaults. */
|
|
48
|
+
function admonitionKeywords(options: Record<string, unknown>): string[] {
|
|
49
|
+
const admonitions = options['admonitions'];
|
|
50
|
+
if (admonitions === false) return [];
|
|
51
|
+
if (admonitions === true || admonitions === undefined) return [...DEFAULT_ADMONITION_KEYWORDS];
|
|
52
|
+
|
|
53
|
+
const keywords = get<string[] | undefined>(admonitions, ['keywords'], undefined);
|
|
54
|
+
const extendDefaults = get<boolean>(admonitions, ['extendDefaults'], true);
|
|
55
|
+
if (!keywords) return [...DEFAULT_ADMONITION_KEYWORDS];
|
|
56
|
+
return extendDefaults ? [...new Set([...DEFAULT_ADMONITION_KEYWORDS, ...keywords])] : [...keywords];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Turn one of Docusaurus's loaded documents into ours. */
|
|
60
|
+
function toDoc(
|
|
61
|
+
raw: Record<string, unknown>,
|
|
62
|
+
version: { name: string; pathPrefix: string },
|
|
63
|
+
siteDir: string,
|
|
64
|
+
siteFormat: 'md' | 'mdx' | 'detect',
|
|
65
|
+
): Doc {
|
|
66
|
+
const sourceAliased = String(raw['source'] ?? '');
|
|
67
|
+
const sourceAbsolutePath = resolveAliasedPath(sourceAliased, siteDir);
|
|
68
|
+
const permalink = String(raw['permalink'] ?? '');
|
|
69
|
+
const frontMatter = (raw['frontMatter'] ?? {}) as Record<string, unknown>;
|
|
70
|
+
|
|
71
|
+
const declaredFormat = frontMatter['format'];
|
|
72
|
+
const format: MarkdownFormat =
|
|
73
|
+
declaredFormat === 'md' || declaredFormat === 'mdx'
|
|
74
|
+
? declaredFormat
|
|
75
|
+
: siteFormat === 'detect'
|
|
76
|
+
? detectFormat(sourceAbsolutePath)
|
|
77
|
+
: siteFormat;
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
id: String(raw['id'] ?? ''),
|
|
81
|
+
versionName: version.name,
|
|
82
|
+
title: String(raw['title'] ?? ''),
|
|
83
|
+
description: String(raw['description'] ?? ''),
|
|
84
|
+
sourceAliased,
|
|
85
|
+
sourceAbsolutePath,
|
|
86
|
+
sourceRelativePath: toPosix(path.relative(siteDir, sourceAbsolutePath)),
|
|
87
|
+
sourceDirName: String(raw['sourceDirName'] ?? '.'),
|
|
88
|
+
slug: String(raw['slug'] ?? ''),
|
|
89
|
+
permalink,
|
|
90
|
+
// The position in the published tree is the permalink with the version's
|
|
91
|
+
// own prefix removed, so front-matter slugs are honoured for free.
|
|
92
|
+
treePath: relativeToPrefix(permalink, version.pathPrefix) ?? '',
|
|
93
|
+
draft: raw['draft'] === true,
|
|
94
|
+
unlisted: raw['unlisted'] === true,
|
|
95
|
+
frontMatter,
|
|
96
|
+
sidebarName: (raw['sidebar'] as string | undefined) ?? undefined,
|
|
97
|
+
sidebarPosition: (raw['sidebarPosition'] as number | undefined) ?? undefined,
|
|
98
|
+
previous: (raw['previous'] as Doc['previous']) ?? undefined,
|
|
99
|
+
next: (raw['next'] as Doc['next']) ?? undefined,
|
|
100
|
+
tags: (raw['tags'] as Doc['tags']) ?? [],
|
|
101
|
+
format,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Turn one of Docusaurus's loaded versions into ours. */
|
|
106
|
+
function toVersion(
|
|
107
|
+
raw: Record<string, unknown>,
|
|
108
|
+
siteDir: string,
|
|
109
|
+
siteFormat: 'md' | 'mdx' | 'detect',
|
|
110
|
+
includeDrafts: boolean,
|
|
111
|
+
): DocsVersion {
|
|
112
|
+
const name = String(raw['versionName'] ?? 'current');
|
|
113
|
+
const pathPrefix = String(raw['path'] ?? '');
|
|
114
|
+
const contentPath = String(raw['contentPath'] ?? '');
|
|
115
|
+
|
|
116
|
+
const published = (raw['docs'] as Record<string, unknown>[] | undefined) ?? [];
|
|
117
|
+
const drafts = (raw['drafts'] as Record<string, unknown>[] | undefined) ?? [];
|
|
118
|
+
const source = includeDrafts ? [...published, ...drafts] : published;
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
name,
|
|
122
|
+
label: String(raw['label'] ?? name),
|
|
123
|
+
isLast: raw['isLast'] === true,
|
|
124
|
+
pathPrefix,
|
|
125
|
+
contentPath,
|
|
126
|
+
contentPathLocalized: String(raw['contentPathLocalized'] ?? contentPath),
|
|
127
|
+
banner: (raw['banner'] as DocsVersion['banner']) ?? null,
|
|
128
|
+
noIndex: raw['noIndex'] === true,
|
|
129
|
+
sidebars: (raw['sidebars'] as Record<string, SidebarItem[]> | undefined) ?? {},
|
|
130
|
+
docs: source.map((doc) => toDoc(doc, { name, pathPrefix }, siteDir, siteFormat)),
|
|
131
|
+
draftCount: drafts.length,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Keep only the versions the caller asked for. */
|
|
136
|
+
function selectVersions(versions: DocsVersion[], selector: LoadModelOptions['versions']): DocsVersion[] {
|
|
137
|
+
if (selector === 'all') return versions;
|
|
138
|
+
if (Array.isArray(selector)) return versions.filter((version) => selector.includes(version.name));
|
|
139
|
+
const last = versions.find((version) => version.isLast);
|
|
140
|
+
return last ? [last] : versions.slice(0, 1);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Normalise a loaded Docusaurus site into the model.
|
|
145
|
+
*
|
|
146
|
+
* Exposed separately from {@link loadModel} so the plugin, which already has
|
|
147
|
+
* the loaded state, can reuse it without loading the site again.
|
|
148
|
+
*/
|
|
149
|
+
export function toSiteModel(site: LoadedSite, options: LoadModelOptions): SiteModel {
|
|
150
|
+
const { props } = site;
|
|
151
|
+
const siteDir = props.siteDir;
|
|
152
|
+
const siteConfig = props.siteConfig;
|
|
153
|
+
const siteFormat = get<'md' | 'mdx' | 'detect'>(siteConfig, ['markdown', 'format'], 'mdx');
|
|
154
|
+
const staticDirectories = get<string[]>(siteConfig, ['staticDirectories'], ['static']);
|
|
155
|
+
|
|
156
|
+
const instances: DocsInstance[] = props.plugins
|
|
157
|
+
.filter((plugin) => plugin.name === DOCS_PLUGIN)
|
|
158
|
+
.map((plugin) => {
|
|
159
|
+
const loadedVersions =
|
|
160
|
+
get<Record<string, unknown>[]>(plugin.content, ['loadedVersions'], []) ?? [];
|
|
161
|
+
const versions = loadedVersions.map((version) =>
|
|
162
|
+
toVersion(version, siteDir, siteFormat, options.includeDrafts === true),
|
|
163
|
+
);
|
|
164
|
+
return {
|
|
165
|
+
id: String(plugin.options['id'] ?? 'default'),
|
|
166
|
+
routeBasePath: String(plugin.options['routeBasePath'] ?? 'docs'),
|
|
167
|
+
contentDirName: String(plugin.options['path'] ?? 'docs'),
|
|
168
|
+
admonitionKeywords: admonitionKeywords(plugin.options),
|
|
169
|
+
breadcrumbs: plugin.options['breadcrumbs'] !== false,
|
|
170
|
+
versions: selectVersions(versions, options.versions),
|
|
171
|
+
};
|
|
172
|
+
})
|
|
173
|
+
.filter(
|
|
174
|
+
(instance) =>
|
|
175
|
+
options.instances === undefined ||
|
|
176
|
+
options.instances === 'all' ||
|
|
177
|
+
options.instances.includes(instance.id),
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
siteDir,
|
|
182
|
+
url: String(siteConfig['url'] ?? ''),
|
|
183
|
+
baseUrl: props.baseUrl,
|
|
184
|
+
trailingSlash: siteConfig['trailingSlash'] as boolean | undefined,
|
|
185
|
+
locale: props.i18n.currentLocale,
|
|
186
|
+
defaultLocale: props.i18n.defaultLocale,
|
|
187
|
+
locales: props.i18n.locales,
|
|
188
|
+
markdownFormat: siteFormat,
|
|
189
|
+
maintainCase: get<boolean>(siteConfig, ['markdown', 'anchors', 'maintainCase'], false),
|
|
190
|
+
siteTitle: String(siteConfig['title'] ?? ''),
|
|
191
|
+
staticDirs: staticDirectories.map((dir) => path.resolve(siteDir, dir)),
|
|
192
|
+
docusaurusVersion: props.siteMetadata.docusaurusVersion,
|
|
193
|
+
instances,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Load a Docusaurus site and normalise it.
|
|
199
|
+
*
|
|
200
|
+
* One call loads one locale, because that is what Docusaurus's own loader
|
|
201
|
+
* does; publishing several locales means calling this once for each.
|
|
202
|
+
*/
|
|
203
|
+
export async function loadModel(options: LoadModelOptions): Promise<SiteModel> {
|
|
204
|
+
const warn = options.warn ?? (() => {});
|
|
205
|
+
const server = loadDocusaurusServer(options.siteDir, warn);
|
|
206
|
+
|
|
207
|
+
// Docusaurus's own CLI always runs from the site directory, and plugins rely
|
|
208
|
+
// on it: a typedoc entry point of `../client/src/index.ts`, for instance, is
|
|
209
|
+
// resolved against the working directory rather than the site. Loading from
|
|
210
|
+
// anywhere else makes those plugins look in the wrong place.
|
|
211
|
+
const previousCwd = process.cwd();
|
|
212
|
+
process.chdir(options.siteDir);
|
|
213
|
+
|
|
214
|
+
// Docusaurus only drops drafts and applies production behaviour when
|
|
215
|
+
// NODE_ENV says so, and publishing is a production act. Without this a draft
|
|
216
|
+
// reaches the site, which is precisely what marking it a draft asked to
|
|
217
|
+
// avoid.
|
|
218
|
+
const previousNodeEnv = process.env['NODE_ENV'];
|
|
219
|
+
process.env['NODE_ENV'] = 'production';
|
|
220
|
+
|
|
221
|
+
try {
|
|
222
|
+
const site = await server.loadSite({
|
|
223
|
+
siteDir: options.siteDir,
|
|
224
|
+
locale: options.locale,
|
|
225
|
+
config: options.configPath,
|
|
226
|
+
});
|
|
227
|
+
return toSiteModel(site, options);
|
|
228
|
+
} finally {
|
|
229
|
+
process.chdir(previousCwd);
|
|
230
|
+
if (previousNodeEnv === undefined) delete process.env['NODE_ENV'];
|
|
231
|
+
else process.env['NODE_ENV'] = previousNodeEnv;
|
|
232
|
+
}
|
|
233
|
+
}
|
package/src/reader.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading a model out of a real Docusaurus site.
|
|
3
|
+
*
|
|
4
|
+
* The reader interface and the sources that need no Docusaurus live in
|
|
5
|
+
* `src/model`; this is the one that does.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { loadModel, toSiteModel, type LoadModelOptions } from './model';
|
|
9
|
+
import type { LoadedSite } from './server';
|
|
10
|
+
import { createMemoryReader, type SourceReader, type SiteModel } from '@pterodoc/core/model';
|
|
11
|
+
|
|
12
|
+
/** Read from a real Docusaurus site, one locale at a time. */
|
|
13
|
+
export function createDocusaurusReader(options: LoadModelOptions): SourceReader {
|
|
14
|
+
let firstLocale: SiteModel | undefined;
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
kind: 'docusaurus',
|
|
18
|
+
async locales(): Promise<string[]> {
|
|
19
|
+
firstLocale ??= await loadModel(options);
|
|
20
|
+
return firstLocale.locales;
|
|
21
|
+
},
|
|
22
|
+
async read(locale?: string): Promise<SiteModel> {
|
|
23
|
+
if (firstLocale && (locale === undefined || locale === firstLocale.locale)) {
|
|
24
|
+
return firstLocale;
|
|
25
|
+
}
|
|
26
|
+
const model = await loadModel({ ...options, locale });
|
|
27
|
+
firstLocale ??= model;
|
|
28
|
+
return model;
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Build a model from a site the caller has already loaded. */
|
|
34
|
+
export function readerFromLoadedSite(site: LoadedSite, options: LoadModelOptions): SourceReader {
|
|
35
|
+
return createMemoryReader(toSiteModel(site, options));
|
|
36
|
+
}
|
package/src/server.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loading Docusaurus's own site loader.
|
|
3
|
+
*
|
|
4
|
+
* `loadSite` runs every plugin's content lifecycle and hands back the resolved
|
|
5
|
+
* site, which is the whole reason this tool does not re-derive sidebars,
|
|
6
|
+
* versions or permalinks. It lives at a path Docusaurus does not advertise, so
|
|
7
|
+
* the import is guarded and fails with something a reader can act on.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import { createRequire } from 'node:module';
|
|
12
|
+
import { ConfigError } from '@pterodoc/core/util';
|
|
13
|
+
|
|
14
|
+
/** Docusaurus versions this tool has been checked against. */
|
|
15
|
+
export const SUPPORTED_RANGE = { min: 3, maxExclusive: 4 };
|
|
16
|
+
|
|
17
|
+
/** Paths tried, in order, when looking for the site loader. */
|
|
18
|
+
const CANDIDATES = ['lib/server/site.js', 'server.js', 'lib/server/index.js'];
|
|
19
|
+
|
|
20
|
+
/** What `loadSite` gives back, narrowed to the parts we read. */
|
|
21
|
+
export interface LoadedSite {
|
|
22
|
+
props: {
|
|
23
|
+
siteDir: string;
|
|
24
|
+
siteConfig: Record<string, unknown>;
|
|
25
|
+
siteConfigPath: string;
|
|
26
|
+
baseUrl: string;
|
|
27
|
+
outDir: string;
|
|
28
|
+
generatedFilesDir: string;
|
|
29
|
+
i18n: {
|
|
30
|
+
defaultLocale: string;
|
|
31
|
+
locales: string[];
|
|
32
|
+
currentLocale: string;
|
|
33
|
+
localeConfigs: Record<string, { htmlLang?: string; direction?: string }>;
|
|
34
|
+
};
|
|
35
|
+
siteMetadata: { docusaurusVersion: string };
|
|
36
|
+
plugins: Array<{
|
|
37
|
+
name: string;
|
|
38
|
+
options: Record<string, unknown>;
|
|
39
|
+
content: unknown;
|
|
40
|
+
}>;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Options `loadSite` accepts. */
|
|
45
|
+
export interface LoadSiteParams {
|
|
46
|
+
siteDir: string;
|
|
47
|
+
locale?: string | undefined;
|
|
48
|
+
config?: string | undefined;
|
|
49
|
+
outDir?: string | undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** The shape of the Docusaurus module we depend on. */
|
|
53
|
+
export interface DocusaurusServer {
|
|
54
|
+
/** Run the content lifecycle and return the resolved site. */
|
|
55
|
+
loadSite(params: LoadSiteParams): Promise<LoadedSite>;
|
|
56
|
+
/** The Docusaurus version that was loaded. */
|
|
57
|
+
version: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Find and load Docusaurus's site loader, resolving it from the site rather
|
|
62
|
+
* than from pterodoc so a hoisted or a nested install both work.
|
|
63
|
+
*
|
|
64
|
+
* @param siteDir Absolute path of the Docusaurus site directory.
|
|
65
|
+
* @param warn Called with a message when the version is outside the tested range.
|
|
66
|
+
*/
|
|
67
|
+
export function loadDocusaurusServer(
|
|
68
|
+
siteDir: string,
|
|
69
|
+
warn: (message: string) => void = () => {},
|
|
70
|
+
): DocusaurusServer {
|
|
71
|
+
const requireFromSite = createRequire(path.join(siteDir, 'noop.js'));
|
|
72
|
+
|
|
73
|
+
let corePackagePath: string;
|
|
74
|
+
try {
|
|
75
|
+
corePackagePath = requireFromSite.resolve('@docusaurus/core/package.json');
|
|
76
|
+
} catch {
|
|
77
|
+
throw new ConfigError(
|
|
78
|
+
`Could not find @docusaurus/core from ${siteDir}. pterodoc reads a Docusaurus site using Docusaurus itself, so it has to run inside the site's own project. Use --site-dir to point at it, or --model to render from a captured model instead.`,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const corePackage = requireFromSite(corePackagePath) as { version?: string };
|
|
83
|
+
const version = corePackage.version ?? '0.0.0';
|
|
84
|
+
const major = Number.parseInt(version.split('.')[0] ?? '0', 10);
|
|
85
|
+
if (major < SUPPORTED_RANGE.min || major >= SUPPORTED_RANGE.maxExclusive) {
|
|
86
|
+
warn(
|
|
87
|
+
`This is Docusaurus ${version}; pterodoc has been checked against ${SUPPORTED_RANGE.min}.x. Continuing, but the site model may not load.`,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const coreDir = path.dirname(corePackagePath);
|
|
92
|
+
const tried: string[] = [];
|
|
93
|
+
for (const candidate of CANDIDATES) {
|
|
94
|
+
const candidatePath = path.join(coreDir, candidate);
|
|
95
|
+
tried.push(candidatePath);
|
|
96
|
+
try {
|
|
97
|
+
const loaded = requireFromSite(candidatePath) as { loadSite?: unknown };
|
|
98
|
+
if (typeof loaded.loadSite === 'function') {
|
|
99
|
+
return { loadSite: loaded.loadSite as DocusaurusServer['loadSite'], version };
|
|
100
|
+
}
|
|
101
|
+
} catch {
|
|
102
|
+
// Try the next candidate.
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
throw new ConfigError(
|
|
107
|
+
`Docusaurus ${version} is installed but its site loader was not where pterodoc expected it.\n` +
|
|
108
|
+
`Tried:\n${tried.map((entry) => ` ${entry}`).join('\n')}\n` +
|
|
109
|
+
'This usually means a Docusaurus release moved it. Capture a model on a working version with `pterodoc capture`, then render from it with `--model`, and please report the version.',
|
|
110
|
+
);
|
|
111
|
+
}
|