@pterodoc/core 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.
Files changed (130) hide show
  1. package/LICENCE.md +10 -0
  2. package/README.md +12 -0
  3. package/lib/chunks/capture-BhdvHwrN.js +428 -0
  4. package/lib/chunks/capture-BhdvHwrN.js.map +1 -0
  5. package/lib/chunks/issues-DBsPh6ek.js +55 -0
  6. package/lib/chunks/issues-DBsPh6ek.js.map +1 -0
  7. package/lib/chunks/mime-2SUyOolJ.js +62 -0
  8. package/lib/chunks/mime-2SUyOolJ.js.map +1 -0
  9. package/lib/chunks/paths-Bh6UsRI9.js +177 -0
  10. package/lib/chunks/paths-Bh6UsRI9.js.map +1 -0
  11. package/lib/config/load.d.ts +123 -0
  12. package/lib/config/load.d.ts.map +1 -0
  13. package/lib/config/types.d.ts +136 -0
  14. package/lib/config/types.d.ts.map +1 -0
  15. package/lib/errors.d.ts +53 -0
  16. package/lib/errors.d.ts.map +1 -0
  17. package/lib/index.d.ts +23 -0
  18. package/lib/index.d.ts.map +1 -0
  19. package/lib/index.js +807 -0
  20. package/lib/index.js.map +1 -0
  21. package/lib/model/capture.d.ts +37 -0
  22. package/lib/model/capture.d.ts.map +1 -0
  23. package/lib/model/index.d.ts +13 -0
  24. package/lib/model/index.d.ts.map +1 -0
  25. package/lib/model/index.js +7 -0
  26. package/lib/model/index.js.map +1 -0
  27. package/lib/model/reader.d.ts +25 -0
  28. package/lib/model/reader.d.ts.map +1 -0
  29. package/lib/model/tree.d.ts +81 -0
  30. package/lib/model/tree.d.ts.map +1 -0
  31. package/lib/model/types.d.ts +166 -0
  32. package/lib/model/types.d.ts.map +1 -0
  33. package/lib/render/admonitions.d.ts +26 -0
  34. package/lib/render/admonitions.d.ts.map +1 -0
  35. package/lib/render/blocks.d.ts +39 -0
  36. package/lib/render/blocks.d.ts.map +1 -0
  37. package/lib/render/code.d.ts +37 -0
  38. package/lib/render/code.d.ts.map +1 -0
  39. package/lib/render/components.d.ts +49 -0
  40. package/lib/render/components.d.ts.map +1 -0
  41. package/lib/render/excerpt.d.ts +9 -0
  42. package/lib/render/excerpt.d.ts.map +1 -0
  43. package/lib/render/images.d.ts +58 -0
  44. package/lib/render/images.d.ts.map +1 -0
  45. package/lib/render/index.d.ts +80 -0
  46. package/lib/render/index.d.ts.map +1 -0
  47. package/lib/render/index.js +1336 -0
  48. package/lib/render/index.js.map +1 -0
  49. package/lib/render/inline.d.ts +25 -0
  50. package/lib/render/inline.d.ts.map +1 -0
  51. package/lib/render/links.d.ts +45 -0
  52. package/lib/render/links.d.ts.map +1 -0
  53. package/lib/render/mdx.d.ts +37 -0
  54. package/lib/render/mdx.d.ts.map +1 -0
  55. package/lib/render/page.d.ts +85 -0
  56. package/lib/render/page.d.ts.map +1 -0
  57. package/lib/render/parse.d.ts +27 -0
  58. package/lib/render/parse.d.ts.map +1 -0
  59. package/lib/render/references.d.ts +20 -0
  60. package/lib/render/references.d.ts.map +1 -0
  61. package/lib/render/renderers.d.ts +57 -0
  62. package/lib/render/renderers.d.ts.map +1 -0
  63. package/lib/render/slug.d.ts +34 -0
  64. package/lib/render/slug.d.ts.map +1 -0
  65. package/lib/render/theme.d.ts +62 -0
  66. package/lib/render/theme.d.ts.map +1 -0
  67. package/lib/sync/artifacts.d.ts +46 -0
  68. package/lib/sync/artifacts.d.ts.map +1 -0
  69. package/lib/sync/plan.d.ts +59 -0
  70. package/lib/sync/plan.d.ts.map +1 -0
  71. package/lib/sync/run.d.ts +39 -0
  72. package/lib/sync/run.d.ts.map +1 -0
  73. package/lib/target/index.d.ts +8 -0
  74. package/lib/target/index.d.ts.map +1 -0
  75. package/lib/target/index.js +2 -0
  76. package/lib/target/index.js.map +1 -0
  77. package/lib/target/target.d.ts +152 -0
  78. package/lib/target/target.d.ts.map +1 -0
  79. package/lib/util/hash.d.ts +10 -0
  80. package/lib/util/hash.d.ts.map +1 -0
  81. package/lib/util/index.d.ts +14 -0
  82. package/lib/util/index.d.ts.map +1 -0
  83. package/lib/util/index.js +7 -0
  84. package/lib/util/index.js.map +1 -0
  85. package/lib/util/issues.d.ts +52 -0
  86. package/lib/util/issues.d.ts.map +1 -0
  87. package/lib/util/mime.d.ts +16 -0
  88. package/lib/util/mime.d.ts.map +1 -0
  89. package/lib/util/paths.d.ts +42 -0
  90. package/lib/util/paths.d.ts.map +1 -0
  91. package/lib/version.d.ts +6 -0
  92. package/lib/version.d.ts.map +1 -0
  93. package/package.json +43 -0
  94. package/src/config/load.ts +387 -0
  95. package/src/config/types.ts +137 -0
  96. package/src/errors.ts +77 -0
  97. package/src/globals.d.ts +10 -0
  98. package/src/index.ts +31 -0
  99. package/src/model/capture.ts +92 -0
  100. package/src/model/index.ts +25 -0
  101. package/src/model/reader.ts +37 -0
  102. package/src/model/tree.ts +416 -0
  103. package/src/model/types.ts +167 -0
  104. package/src/render/admonitions.ts +74 -0
  105. package/src/render/blocks.ts +79 -0
  106. package/src/render/code.ts +112 -0
  107. package/src/render/components.ts +230 -0
  108. package/src/render/excerpt.ts +16 -0
  109. package/src/render/images.ts +113 -0
  110. package/src/render/index.ts +129 -0
  111. package/src/render/inline.ts +62 -0
  112. package/src/render/links.ts +94 -0
  113. package/src/render/mdx.ts +178 -0
  114. package/src/render/page.ts +289 -0
  115. package/src/render/parse.ts +65 -0
  116. package/src/render/references.ts +69 -0
  117. package/src/render/renderers.ts +346 -0
  118. package/src/render/slug.ts +63 -0
  119. package/src/render/theme.ts +92 -0
  120. package/src/sync/artifacts.ts +70 -0
  121. package/src/sync/plan.ts +69 -0
  122. package/src/sync/run.ts +641 -0
  123. package/src/target/index.ts +18 -0
  124. package/src/target/target.ts +146 -0
  125. package/src/util/hash.ts +14 -0
  126. package/src/util/index.ts +23 -0
  127. package/src/util/issues.ts +86 -0
  128. package/src/util/mime.ts +47 -0
  129. package/src/util/paths.ts +101 -0
  130. package/src/version.ts +31 -0
@@ -0,0 +1,289 @@
1
+ /**
2
+ * Page composition: the navigation and layout wrapped around a rendered body.
3
+ *
4
+ * This module deliberately describes the page tree structurally rather than
5
+ * importing it, so the renderer stays independent of where the model came from
6
+ * and of where the page is going.
7
+ */
8
+
9
+ import { escapeText, joinBlocks, serializeBlock, serializeVoidBlock } from './blocks';
10
+ import { excerptFrom } from './excerpt';
11
+ import type { Theme } from './theme';
12
+
13
+ /** The parts of a page node this module reads. */
14
+ export interface PageLike {
15
+ /** Path within the published tree; '' is the root. */
16
+ path: string;
17
+ /** Title shown on the page and in navigation. */
18
+ title: string;
19
+ /** Short description, when the document supplied one. */
20
+ description?: string | undefined;
21
+ /** Parent page, for the breadcrumb. */
22
+ parent?: PageLike | undefined;
23
+ /** Children, in order. */
24
+ children: PageLike[];
25
+ /** Children grouped by the section that named them. */
26
+ sections: { label?: string | undefined; children: PageLike[] }[];
27
+ /** Path of the previous page in reading order. */
28
+ previousPath?: string | undefined;
29
+ /** Path of the next page. */
30
+ nextPath?: string | undefined;
31
+ }
32
+
33
+ /** How a page is laid out on the target. */
34
+ export interface PageLayout {
35
+ /** `two-column` puts navigation beside the document; `single` omits it. */
36
+ kind: 'two-column' | 'single';
37
+ /** Width of the navigation column. */
38
+ navWidth: string;
39
+ /** Width of the document column. */
40
+ mainWidth: string;
41
+ /** Alignment of the columns block: '', 'wide' or 'full'. */
42
+ align: '' | 'wide' | 'full';
43
+ /** `page-list` renders the target's own page tree; `none` omits navigation. */
44
+ nav: 'page-list' | 'none';
45
+ /** Show a trail of links back to the root. */
46
+ breadcrumb: boolean;
47
+ /** Show previous and next links. */
48
+ pagination: boolean;
49
+ /** When to list child pages: automatically, always, or never. */
50
+ childIndex: 'auto' | 'always' | 'never';
51
+ }
52
+
53
+ /** The layout used when a site configures none. */
54
+ export const DEFAULT_LAYOUT: PageLayout = {
55
+ kind: 'two-column',
56
+ navWidth: '25%',
57
+ mainWidth: '75%',
58
+ align: '',
59
+ nav: 'page-list',
60
+ breadcrumb: true,
61
+ pagination: true,
62
+ childIndex: 'auto',
63
+ };
64
+
65
+ /** Everything needed to compose one page. */
66
+ export interface ComposePageInput {
67
+ /** The page being composed. */
68
+ node: PageLike;
69
+ /** The rendered document body; empty for a page with no document. */
70
+ body: string;
71
+ /** Tree paths this page's body already links to. */
72
+ links: Set<string>;
73
+ /** Absolute target URL for a tree path. */
74
+ href: (treePath: string) => string;
75
+ /** Look a page up by tree path, for previous and next. */
76
+ lookup: (treePath: string) => PageLike | undefined;
77
+ /** Class names and strings. */
78
+ theme: Theme;
79
+ /** How the page is laid out. */
80
+ layout: PageLayout;
81
+ /** Id of the page the navigation block should list from. */
82
+ navRootId?: number | null | undefined;
83
+ /** A notice shown above the body, such as an old-version banner. */
84
+ banner?: string | undefined;
85
+ }
86
+
87
+ /** A short summary of a page, for an index listing. */
88
+ function summaryOf(node: PageLike, theme: Theme): string {
89
+ if (node.description) return excerptFrom(node.description, 140);
90
+ const count = node.children.length;
91
+ if (count === 1) return theme.text('pageCountOne');
92
+ if (count > 1) return theme.text('pageCount', { count });
93
+ return '';
94
+ }
95
+
96
+ /** The trail of links back to the root. */
97
+ export function renderBreadcrumb(input: ComposePageInput): string {
98
+ const trail: PageLike[] = [];
99
+ for (let current = input.node.parent; current; current = current.parent) trail.unshift(current);
100
+ if (trail.length === 0) return '';
101
+
102
+ const links = trail.map(
103
+ (ancestor) => `<a href="${input.href(ancestor.path)}">${escapeText(ancestor.title)}</a>`,
104
+ );
105
+ links.push(escapeText(input.node.title));
106
+
107
+ const className = input.theme.cls('docs-breadcrumb');
108
+
109
+ // Marked up, the plugin can swap the separator for a configured one exactly
110
+ // rather than by guessing which run of text between two links is one. Left
111
+ // bare, it is the same characters it has always been.
112
+ const separator = escapeText(input.theme.strings.breadcrumbSeparator);
113
+ const joined =
114
+ input.theme.blocks === 'plugin'
115
+ ? links.join(`<span class="${input.theme.cls('breadcrumb-separator')}">${separator}</span>`)
116
+ : links.join(separator);
117
+
118
+ return serializeBlock(
119
+ 'group',
120
+ { className },
121
+ `<div class="wp-block-group ${className}">${serializeBlock(
122
+ 'paragraph',
123
+ undefined,
124
+ `<p>${joined}</p>`,
125
+ )}</div>`,
126
+ );
127
+ }
128
+
129
+ /** Previous and next links, following the site's own order. */
130
+ export function renderPagination(input: ComposePageInput): string {
131
+ const previous = input.node.previousPath === undefined ? undefined : input.lookup(input.node.previousPath);
132
+ const next = input.node.nextPath === undefined ? undefined : input.lookup(input.node.nextPath);
133
+ if (!previous && !next) return '';
134
+
135
+ const parts: string[] = [];
136
+ if (previous) {
137
+ const className = input.theme.cls('docs-pagination-prev');
138
+ parts.push(
139
+ serializeBlock(
140
+ 'paragraph',
141
+ { className },
142
+ `<p class="${className}"><a href="${input.href(previous.path)}">${escapeText(
143
+ input.theme.text('previous', { title: previous.title }),
144
+ )}</a></p>`,
145
+ ),
146
+ );
147
+ }
148
+ if (next) {
149
+ const className = input.theme.cls('docs-pagination-next');
150
+ parts.push(
151
+ serializeBlock(
152
+ 'paragraph',
153
+ { className },
154
+ `<p class="${className}"><a href="${input.href(next.path)}">${escapeText(
155
+ input.theme.text('next', { title: next.title }),
156
+ )}</a></p>`,
157
+ ),
158
+ );
159
+ }
160
+
161
+ const className = input.theme.cls('docs-pagination');
162
+ return serializeBlock(
163
+ 'group',
164
+ { className },
165
+ `<div class="wp-block-group ${className}">${joinBlocks(parts)}</div>`,
166
+ );
167
+ }
168
+
169
+ /** A list of the pages below this one, grouped as the sidebar grouped them. */
170
+ export function renderChildIndex(input: ComposePageInput, heading: string): string {
171
+ const { node, theme } = input;
172
+ if (node.children.length === 0) return '';
173
+
174
+ const parts: string[] = [];
175
+ if (heading) {
176
+ const className = theme.cls('docs-index-heading');
177
+ parts.push(
178
+ serializeBlock(
179
+ 'heading',
180
+ { className },
181
+ `<h2 class="wp-block-heading ${className}" id="in-this-section">${escapeText(heading)}</h2>`,
182
+ ),
183
+ );
184
+ }
185
+
186
+ for (const section of node.sections.length > 0 ? node.sections : [{ label: undefined, children: node.children }]) {
187
+ if (section.label) {
188
+ parts.push(
189
+ serializeBlock('heading', { level: 3 }, `<h3 class="wp-block-heading">${escapeText(section.label)}</h3>`),
190
+ );
191
+ }
192
+ const items = section.children
193
+ .map((child) => {
194
+ const summary = summaryOf(child, theme);
195
+ const link = `<a href="${input.href(child.path)}">${escapeText(child.title)}</a>`;
196
+ return serializeBlock(
197
+ 'list-item',
198
+ undefined,
199
+ `<li>${link}${summary ? ` — ${escapeText(summary)}` : ''}</li>`,
200
+ );
201
+ })
202
+ .join('\n\n');
203
+
204
+ const className = theme.cls('docs-index');
205
+ parts.push(
206
+ serializeBlock(
207
+ 'list',
208
+ { className },
209
+ `<ul class="wp-block-list ${className}">${items}</ul>`,
210
+ ),
211
+ );
212
+ }
213
+
214
+ return joinBlocks(parts);
215
+ }
216
+
217
+ /** The navigation column's contents. */
218
+ function renderNavigation(input: ComposePageInput): string {
219
+ if (input.layout.nav === 'none') return '';
220
+ return serializeVoidBlock('page-list', { parentPageID: input.navRootId ?? 0 });
221
+ }
222
+
223
+ /** Compose the stored content of one page. */
224
+ export function composePage(input: ComposePageInput): string {
225
+ const { node, theme, layout } = input;
226
+
227
+ let index = '';
228
+ if (node.children.length > 0 && layout.childIndex !== 'never') {
229
+ // A page that already links all of its children does not need a generated
230
+ // list; one that links none of them does.
231
+ const linksAllChildren = node.children.every((child) => input.links.has(child.path));
232
+ if (layout.childIndex === 'always' || !linksAllChildren) {
233
+ index = renderChildIndex(input, input.body ? theme.text('indexHeading') : '');
234
+ }
235
+ }
236
+
237
+ const main = joinBlocks([
238
+ input.banner ?? '',
239
+ layout.breadcrumb ? renderBreadcrumb(input) : '',
240
+ input.body,
241
+ index,
242
+ layout.pagination ? renderPagination(input) : '',
243
+ ]);
244
+
245
+ if (layout.kind === 'single' || layout.nav === 'none') return main;
246
+
247
+ const navClass = theme.cls('docs-nav');
248
+ const mainClass = theme.cls('docs-main');
249
+ const navColumn = serializeBlock(
250
+ 'column',
251
+ { width: layout.navWidth, className: navClass },
252
+ `<div class="wp-block-column ${navClass}" style="flex-basis:${layout.navWidth}">${renderNavigation(input)}</div>`,
253
+ );
254
+ const mainColumn = serializeBlock(
255
+ 'column',
256
+ { width: layout.mainWidth, className: mainClass },
257
+ `<div class="wp-block-column ${mainClass}" style="flex-basis:${layout.mainWidth}">${main}</div>`,
258
+ );
259
+
260
+ const columnsClass = theme.cls('docs');
261
+ const attributes: Record<string, unknown> = { className: columnsClass };
262
+ if (layout.align) attributes['align'] = layout.align;
263
+ const alignClass = layout.align ? ` align${layout.align}` : '';
264
+
265
+ return serializeBlock(
266
+ 'columns',
267
+ attributes,
268
+ `<div class="wp-block-columns${alignClass} ${columnsClass}">${navColumn}\n\n${mainColumn}</div>`,
269
+ );
270
+ }
271
+
272
+ /** Body for a path segment that exists only so the documentation has a parent. */
273
+ export function renderNavigationStub(selfId: number): string {
274
+ return serializeVoidBlock('page-list', { parentPageID: selfId });
275
+ }
276
+
277
+ /** A notice shown on every page of a version that is not the current one. */
278
+ export function renderVersionBanner(theme: Theme, label: string, kind: string): string {
279
+ const className = `${theme.cls('version-banner')} ${theme.cls(`version-banner-${kind}`)}`;
280
+ return serializeBlock(
281
+ 'group',
282
+ { className },
283
+ `<div class="wp-block-group ${className}">${serializeBlock(
284
+ 'paragraph',
285
+ undefined,
286
+ `<p>${escapeText(theme.text('versionBanner', { label }))}</p>`,
287
+ )}</div>`,
288
+ );
289
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Markdown and MDX parsing.
3
+ *
4
+ * Two processors, built once and reused: Docusaurus decides per document
5
+ * whether a file is plain markdown or MDX, and the model carries that decision
6
+ * so it is never guessed here.
7
+ */
8
+
9
+ import { unified, type Processor } from 'unified';
10
+ import remarkParse from 'remark-parse';
11
+ import remarkGfm from 'remark-gfm';
12
+ import remarkDirective from 'remark-directive';
13
+ import remarkFrontmatter from 'remark-frontmatter';
14
+ import remarkMdx from 'remark-mdx';
15
+ import type { Root } from 'mdast';
16
+
17
+ /** Which flavour a document is written in. */
18
+ export type MarkdownFormat = 'md' | 'mdx';
19
+
20
+ /** Extensions Docusaurus treats as plain markdown when `format: 'detect'`. */
21
+ const PLAIN_EXTENSIONS = new Set([
22
+ '.md',
23
+ '.markdown',
24
+ '.mdown',
25
+ '.mkdn',
26
+ '.mkd',
27
+ '.mdwn',
28
+ '.mkdown',
29
+ '.ron',
30
+ ]);
31
+
32
+ /**
33
+ * Resolve `format: 'detect'` the way Docusaurus does: by extension.
34
+ *
35
+ * @param filePath Path of the source file.
36
+ */
37
+ export function detectFormat(filePath: string): MarkdownFormat {
38
+ const dot = filePath.lastIndexOf('.');
39
+ const extension = dot === -1 ? '' : filePath.slice(dot).toLowerCase();
40
+ return PLAIN_EXTENSIONS.has(extension) ? 'md' : 'mdx';
41
+ }
42
+
43
+ const base = (): Processor<Root> =>
44
+ unified()
45
+ .use(remarkParse)
46
+ .use(remarkFrontmatter, ['yaml'])
47
+ .use(remarkGfm)
48
+ .use(remarkDirective) as unknown as Processor<Root>;
49
+
50
+ const markdownProcessor = base();
51
+ const mdxProcessor = (base() as never as { use: (p: unknown) => Processor<Root> }).use(remarkMdx);
52
+
53
+ /**
54
+ * Parse a document body.
55
+ *
56
+ * Only the parser runs: no transformers, no compiler. The tree is what the
57
+ * rest of the renderer works on.
58
+ *
59
+ * @param markdown The body, with front matter already removed.
60
+ * @param format Which flavour to parse as.
61
+ */
62
+ export function parseMarkdown(markdown: string, format: MarkdownFormat = 'md'): Root {
63
+ const processor = format === 'mdx' ? mdxProcessor : markdownProcessor;
64
+ return processor.parse(markdown) as Root;
65
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Reference-style links and images.
3
+ *
4
+ * `[text][ref]` with a `[ref]: ./target.md` definition elsewhere only works if
5
+ * something joins the two. Doing it here, as a pre-pass over the whole
6
+ * document, means the rest of the renderer never sees a reference node and the
7
+ * definitions never reach the page as visible text.
8
+ */
9
+
10
+ import { visit } from 'unist-util-visit';
11
+ import type { Definition, Image, Link, Root, RootContent } from 'mdast';
12
+
13
+ /**
14
+ * Turn resolvable references into ordinary links and images, and remove the
15
+ * definitions that backed them.
16
+ *
17
+ * An unresolvable reference is left alone: CommonMark says it renders as the
18
+ * literal text that was typed, which is what the HTML conversion then does.
19
+ *
20
+ * @param root The document, modified in place.
21
+ */
22
+ export function resolveReferences(root: Root): void {
23
+ const definitions = new Map<string, Definition>();
24
+ visit(root, 'definition', (node: Definition) => {
25
+ if (!definitions.has(node.identifier)) definitions.set(node.identifier, node);
26
+ });
27
+
28
+ visit(root, (node, index, parent) => {
29
+ if (!parent || index === undefined) return;
30
+ if (node.type !== 'linkReference' && node.type !== 'imageReference') return;
31
+
32
+ // A reference with no definition never reaches here: CommonMark says it is
33
+ // not a link at all, so the parser has already turned it into plain text.
34
+ const definition = definitions.get(node.identifier);
35
+ if (!definition) return;
36
+
37
+ const replacement: Link | Image =
38
+ node.type === 'linkReference'
39
+ ? {
40
+ type: 'link',
41
+ url: definition.url,
42
+ ...(definition.title != null ? { title: definition.title } : {}),
43
+ children: node.children,
44
+ ...(node.position ? { position: node.position } : {}),
45
+ }
46
+ : {
47
+ type: 'image',
48
+ url: definition.url,
49
+ ...(definition.title != null ? { title: definition.title } : {}),
50
+ alt: node.alt ?? '',
51
+ ...(node.position ? { position: node.position } : {}),
52
+ };
53
+
54
+ (parent.children as RootContent[])[index] = replacement;
55
+ });
56
+
57
+ // Definitions carry no visible content; leaving them in would print them.
58
+ removeDefinitions(root);
59
+ }
60
+
61
+ /** Drop every definition node, at any depth. */
62
+ function removeDefinitions(root: Root): void {
63
+ const prune = (node: { children?: RootContent[] }): void => {
64
+ if (!Array.isArray(node.children)) return;
65
+ node.children = node.children.filter((child) => child.type !== 'definition');
66
+ for (const child of node.children) prune(child as { children?: RootContent[] });
67
+ };
68
+ prune(root as unknown as { children?: RootContent[] });
69
+ }