@doc-kit/core 1.0.1 → 1.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/CHANGELOG.md +18 -0
- package/README.md +2 -1
- package/package.json +11 -6
- package/src/generators/index.mjs +3 -0
- package/src/generators/json/README.md +151 -0
- package/src/generators/json/constants.mjs +57 -0
- package/src/generators/json/generate.mjs +65 -0
- package/src/generators/json/generated/schema.d.ts +351 -0
- package/src/generators/json/index.mjs +27 -0
- package/src/generators/json/schema.json +529 -0
- package/src/generators/json/types.d.ts +29 -0
- package/src/generators/json/utils/document.mjs +58 -0
- package/src/generators/json/utils/entry.mjs +87 -0
- package/src/generators/json/utils/lifecycle.mjs +61 -0
- package/src/generators/json/utils/markdown.mjs +57 -0
- package/src/generators/json/utils/node.mjs +153 -0
- package/src/generators/json/utils/schema.mjs +15 -0
- package/src/generators/json/utils/signature.mjs +185 -0
- package/src/generators/json/utils/types.mjs +28 -0
- package/src/generators/json-all/README.md +31 -0
- package/src/generators/json-all/constants.mjs +5 -0
- package/src/generators/json-all/generate.mjs +35 -0
- package/src/generators/json-all/index.mjs +25 -0
- package/src/generators/json-all/schema.json +22 -0
- package/src/generators/json-all/types.d.ts +20 -0
- package/src/generators/json-simple/generate.mjs +2 -7
- package/src/generators/loader.mjs +8 -3
- package/src/generators/metadata/maps/mdn.json +9 -2
- package/src/generators/metadata/types.d.ts +24 -0
- package/src/generators/pipeline.mjs +169 -0
- package/src/generators/types.d.ts +20 -0
- package/src/generators.mjs +19 -12
- package/src/logger/logger.mjs +8 -0
- package/src/parsers/markdown.mjs +5 -6
- package/src/threading/chunk-worker.mjs +7 -0
- package/src/threading/index.mjs +1 -0
- package/src/utils/configuration/constants.mjs +13 -0
- package/src/utils/configuration/index.mjs +7 -2
- package/src/utils/file.mjs +10 -0
- package/src/utils/hierarchy.mjs +50 -0
- package/src/utils/overloads.mjs +54 -0
- package/src/utils/queries/index.mjs +2 -0
- package/src/utils/queries/utils.mjs +15 -0
- package/src/utils/remark-shiki.mjs +29 -0
- package/src/utils/remark.mjs +27 -49
- package/src/utils/signature/constants.mjs +3 -0
- package/src/utils/signature/extractListItem.mjs +140 -0
- package/src/utils/stability.mjs +24 -0
- package/src/utils/type-annotations/hast.mjs +0 -58
- package/src/utils/type-annotations/highlighted.mjs +64 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://doc-kit.nodejs.org/schemas/api-doc-all/1.0.1.json",
|
|
4
|
+
"title": "Bundle",
|
|
5
|
+
"description": "Every document of a documentation set, as emitted by the doc-kit `json-all` generator",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"$schema": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"description": "The URL of the schema this bundle conforms to. Its last path segment is the schema version."
|
|
11
|
+
},
|
|
12
|
+
"documents": {
|
|
13
|
+
"type": "array",
|
|
14
|
+
"items": {
|
|
15
|
+
"$ref": "https://doc-kit.nodejs.org/schemas/api-doc/1.0.1.json"
|
|
16
|
+
},
|
|
17
|
+
"description": "The documents, in the order of the configured index, then by id."
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"required": ["$schema", "documents"],
|
|
21
|
+
"additionalProperties": false
|
|
22
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Document } from '../json/generated/schema';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Every document of a documentation set
|
|
5
|
+
*/
|
|
6
|
+
export interface Bundle {
|
|
7
|
+
/** The URL of the schema the bundle conforms to */
|
|
8
|
+
$schema: string;
|
|
9
|
+
documents: Array<Document>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface Configuration {
|
|
13
|
+
/** Where the schema is published; `{schemaVersion}` is filled in */
|
|
14
|
+
schemaURL: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type Generator = GeneratorMetadata<
|
|
18
|
+
Configuration,
|
|
19
|
+
Generate<Array<Document>, Promise<Bundle>>
|
|
20
|
+
>;
|
|
@@ -5,7 +5,7 @@ import { join } from 'node:path';
|
|
|
5
5
|
import { remove } from 'unist-util-remove';
|
|
6
6
|
|
|
7
7
|
import getConfig from '#utils/configuration/index.mjs';
|
|
8
|
-
import {
|
|
8
|
+
import { writeJSON } from '#utils/file.mjs';
|
|
9
9
|
import { UNIST } from '#utils/queries/index.mjs';
|
|
10
10
|
|
|
11
11
|
/**
|
|
@@ -21,12 +21,7 @@ export async function generate(input) {
|
|
|
21
21
|
);
|
|
22
22
|
|
|
23
23
|
if (config.output) {
|
|
24
|
-
|
|
25
|
-
// Note: The full JSON generator in the future will create one JSON file per top-level API doc file
|
|
26
|
-
await writeFile(
|
|
27
|
-
join(config.output, 'api-docs.json'),
|
|
28
|
-
config.minify ? JSON.stringify(input) : JSON.stringify(input, null, 2)
|
|
29
|
-
);
|
|
24
|
+
await writeJSON(join(config.output, 'api-docs.json'), input, config.minify);
|
|
30
25
|
}
|
|
31
26
|
|
|
32
27
|
return input;
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
import { isAbsolute } from 'node:path';
|
|
4
4
|
import { pathToFileURL } from 'node:url';
|
|
5
5
|
|
|
6
|
+
import { enforceArray } from '#utils/array.mjs';
|
|
7
|
+
|
|
6
8
|
import { allGenerators } from './index.mjs';
|
|
7
9
|
|
|
8
10
|
/**
|
|
@@ -67,7 +69,7 @@ export const loadGenerator = async specifier => {
|
|
|
67
69
|
|
|
68
70
|
/**
|
|
69
71
|
* Loads the given generators plus the transitive closure of their
|
|
70
|
-
* dependencies (via `dependsOn`).
|
|
72
|
+
* dependencies (via `dependsOn`) and dependents (via `dependent`).
|
|
71
73
|
*
|
|
72
74
|
* @param {string[]} targets - Shorthand names, paths, or import specifiers
|
|
73
75
|
* @returns {Promise<Map<string, GeneratorMetadata>>} Loaded generators keyed
|
|
@@ -87,8 +89,11 @@ export const loadGenerators = async targets => {
|
|
|
87
89
|
const generator = await loadGenerator(specifier);
|
|
88
90
|
generators.set(specifier, generator);
|
|
89
91
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
+
for (const related of [
|
|
93
|
+
...enforceArray(generator.dependsOn ?? []),
|
|
94
|
+
...enforceArray(generator.dependent ?? []),
|
|
95
|
+
]) {
|
|
96
|
+
queue.push(resolveGeneratorSpecifier(related));
|
|
92
97
|
}
|
|
93
98
|
}
|
|
94
99
|
|
|
@@ -421,6 +421,7 @@
|
|
|
421
421
|
"idbkeyrange": "https://developer.mozilla.org/docs/Web/API/IDBKeyRange",
|
|
422
422
|
"idbobjectstore": "https://developer.mozilla.org/docs/Web/API/IDBObjectStore",
|
|
423
423
|
"idbopendbrequest": "https://developer.mozilla.org/docs/Web/API/IDBOpenDBRequest",
|
|
424
|
+
"idbrecord": "https://developer.mozilla.org/docs/Web/API/IDBRecord",
|
|
424
425
|
"idbrequest": "https://developer.mozilla.org/docs/Web/API/IDBRequest",
|
|
425
426
|
"idbtransaction": "https://developer.mozilla.org/docs/Web/API/IDBTransaction",
|
|
426
427
|
"idbversionchangeevent": "https://developer.mozilla.org/docs/Web/API/IDBVersionChangeEvent",
|
|
@@ -442,6 +443,7 @@
|
|
|
442
443
|
"inputdeviceinfo": "https://developer.mozilla.org/docs/Web/API/InputDeviceInfo",
|
|
443
444
|
"inputevent": "https://developer.mozilla.org/docs/Web/API/InputEvent",
|
|
444
445
|
"installevent": "https://developer.mozilla.org/docs/Web/API/InstallEvent",
|
|
446
|
+
"interactioncontentfulpaint": "https://developer.mozilla.org/docs/Web/API/InteractionContentfulPaint",
|
|
445
447
|
"interestevent": "https://developer.mozilla.org/docs/Web/API/InterestEvent",
|
|
446
448
|
"intersectionobserver": "https://developer.mozilla.org/docs/Web/API/IntersectionObserver",
|
|
447
449
|
"intersectionobserverentry": "https://developer.mozilla.org/docs/Web/API/IntersectionObserverEntry",
|
|
@@ -452,6 +454,7 @@
|
|
|
452
454
|
"keyboardlayoutmap": "https://developer.mozilla.org/docs/Web/API/KeyboardLayoutMap",
|
|
453
455
|
"keyframeeffect": "https://developer.mozilla.org/docs/Web/API/KeyframeEffect",
|
|
454
456
|
"languagedetector": "https://developer.mozilla.org/docs/Web/API/LanguageDetector",
|
|
457
|
+
"languagemodel": "https://developer.mozilla.org/docs/Web/API/LanguageModel",
|
|
455
458
|
"largestcontentfulpaint": "https://developer.mozilla.org/docs/Web/API/LargestContentfulPaint",
|
|
456
459
|
"launchparams": "https://developer.mozilla.org/docs/Web/API/LaunchParams",
|
|
457
460
|
"launchqueue": "https://developer.mozilla.org/docs/Web/API/LaunchQueue",
|
|
@@ -589,7 +592,9 @@
|
|
|
589
592
|
"performanceresourcetiming": "https://developer.mozilla.org/docs/Web/API/PerformanceResourceTiming",
|
|
590
593
|
"performancescripttiming": "https://developer.mozilla.org/docs/Web/API/PerformanceScriptTiming",
|
|
591
594
|
"performanceservertiming": "https://developer.mozilla.org/docs/Web/API/PerformanceServerTiming",
|
|
595
|
+
"performancesoftnavigation": "https://developer.mozilla.org/docs/Web/API/PerformanceSoftNavigation",
|
|
592
596
|
"performancetiming": "https://developer.mozilla.org/docs/Web/API/PerformanceTiming",
|
|
597
|
+
"performancetimingconfidence": "https://developer.mozilla.org/docs/Web/API/PerformanceTimingConfidence",
|
|
593
598
|
"periodicsyncevent": "https://developer.mozilla.org/docs/Web/API/PeriodicSyncEvent",
|
|
594
599
|
"periodicsyncmanager": "https://developer.mozilla.org/docs/Web/API/PeriodicSyncManager",
|
|
595
600
|
"periodicwave": "https://developer.mozilla.org/docs/Web/API/PeriodicWave",
|
|
@@ -636,6 +641,7 @@
|
|
|
636
641
|
"rtcerror": "https://developer.mozilla.org/docs/Web/API/RTCError",
|
|
637
642
|
"rtcerrorevent": "https://developer.mozilla.org/docs/Web/API/RTCErrorEvent",
|
|
638
643
|
"rtcicecandidate": "https://developer.mozilla.org/docs/Web/API/RTCIceCandidate",
|
|
644
|
+
"rtcicecandidatepair": "https://developer.mozilla.org/docs/Web/API/RTCIceCandidatePair",
|
|
639
645
|
"rtcicetransport": "https://developer.mozilla.org/docs/Web/API/RTCIceTransport",
|
|
640
646
|
"rtcidentityassertion": "https://developer.mozilla.org/docs/Web/API/RTCIdentityAssertion",
|
|
641
647
|
"rtcpeerconnection": "https://developer.mozilla.org/docs/Web/API/RTCPeerConnection",
|
|
@@ -672,7 +678,6 @@
|
|
|
672
678
|
"restrictiontarget": "https://developer.mozilla.org/docs/Web/API/RestrictionTarget",
|
|
673
679
|
"svgaelement": "https://developer.mozilla.org/docs/Web/API/SVGAElement",
|
|
674
680
|
"svgangle": "https://developer.mozilla.org/docs/Web/API/SVGAngle",
|
|
675
|
-
"svganimatecolorelement": "https://developer.mozilla.org/docs/Web/API/SVGAnimateColorElement",
|
|
676
681
|
"svganimateelement": "https://developer.mozilla.org/docs/Web/API/SVGAnimateElement",
|
|
677
682
|
"svganimatemotionelement": "https://developer.mozilla.org/docs/Web/API/SVGAnimateMotionElement",
|
|
678
683
|
"svganimatetransformelement": "https://developer.mozilla.org/docs/Web/API/SVGAnimateTransformElement",
|
|
@@ -749,7 +754,6 @@
|
|
|
749
754
|
"svgradialgradientelement": "https://developer.mozilla.org/docs/Web/API/SVGRadialGradientElement",
|
|
750
755
|
"svgrect": "https://developer.mozilla.org/docs/Web/API/SVGRect",
|
|
751
756
|
"svgrectelement": "https://developer.mozilla.org/docs/Web/API/SVGRectElement",
|
|
752
|
-
"svgrenderingintent": "https://developer.mozilla.org/docs/Web/API/SVGRenderingIntent",
|
|
753
757
|
"svgsvgelement": "https://developer.mozilla.org/docs/Web/API/SVGSVGElement",
|
|
754
758
|
"svgscriptelement": "https://developer.mozilla.org/docs/Web/API/SVGScriptElement",
|
|
755
759
|
"svgsetelement": "https://developer.mozilla.org/docs/Web/API/SVGSetElement",
|
|
@@ -947,8 +951,10 @@
|
|
|
947
951
|
"webtransport": "https://developer.mozilla.org/docs/Web/API/WebTransport",
|
|
948
952
|
"webtransportbidirectionalstream": "https://developer.mozilla.org/docs/Web/API/WebTransportBidirectionalStream",
|
|
949
953
|
"webtransportdatagramduplexstream": "https://developer.mozilla.org/docs/Web/API/WebTransportDatagramDuplexStream",
|
|
954
|
+
"webtransportdatagramswritable": "https://developer.mozilla.org/docs/Web/API/WebTransportDatagramsWritable",
|
|
950
955
|
"webtransporterror": "https://developer.mozilla.org/docs/Web/API/WebTransportError",
|
|
951
956
|
"webtransportreceivestream": "https://developer.mozilla.org/docs/Web/API/WebTransportReceiveStream",
|
|
957
|
+
"webtransportsendgroup": "https://developer.mozilla.org/docs/Web/API/WebTransportSendGroup",
|
|
952
958
|
"webtransportsendstream": "https://developer.mozilla.org/docs/Web/API/WebTransportSendStream",
|
|
953
959
|
"wheelevent": "https://developer.mozilla.org/docs/Web/API/WheelEvent",
|
|
954
960
|
"window": "https://developer.mozilla.org/docs/Web/API/Window",
|
|
@@ -1014,6 +1020,7 @@
|
|
|
1014
1020
|
"xrview": "https://developer.mozilla.org/docs/Web/API/XRView",
|
|
1015
1021
|
"xrviewerpose": "https://developer.mozilla.org/docs/Web/API/XRViewerPose",
|
|
1016
1022
|
"xrviewport": "https://developer.mozilla.org/docs/Web/API/XRViewport",
|
|
1023
|
+
"xrvisibilitymaskchangeevent": "https://developer.mozilla.org/docs/Web/API/XRVisibilityMaskChangeEvent",
|
|
1017
1024
|
"xrwebglbinding": "https://developer.mozilla.org/docs/Web/API/XRWebGLBinding",
|
|
1018
1025
|
"xrwebgldepthinformation": "https://developer.mozilla.org/docs/Web/API/XRWebGLDepthInformation",
|
|
1019
1026
|
"xrwebgllayer": "https://developer.mozilla.org/docs/Web/API/XRWebGLLayer",
|
|
@@ -97,6 +97,11 @@ export interface HeadingData extends Data {
|
|
|
97
97
|
* from the ToC while still rendering on the page.
|
|
98
98
|
*/
|
|
99
99
|
isOverload?: boolean;
|
|
100
|
+
/**
|
|
101
|
+
* The slug of the first heading of the overloaded function this heading is
|
|
102
|
+
* an overload of. Set alongside `isOverload`.
|
|
103
|
+
*/
|
|
104
|
+
overloadOf?: string;
|
|
100
105
|
}
|
|
101
106
|
|
|
102
107
|
/**
|
|
@@ -120,6 +125,23 @@ export interface StabilityData extends Data {
|
|
|
120
125
|
*/
|
|
121
126
|
export type StabilityNode = NodeWithData<Blockquote, StabilityData>;
|
|
122
127
|
|
|
128
|
+
/**
|
|
129
|
+
* Describes a chunk page: a section of a module rendered as a page of its own
|
|
130
|
+
* (see the `section-pages` generator). Present on every entry of a chunk page.
|
|
131
|
+
*/
|
|
132
|
+
export interface ChunkInfo {
|
|
133
|
+
/** The `api` of the module the chunk was split from */
|
|
134
|
+
api: string;
|
|
135
|
+
/** The `path` of the module the chunk was split from */
|
|
136
|
+
path: string;
|
|
137
|
+
/** The section's anchor on the full module page */
|
|
138
|
+
slug: string;
|
|
139
|
+
/** The chunk's position within the module, in document order */
|
|
140
|
+
index: number;
|
|
141
|
+
/** The original depth of the chunk's heading */
|
|
142
|
+
depth: number;
|
|
143
|
+
}
|
|
144
|
+
|
|
123
145
|
/**
|
|
124
146
|
* Complete metadata entry for a single API documentation element.
|
|
125
147
|
*
|
|
@@ -136,6 +158,8 @@ export interface MetadataEntry extends YAMLProperties {
|
|
|
136
158
|
synthetic?: boolean;
|
|
137
159
|
/** Whether this page was authored as MDX (JSX-in-Markdown). */
|
|
138
160
|
mdx?: boolean;
|
|
161
|
+
/** Set when this entry belongs to a chunk page rather than a full page. */
|
|
162
|
+
chunk?: ChunkInfo;
|
|
139
163
|
/** Processed heading with metadata */
|
|
140
164
|
heading: HeadingNode;
|
|
141
165
|
/** Stability classification information */
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import { enforceArray } from '#utils/array.mjs';
|
|
4
|
+
|
|
5
|
+
import { resolveGeneratorSpecifier } from './loader.mjs';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The resolved specifiers of the generators a generator is delivered through.
|
|
9
|
+
*
|
|
10
|
+
* @param {GeneratorMetadata | undefined} generator
|
|
11
|
+
* @returns {string[]}
|
|
12
|
+
*/
|
|
13
|
+
const dependentsOf = generator =>
|
|
14
|
+
enforceArray(generator?.dependent ?? []).map(resolveGeneratorSpecifier);
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Collects the generators reached from the given ones through `dependsOn`.
|
|
18
|
+
*
|
|
19
|
+
* @param {string[]} specifiers - Resolved generator specifiers
|
|
20
|
+
* @param {Map<string, GeneratorMetadata>} generators - Loaded generators
|
|
21
|
+
* @returns {Set<string>}
|
|
22
|
+
*/
|
|
23
|
+
const dependencyClosure = (specifiers, generators) => {
|
|
24
|
+
const closure = new Set();
|
|
25
|
+
const stack = [...specifiers];
|
|
26
|
+
|
|
27
|
+
while (stack.length > 0) {
|
|
28
|
+
const specifier = stack.pop();
|
|
29
|
+
|
|
30
|
+
if (closure.has(specifier)) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
closure.add(specifier);
|
|
35
|
+
|
|
36
|
+
const { dependsOn } = generators.get(specifier) ?? {};
|
|
37
|
+
|
|
38
|
+
if (dependsOn) {
|
|
39
|
+
stack.push(resolveGeneratorSpecifier(dependsOn));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return closure;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Splices a generator into a dependent's pipeline: walking from the dependent
|
|
48
|
+
* up its dependency chain, the first generator that consumes the splicing
|
|
49
|
+
* generator's own input is rewired to consume the splicing generator instead.
|
|
50
|
+
*
|
|
51
|
+
* @param {string} specifier - The generator to splice in
|
|
52
|
+
* @param {string} dependent - The pipeline to splice it into
|
|
53
|
+
* @param {Map<string, string | undefined>} inputOf - Effective inputs, mutated
|
|
54
|
+
* @param {string} name - The splicing generator's name, for errors
|
|
55
|
+
*/
|
|
56
|
+
const splice = (specifier, dependent, inputOf, name) => {
|
|
57
|
+
const upstream = inputOf.get(specifier);
|
|
58
|
+
const visited = new Set();
|
|
59
|
+
|
|
60
|
+
let current = dependent;
|
|
61
|
+
|
|
62
|
+
while (current !== undefined && current !== specifier) {
|
|
63
|
+
if (visited.has(current)) {
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
visited.add(current);
|
|
68
|
+
|
|
69
|
+
if (inputOf.get(current) === upstream) {
|
|
70
|
+
inputOf.set(current, specifier);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
current = inputOf.get(current);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
throw new Error(
|
|
78
|
+
`Generator "${name}" declares "${dependent}" as its dependent, but ` +
|
|
79
|
+
`nothing in that pipeline consumes "${upstream ?? 'no input'}", so ` +
|
|
80
|
+
'there is nowhere to splice it in.'
|
|
81
|
+
);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Follows a generator's `dependent` links to the generators that finally
|
|
86
|
+
* deliver its output. When some of its dependents are already part of the
|
|
87
|
+
* run, only those deliver it; otherwise all of them do.
|
|
88
|
+
*
|
|
89
|
+
* @param {string} specifier - Resolved generator specifier
|
|
90
|
+
* @param {Map<string, GeneratorMetadata>} generators - Loaded generators
|
|
91
|
+
* @param {Set<string>} requested - Generators already part of the run
|
|
92
|
+
* @param {Set<string>} [seen] - Guards against dependent cycles
|
|
93
|
+
* @returns {string[]} Resolved specifiers of the terminal generators
|
|
94
|
+
*/
|
|
95
|
+
const resolveTerminals = (
|
|
96
|
+
specifier,
|
|
97
|
+
generators,
|
|
98
|
+
requested,
|
|
99
|
+
seen = new Set()
|
|
100
|
+
) => {
|
|
101
|
+
if (seen.has(specifier)) {
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
seen.add(specifier);
|
|
106
|
+
|
|
107
|
+
const dependents = dependentsOf(generators.get(specifier));
|
|
108
|
+
|
|
109
|
+
if (dependents.length === 0) {
|
|
110
|
+
return [specifier];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const active = dependents.filter(dependent => requested.has(dependent));
|
|
114
|
+
|
|
115
|
+
return (active.length > 0 ? active : dependents).flatMap(dependent =>
|
|
116
|
+
resolveTerminals(dependent, generators, requested, seen)
|
|
117
|
+
);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Resolves the effective pipeline for a run: which generator each generator
|
|
122
|
+
* reads its input from, and which generators the run finally collects.
|
|
123
|
+
*
|
|
124
|
+
* By default a generator reads from its `dependsOn`. A generator that declares
|
|
125
|
+
* one or more `dependent`s is spliced into each dependent's pipeline (see
|
|
126
|
+
* {@link splice}). Several generators may splice at the same point; each later
|
|
127
|
+
* one is inserted upstream of the earlier ones, forming a chain.
|
|
128
|
+
*
|
|
129
|
+
* A requested generator that declares dependents is delivered through them,
|
|
130
|
+
* so the run collects their output rather than the generator's own: through
|
|
131
|
+
* the dependents that are already part of the run when there are any, and
|
|
132
|
+
* through all of them otherwise.
|
|
133
|
+
*
|
|
134
|
+
* @param {string[]} targets - Requested targets, as resolved specifiers
|
|
135
|
+
* @param {Map<string, GeneratorMetadata>} generators - Loaded generators,
|
|
136
|
+
* including every dependency and dependent of the targets
|
|
137
|
+
* @returns {{ targets: string[], inputOf: Map<string, string | undefined> }}
|
|
138
|
+
* The generators to collect, and each generator's effective input generator
|
|
139
|
+
*/
|
|
140
|
+
export const resolvePipeline = (targets, generators) => {
|
|
141
|
+
/** @type {Map<string, string | undefined>} */
|
|
142
|
+
const inputOf = new Map();
|
|
143
|
+
|
|
144
|
+
for (const [specifier, { dependsOn }] of generators) {
|
|
145
|
+
inputOf.set(
|
|
146
|
+
specifier,
|
|
147
|
+
dependsOn ? resolveGeneratorSpecifier(dependsOn) : undefined
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
for (const [specifier, generator] of generators) {
|
|
152
|
+
for (const dependent of dependentsOf(generator)) {
|
|
153
|
+
splice(specifier, dependent, inputOf, generator.name);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const requested = dependencyClosure(targets, generators);
|
|
158
|
+
const collected = [];
|
|
159
|
+
|
|
160
|
+
for (const target of targets) {
|
|
161
|
+
for (const terminal of resolveTerminals(target, generators, requested)) {
|
|
162
|
+
if (!collected.includes(terminal)) {
|
|
163
|
+
collected.push(terminal);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return { targets: collected, inputOf };
|
|
169
|
+
};
|
|
@@ -97,6 +97,26 @@ declare global {
|
|
|
97
97
|
*/
|
|
98
98
|
dependsOn: string | undefined;
|
|
99
99
|
|
|
100
|
+
/**
|
|
101
|
+
* The generator(s) this generator's output is delivered through, declared
|
|
102
|
+
* as import specifiers. This is the inverse of `dependsOn`: rather than
|
|
103
|
+
* pulling a dependency in, it pushes this generator into another
|
|
104
|
+
* generator's pipeline.
|
|
105
|
+
*
|
|
106
|
+
* The orchestrator splices this generator in front of the first generator
|
|
107
|
+
* in each dependent's dependency chain that consumes the same `dependsOn`
|
|
108
|
+
* as this one. For example, a generator with
|
|
109
|
+
* `dependsOn: '@doc-kit/core/metadata'` and
|
|
110
|
+
* `dependent: '@doc-kit/generator-react/html'` is inserted between
|
|
111
|
+
* `metadata` and `jsx-ast` (the consumer of `metadata` on the way to
|
|
112
|
+
* `html`), so its output is what `jsx-ast` — and therefore `html` — sees.
|
|
113
|
+
*
|
|
114
|
+
* Requesting a generator that declares dependents runs them as well, and
|
|
115
|
+
* the run's result is their output: when some of the dependents are
|
|
116
|
+
* already part of the run, only those; otherwise all of them.
|
|
117
|
+
*/
|
|
118
|
+
dependent?: string | string[];
|
|
119
|
+
|
|
100
120
|
/**
|
|
101
121
|
* Generators are abstract and the different generators have different sort of inputs and outputs.
|
|
102
122
|
* For example, a MDX generator would take the raw AST and output MDX with React Components;
|
package/src/generators.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
loadGenerators,
|
|
6
6
|
resolveGeneratorSpecifier,
|
|
7
7
|
} from './generators/loader.mjs';
|
|
8
|
+
import { resolvePipeline } from './generators/pipeline.mjs';
|
|
8
9
|
import logger from './logger/index.mjs';
|
|
9
10
|
import createWorkerPool from './threading/index.mjs';
|
|
10
11
|
import createParallelWorker from './threading/parallel.mjs';
|
|
@@ -44,9 +45,12 @@ const createGenerator = () => {
|
|
|
44
45
|
*
|
|
45
46
|
* @param {string} specifier - Resolved generator specifier to schedule
|
|
46
47
|
* @param {Map<string, GeneratorMetadata>} generators - Loaded generators
|
|
48
|
+
* @param {Map<string, string | undefined>} inputOf - Each generator's
|
|
49
|
+
* effective input generator (its dependency, unless another generator was
|
|
50
|
+
* spliced in front of it via `dependent`)
|
|
47
51
|
* @param {import('./utils/configuration/types').Configuration} configuration - Runtime options
|
|
48
52
|
*/
|
|
49
|
-
const scheduleGenerator = (specifier, generators, configuration) => {
|
|
53
|
+
const scheduleGenerator = (specifier, generators, inputOf, configuration) => {
|
|
50
54
|
if (cache.has(specifier)) {
|
|
51
55
|
return;
|
|
52
56
|
}
|
|
@@ -54,12 +58,11 @@ const createGenerator = () => {
|
|
|
54
58
|
const generator = generators.get(specifier);
|
|
55
59
|
const { name, generate, hasParallelProcessor } = generator;
|
|
56
60
|
|
|
57
|
-
const dependsOn =
|
|
58
|
-
generator.dependsOn && resolveGeneratorSpecifier(generator.dependsOn);
|
|
61
|
+
const dependsOn = inputOf.get(specifier);
|
|
59
62
|
|
|
60
63
|
// Schedule dependency first
|
|
61
64
|
if (dependsOn && !cache.has(dependsOn)) {
|
|
62
|
-
scheduleGenerator(dependsOn, generators, configuration);
|
|
65
|
+
scheduleGenerator(dependsOn, generators, inputOf, configuration);
|
|
63
66
|
}
|
|
64
67
|
|
|
65
68
|
generatorsLogger.debug(`Scheduling "${name}"`, {
|
|
@@ -104,8 +107,16 @@ const createGenerator = () => {
|
|
|
104
107
|
|
|
105
108
|
// Resolve shorthand names and load the full dependency closure up front,
|
|
106
109
|
// so scheduling below is fully synchronous.
|
|
107
|
-
const
|
|
108
|
-
|
|
110
|
+
const generators = await loadGenerators(
|
|
111
|
+
target.map(resolveGeneratorSpecifier)
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
// Work out who reads from whom once generators declaring a `dependent`
|
|
115
|
+
// have been spliced in, and which generators the run finally collects.
|
|
116
|
+
const { targets, inputOf } = resolvePipeline(
|
|
117
|
+
target.map(resolveGeneratorSpecifier),
|
|
118
|
+
generators
|
|
119
|
+
);
|
|
109
120
|
|
|
110
121
|
generatorsLogger.debug(`Starting pipeline`, {
|
|
111
122
|
generators: targets.join(', '),
|
|
@@ -114,18 +125,14 @@ const createGenerator = () => {
|
|
|
114
125
|
|
|
115
126
|
// Compute consumer counts up front so dependencies can be evicted as soon
|
|
116
127
|
// as their last consumer runs (must be ready before any generator starts).
|
|
117
|
-
cache.populateConsumerCounts(targets, specifier =>
|
|
118
|
-
const { dependsOn } = generators.get(specifier);
|
|
119
|
-
|
|
120
|
-
return dependsOn && resolveGeneratorSpecifier(dependsOn);
|
|
121
|
-
});
|
|
128
|
+
cache.populateConsumerCounts(targets, specifier => inputOf.get(specifier));
|
|
122
129
|
|
|
123
130
|
// Create worker pool
|
|
124
131
|
pool = createWorkerPool(threads);
|
|
125
132
|
|
|
126
133
|
// Schedule all generators
|
|
127
134
|
for (const specifier of targets) {
|
|
128
|
-
scheduleGenerator(specifier, generators, configuration);
|
|
135
|
+
scheduleGenerator(specifier, generators, inputOf, configuration);
|
|
129
136
|
}
|
|
130
137
|
|
|
131
138
|
// Start all collections in parallel (don't await sequentially). Consuming
|
package/src/logger/logger.mjs
CHANGED
|
@@ -165,6 +165,13 @@ export const createLogger = (
|
|
|
165
165
|
}
|
|
166
166
|
};
|
|
167
167
|
|
|
168
|
+
/**
|
|
169
|
+
* Gets the current log level for this logger instance.
|
|
170
|
+
*
|
|
171
|
+
* @returns {number} The current numeric log level
|
|
172
|
+
*/
|
|
173
|
+
const getLogLevel = () => currentLevel;
|
|
174
|
+
|
|
168
175
|
return {
|
|
169
176
|
info,
|
|
170
177
|
warn,
|
|
@@ -173,5 +180,6 @@ export const createLogger = (
|
|
|
173
180
|
debug,
|
|
174
181
|
child,
|
|
175
182
|
setLogLevel,
|
|
183
|
+
getLogLevel,
|
|
176
184
|
};
|
|
177
185
|
};
|
package/src/parsers/markdown.mjs
CHANGED
|
@@ -22,9 +22,7 @@ const NODE_LTS_VERSION_REGEX = /Long Term Support/i;
|
|
|
22
22
|
export const parseChangelog = async path => {
|
|
23
23
|
const changelog = await loadFromURL(path);
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
return nodeMajors.map(match => ({
|
|
25
|
+
return Array.from(changelog.matchAll(NODE_VERSIONS_REGEX), match => ({
|
|
28
26
|
version: coerce(match[1]),
|
|
29
27
|
isLts: NODE_LTS_VERSION_REGEX.test(match[2]),
|
|
30
28
|
}));
|
|
@@ -43,7 +41,8 @@ export const parseIndex = async path => {
|
|
|
43
41
|
|
|
44
42
|
const index = await loadFromURL(path);
|
|
45
43
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
44
|
+
return Array.from(index.matchAll(LIST_ITEM_REGEX), ([, section, api]) => ({
|
|
45
|
+
section,
|
|
46
|
+
api,
|
|
47
|
+
}));
|
|
49
48
|
};
|
|
@@ -1,6 +1,13 @@
|
|
|
1
|
+
import { workerData } from 'node:worker_threads';
|
|
2
|
+
|
|
1
3
|
import { loadGenerator } from '#generators/loader.mjs';
|
|
4
|
+
import logger from '#logger/index.mjs';
|
|
2
5
|
import { setConfig } from '#utils/configuration/index.mjs';
|
|
3
6
|
|
|
7
|
+
if (workerData?.logLevel !== undefined) {
|
|
8
|
+
logger.setLogLevel(workerData.logLevel);
|
|
9
|
+
}
|
|
10
|
+
|
|
4
11
|
/**
|
|
5
12
|
* Processes a chunk of items using the specified generator's processChunk method.
|
|
6
13
|
* This is the worker entry point for Piscina.
|
package/src/threading/index.mjs
CHANGED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The default `threads` ceiling. Each worker that highlights code holds Shiki's
|
|
5
|
+
* grammars and regex engine (~300MB) on top of the pages it is building, so
|
|
6
|
+
* past a few threads memory, not CPU, is what runs out. `--threads` raises it.
|
|
7
|
+
*/
|
|
8
|
+
export const DEFAULT_MAX_THREADS = 4;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The default number of items each worker task processes.
|
|
12
|
+
*/
|
|
13
|
+
export const DEFAULT_CHUNK_SIZE = 10;
|
|
@@ -18,6 +18,8 @@ import { enforceArray } from '#utils/array.mjs';
|
|
|
18
18
|
import { leftHandAssign } from '#utils/generators.mjs';
|
|
19
19
|
import { deepMerge } from '#utils/misc.mjs';
|
|
20
20
|
|
|
21
|
+
import { DEFAULT_CHUNK_SIZE, DEFAULT_MAX_THREADS } from './constants.mjs';
|
|
22
|
+
|
|
21
23
|
const configExplorer = cosmiconfig('doc-kit');
|
|
22
24
|
|
|
23
25
|
/**
|
|
@@ -68,8 +70,11 @@ export const getDefaultConfig = (generators, config) =>
|
|
|
68
70
|
// riscv64 with sv39. Running multiple generators that use wasm in
|
|
69
71
|
// parallel could cause failures to allocate new wasm instance.
|
|
70
72
|
// See also https://github.com/nodejs/node/pull/60591
|
|
71
|
-
threads:
|
|
72
|
-
|
|
73
|
+
threads:
|
|
74
|
+
process.arch === 'riscv64'
|
|
75
|
+
? 1
|
|
76
|
+
: Math.min(cpus().length, DEFAULT_MAX_THREADS),
|
|
77
|
+
chunkSize: DEFAULT_CHUNK_SIZE,
|
|
73
78
|
})
|
|
74
79
|
);
|
|
75
80
|
|
package/src/utils/file.mjs
CHANGED
|
@@ -18,3 +18,13 @@ export const writeFile = (file, ...args) =>
|
|
|
18
18
|
fs
|
|
19
19
|
.mkdir(dirname(file), { recursive: true })
|
|
20
20
|
.then(() => fs.writeFile(file, ...args));
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Writes a value as JSON
|
|
24
|
+
*
|
|
25
|
+
* @param {string} file
|
|
26
|
+
* @param {unknown} value
|
|
27
|
+
* @param {boolean} [minify]
|
|
28
|
+
*/
|
|
29
|
+
export const writeJSON = (file, value, minify = false) =>
|
|
30
|
+
writeFile(file, JSON.stringify(value, null, minify ? 0 : 2));
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A node in the entry hierarchy.
|
|
5
|
+
*
|
|
6
|
+
* @typedef {object} HierarchizedEntry
|
|
7
|
+
* @property {import('../generators/metadata/types').MetadataEntry} entry The metadata entry this node wraps
|
|
8
|
+
* @property {Array<HierarchizedEntry>} children Entries nested under this one, by heading depth
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Finds the closest preceding node whose heading is shallower than the
|
|
13
|
+
* entry's, which is the entry's parent.
|
|
14
|
+
*
|
|
15
|
+
* @param {import('../generators/metadata/types').MetadataEntry} entry The entry to find a parent for
|
|
16
|
+
* @param {Array<HierarchizedEntry>} nodes Wrapper nodes, index-aligned with the entries
|
|
17
|
+
* @param {number} startIdx The index to search backwards from
|
|
18
|
+
* @returns {HierarchizedEntry | undefined} The parent, or `undefined` when no shallower entry precedes it
|
|
19
|
+
*/
|
|
20
|
+
export const findParent = (entry, nodes, startIdx) => {
|
|
21
|
+
for (let i = startIdx; i >= 0; i--) {
|
|
22
|
+
if (nodes[i].entry.heading.depth < entry.heading.depth) {
|
|
23
|
+
return nodes[i];
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return undefined;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param {Array<import('../generators/metadata/types').MetadataEntry>} entries Entries in document order
|
|
32
|
+
* @returns {Array<HierarchizedEntry>} The root nodes
|
|
33
|
+
*/
|
|
34
|
+
export const buildHierarchy = entries => {
|
|
35
|
+
const roots = [];
|
|
36
|
+
|
|
37
|
+
// Wrapper nodes, index-aligned with `entries`
|
|
38
|
+
const nodes = entries.map(entry => ({ entry, children: [] }));
|
|
39
|
+
|
|
40
|
+
nodes.forEach((node, i) => {
|
|
41
|
+
const parent =
|
|
42
|
+
node.entry.heading.depth <= 1
|
|
43
|
+
? undefined
|
|
44
|
+
: findParent(node.entry, nodes, i - 1);
|
|
45
|
+
|
|
46
|
+
(parent?.children ?? roots).push(node);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
return roots;
|
|
50
|
+
};
|