@doc-kit/core 1.2.0 → 1.3.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 +16 -0
- package/README.md +1 -1
- package/package.json +6 -7
- package/src/generators/ast/generate.mjs +7 -3
- package/src/generators/ast/index.mjs +9 -0
- package/src/generators/index.mjs +1 -0
- package/src/generators/json/README.md +1 -1
- package/src/generators/json/generated/schema.d.ts +1 -1
- package/src/generators/json/index.mjs +9 -0
- package/src/generators/json/utils/markdown.mjs +2 -2
- package/src/generators/json-all/README.md +1 -1
- package/src/generators/json-simple/README.md +1 -1
- package/src/generators/loader.mjs +14 -0
- package/src/generators/metadata/generate.mjs +4 -2
- package/src/generators/metadata/index.mjs +9 -0
- package/src/generators/metadata/maps/mdn.json +3 -0
- package/src/generators/metadata/types.d.ts +1 -1
- package/src/generators/metadata/utils/parse.mjs +2 -4
- package/src/generators/metadata/utils/transformers.mjs +1 -1
- package/src/generators/metadata/utils/visitors.mjs +2 -2
- package/src/generators/types.d.ts +8 -0
- package/src/generators.mjs +4 -0
- package/src/logger/constants.mjs +35 -0
- package/src/logger/transports/github.mjs +48 -15
- package/src/plugins/shiki/highlighter.mjs +373 -0
- package/src/plugins/shiki/rehype.mjs +38 -0
- package/src/{utils/type-annotations/highlighted.mjs → plugins/type-annotations/highlighter.mjs} +14 -19
- package/src/{utils → plugins}/type-annotations/remark.mjs +6 -2
- package/src/threading/chunk-worker.mjs +4 -0
- package/src/threading/constants.mjs +10 -0
- package/src/threading/index.mjs +6 -1
- package/src/utils/configuration/constants.mjs +3 -3
- package/src/utils/configuration/index.mjs +86 -20
- package/src/utils/configuration/types.d.ts +27 -0
- package/src/utils/loaders.mjs +23 -1
- package/src/utils/markdown/constants.mjs +7 -0
- package/src/utils/markdown/plugins.mjs +232 -0
- package/src/utils/markdown/processor.mjs +60 -0
- package/src/utils/highlighter.mjs +0 -198
- package/src/utils/remark-shiki.mjs +0 -29
- package/src/utils/remark.mjs +0 -90
- /package/src/{utils → plugins}/type-annotations/hast.mjs +0 -0
- /package/src/{utils → plugins}/type-annotations/mdast.mjs +0 -0
- /package/src/{utils → plugins}/type-annotations/syntax.mjs +0 -0
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
|
-
import { createRequire } from 'node:module';
|
|
3
2
|
import { cpus } from 'node:os';
|
|
4
|
-
import {
|
|
5
|
-
import { pathToFileURL } from 'node:url';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
6
4
|
import { isMainThread } from 'node:worker_threads';
|
|
7
5
|
|
|
8
6
|
import { cosmiconfig } from 'cosmiconfig';
|
|
@@ -16,6 +14,12 @@ import logger from '#logger/index.mjs';
|
|
|
16
14
|
import { parseChangelog, parseIndex } from '#parsers/markdown.mjs';
|
|
17
15
|
import { enforceArray } from '#utils/array.mjs';
|
|
18
16
|
import { leftHandAssign } from '#utils/generators.mjs';
|
|
17
|
+
import { resolveSpecifier } from '#utils/loaders.mjs';
|
|
18
|
+
import {
|
|
19
|
+
CONFIGURED_PLUGINS,
|
|
20
|
+
PLUGIN_LISTS,
|
|
21
|
+
} from '#utils/markdown/constants.mjs';
|
|
22
|
+
import { resolveMarkdown } from '#utils/markdown/plugins.mjs';
|
|
19
23
|
import { deepMerge } from '#utils/misc.mjs';
|
|
20
24
|
|
|
21
25
|
import { DEFAULT_CHUNK_SIZE, DEFAULT_MAX_THREADS } from './constants.mjs';
|
|
@@ -58,12 +62,13 @@ export const getDefaultConfig = (generators, config) =>
|
|
|
58
62
|
global: {
|
|
59
63
|
project: detectProject() ?? 'API Docs',
|
|
60
64
|
version: process.version,
|
|
61
|
-
minify:
|
|
65
|
+
minify: false,
|
|
62
66
|
ref: 'HEAD',
|
|
63
67
|
// Without release history there is nothing to build a version picker
|
|
64
68
|
// from, so generators render single-version output.
|
|
65
69
|
changelog: [],
|
|
66
70
|
pathsToCopy: ['assets', 'public', 'static'],
|
|
71
|
+
markdown: { remarkPlugins: [], rehypePlugins: [], recmaPlugins: [] },
|
|
67
72
|
},
|
|
68
73
|
|
|
69
74
|
// The number of wasm memory instances is severely limited on
|
|
@@ -79,21 +84,26 @@ export const getDefaultConfig = (generators, config) =>
|
|
|
79
84
|
);
|
|
80
85
|
|
|
81
86
|
/**
|
|
82
|
-
* Resolves
|
|
83
|
-
*
|
|
84
|
-
* resolves as a package import specifier (e.g. `@node-core/doc-kit/config`).
|
|
87
|
+
* Resolves the Markdown plugins of a configuration (in `global`, and each
|
|
88
|
+
* generator's section) from the file declaring them.
|
|
85
89
|
*
|
|
86
|
-
* @param {
|
|
87
|
-
* @param {string}
|
|
88
|
-
* @returns {
|
|
90
|
+
* @param {Partial<import('./types').Configuration>} config - The configuration
|
|
91
|
+
* @param {string} filePath - The file declaring it
|
|
92
|
+
* @returns {Partial<import('./types').Configuration>}
|
|
89
93
|
*/
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
+
const resolveMarkdownPlugins = (config, filePath) =>
|
|
95
|
+
Object.fromEntries(
|
|
96
|
+
Object.entries(config).map(([name, value]) => {
|
|
97
|
+
if (value?.markdown) {
|
|
98
|
+
const label = `${name}.markdown`;
|
|
99
|
+
const markdown = resolveMarkdown(value.markdown, label, filePath);
|
|
94
100
|
|
|
95
|
-
|
|
96
|
-
}
|
|
101
|
+
return [name, { ...value, markdown }];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return [name, value];
|
|
105
|
+
})
|
|
106
|
+
);
|
|
97
107
|
|
|
98
108
|
/**
|
|
99
109
|
* Loads an explicit configuration file or searches for one using cosmiconfig.
|
|
@@ -112,15 +122,63 @@ export const loadConfigFile = async filePath => {
|
|
|
112
122
|
|
|
113
123
|
let { extends: presets, ...config } = result.config ?? {};
|
|
114
124
|
|
|
125
|
+
config = resolveMarkdownPlugins(config, result.filepath);
|
|
126
|
+
|
|
115
127
|
for (const preset of enforceArray(presets ?? []).toReversed()) {
|
|
116
|
-
const
|
|
128
|
+
const url = resolveSpecifier(preset, result.filepath);
|
|
129
|
+
const module = await import(url);
|
|
117
130
|
|
|
118
|
-
|
|
131
|
+
// A preset's Markdown plugins resolve from the preset
|
|
132
|
+
config = deepMerge(
|
|
133
|
+
resolveMarkdownPlugins(module.default ?? module, fileURLToPath(url)),
|
|
134
|
+
config
|
|
135
|
+
);
|
|
119
136
|
}
|
|
120
137
|
|
|
121
138
|
return config;
|
|
122
139
|
};
|
|
123
140
|
|
|
141
|
+
/**
|
|
142
|
+
* Returns the Markdown plugins a generator takes: for each list of its
|
|
143
|
+
* pipeline with a `'...'`, the global ones, then its own. Generators rendering
|
|
144
|
+
* Markdown (with rehype or recma plugins) skip the global remark plugins,
|
|
145
|
+
* which already ran in `ast`. Its own plugins it doesn't take are ignored with
|
|
146
|
+
* a warning.
|
|
147
|
+
*
|
|
148
|
+
* @param {GeneratorMetadata} generator - The generator
|
|
149
|
+
* @param {Partial<import('./types').MarkdownConfiguration>} [markdown] - Its own plugins
|
|
150
|
+
* @param {import('./types').GlobalConfiguration} global - The global configuration
|
|
151
|
+
* @returns {Partial<import('./types').MarkdownConfiguration> | undefined}
|
|
152
|
+
*/
|
|
153
|
+
const configureMarkdown = (generator, markdown = {}, global) => {
|
|
154
|
+
const pipeline = generator.markdown ?? {};
|
|
155
|
+
const renders = Boolean(pipeline.rehypePlugins || pipeline.recmaPlugins);
|
|
156
|
+
const configured = {};
|
|
157
|
+
|
|
158
|
+
for (const list of PLUGIN_LISTS) {
|
|
159
|
+
if (!pipeline[list]?.includes(CONFIGURED_PLUGINS)) {
|
|
160
|
+
if (markdown[list]?.length > 0) {
|
|
161
|
+
logger.warn(
|
|
162
|
+
`Ignoring \`${generator.name}.markdown.${list}\`: ` +
|
|
163
|
+
`\`${generator.name}\` does not take them.`
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const plugins = markdown[list] ?? [];
|
|
171
|
+
|
|
172
|
+
if (list === 'remarkPlugins' && renders) {
|
|
173
|
+
configured[list] = plugins;
|
|
174
|
+
} else {
|
|
175
|
+
configured[list] = [...global.markdown[list], ...plugins];
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return generator.markdown && configured;
|
|
180
|
+
};
|
|
181
|
+
|
|
124
182
|
/**
|
|
125
183
|
* Transforms configuration values that need async processing or coercion.
|
|
126
184
|
* Only processes values that haven't been transformed yet (strings for changelog/index, non-coerced versions).
|
|
@@ -232,12 +290,20 @@ export const createRunConfiguration = async options => {
|
|
|
232
290
|
|
|
233
291
|
// Now assign to each generator config (they inherit from global)
|
|
234
292
|
await Promise.all(
|
|
235
|
-
[...generators.values()].map(async
|
|
236
|
-
const value = merged[name];
|
|
293
|
+
[...generators.values()].map(async generator => {
|
|
294
|
+
const value = merged[generator.name];
|
|
237
295
|
|
|
238
296
|
// Transform generator-specific overrides
|
|
239
297
|
await transformConfig(value);
|
|
240
298
|
|
|
299
|
+
// A generator's own Markdown plugins add to the global ones. Set even
|
|
300
|
+
// when undefined, so the assignment below doesn't copy the global ones
|
|
301
|
+
value.markdown = configureMarkdown(
|
|
302
|
+
generator,
|
|
303
|
+
value.markdown,
|
|
304
|
+
merged.global
|
|
305
|
+
);
|
|
306
|
+
|
|
241
307
|
// Assign from global (this populates missing values from global)
|
|
242
308
|
leftHandAssign(value, merged.global);
|
|
243
309
|
})
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SemVer } from 'semver';
|
|
2
|
+
|
|
2
3
|
import type { ReleaseEntry } from '#parsers/types';
|
|
3
4
|
|
|
4
5
|
export type Configuration = {
|
|
@@ -61,4 +62,30 @@ export type GlobalConfiguration = {
|
|
|
61
62
|
|
|
62
63
|
// Git ref (i.e. HEAD)
|
|
63
64
|
ref: string;
|
|
65
|
+
|
|
66
|
+
// Markdown plugins added to the pipeline of each generator processing
|
|
67
|
+
// Markdown. A generator's own `markdown` adds to them, rather than
|
|
68
|
+
// replacing them.
|
|
69
|
+
markdown: MarkdownConfiguration;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// A plugin's module specifier (a package name, or a path relative to the file
|
|
73
|
+
// declaring it), alone or with its options
|
|
74
|
+
export type PluginEntry = string | [specifier: string, options?: unknown];
|
|
75
|
+
|
|
76
|
+
export type MarkdownConfiguration = {
|
|
77
|
+
// remark plugins, run on Markdown syntax trees
|
|
78
|
+
remarkPlugins: Array<PluginEntry>;
|
|
79
|
+
|
|
80
|
+
// rehype plugins, run on HTML syntax trees
|
|
81
|
+
rehypePlugins: Array<PluginEntry>;
|
|
82
|
+
|
|
83
|
+
// recma plugins, run on JavaScript syntax trees
|
|
84
|
+
recmaPlugins: Array<PluginEntry>;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// A generator's Markdown pipeline, where each list takes the configured
|
|
88
|
+
// plugins in place of its `'...'`
|
|
89
|
+
export type MarkdownPipeline = {
|
|
90
|
+
[List in keyof MarkdownConfiguration]?: Array<PluginEntry | '...'>;
|
|
64
91
|
};
|
package/src/utils/loaders.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
|
-
import {
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { dirname, extname, isAbsolute, join, resolve } from 'node:path';
|
|
3
4
|
import { pathToFileURL } from 'node:url';
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -28,6 +29,27 @@ export const loadFromURL = async url => {
|
|
|
28
29
|
}
|
|
29
30
|
};
|
|
30
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Resolves an import specifier into a `file:` URL: relative paths resolve
|
|
34
|
+
* against the file it appears in, anything else as a package import
|
|
35
|
+
* specifier (e.g. `@node-core/doc-kit/config`).
|
|
36
|
+
*
|
|
37
|
+
* @param {string} specifier - A path, a package specifier, or a `file:` URL
|
|
38
|
+
* @param {string} filePath - The file it appears in
|
|
39
|
+
* @returns {string} A `file:` URL to import
|
|
40
|
+
*/
|
|
41
|
+
export const resolveSpecifier = (specifier, filePath) => {
|
|
42
|
+
if (specifier.startsWith('file:')) {
|
|
43
|
+
return specifier;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (specifier.startsWith('.') || isAbsolute(specifier)) {
|
|
47
|
+
return pathToFileURL(resolve(dirname(filePath), specifier)).href;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return pathToFileURL(createRequire(filePath).resolve(specifier)).href;
|
|
51
|
+
};
|
|
52
|
+
|
|
31
53
|
/**
|
|
32
54
|
* Dynamically imports a module from a URL, using JSON import assertion if applicable.
|
|
33
55
|
*
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// The plugin lists of a Markdown pipeline, one for each syntax tree
|
|
4
|
+
export const PLUGIN_LISTS = ['remarkPlugins', 'rehypePlugins', 'recmaPlugins'];
|
|
5
|
+
|
|
6
|
+
// Marks where a list of a generator's pipeline takes the configured plugins
|
|
7
|
+
export const CONFIGURED_PLUGINS = '...';
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
import { getGeneratorModule } from '#generators/loader.mjs';
|
|
6
|
+
import { enforceArray } from '#utils/array.mjs';
|
|
7
|
+
import { resolveSpecifier } from '#utils/loaders.mjs';
|
|
8
|
+
import { isPlainObject } from '#utils/misc.mjs';
|
|
9
|
+
|
|
10
|
+
import { CONFIGURED_PLUGINS, PLUGIN_LISTS } from './constants.mjs';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The plugins of a Markdown pipeline, as `processor.use()` takes them.
|
|
14
|
+
*
|
|
15
|
+
* @typedef {Record<'remarkPlugins' | 'rehypePlugins' | 'recmaPlugins', import('unified').PluggableList>} MarkdownPlugins
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
// The pipeline each generator runs on this thread, by its name
|
|
19
|
+
const loadedPipelines = new Map();
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Resolves the module specifiers of Markdown plugins (in the `markdown`
|
|
23
|
+
* option, or a generator's pipeline) from the file declaring them into
|
|
24
|
+
* `file:` URLs, which any thread can import.
|
|
25
|
+
*
|
|
26
|
+
* @param {import('../configuration/types').MarkdownPipeline} markdown - The plugins
|
|
27
|
+
* @param {string} label - Where they are declared, for errors (e.g. `global.markdown`)
|
|
28
|
+
* @param {string} filePath - The file declaring them
|
|
29
|
+
* @returns {import('../configuration/types').MarkdownPipeline}
|
|
30
|
+
*/
|
|
31
|
+
export const resolveMarkdown = (markdown, label, filePath) => {
|
|
32
|
+
const resolved = { ...markdown };
|
|
33
|
+
|
|
34
|
+
for (const list of PLUGIN_LISTS) {
|
|
35
|
+
resolved[list] &&= enforceArray(markdown[list]).map((entry, index) => {
|
|
36
|
+
if (entry === CONFIGURED_PLUGINS) {
|
|
37
|
+
return entry;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const [specifier, ...options] = enforceArray(entry);
|
|
41
|
+
|
|
42
|
+
if (typeof specifier !== 'string') {
|
|
43
|
+
throw new TypeError(
|
|
44
|
+
`${label}.${list}[${index}] in ${filePath} must be a module ` +
|
|
45
|
+
'specifier (a package name or a path), as Markdown is processed ' +
|
|
46
|
+
'in worker threads, which import the plugins themselves.'
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const url = resolveSpecifier(specifier, filePath);
|
|
51
|
+
|
|
52
|
+
return options.length === 0 ? url : [url, ...options];
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return resolved;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Resolves the module specifiers of a generator's Markdown pipeline from the
|
|
61
|
+
* module it was loaded from.
|
|
62
|
+
*
|
|
63
|
+
* @param {GeneratorMetadata} generator - The generator
|
|
64
|
+
* @returns {import('../configuration/types').MarkdownPipeline}
|
|
65
|
+
*/
|
|
66
|
+
const resolveMarkdownPipeline = generator => {
|
|
67
|
+
const module = getGeneratorModule(generator);
|
|
68
|
+
|
|
69
|
+
return resolveMarkdown(
|
|
70
|
+
generator.markdown,
|
|
71
|
+
`${generator.name}.markdown`,
|
|
72
|
+
module && fileURLToPath(module)
|
|
73
|
+
);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Merges options into others: arrays add up, plain objects merge, and other
|
|
78
|
+
* values replace the ones they are merged into.
|
|
79
|
+
*
|
|
80
|
+
* @param {unknown} options - The options merged into
|
|
81
|
+
* @param {unknown} added - The options merged
|
|
82
|
+
* @returns {unknown}
|
|
83
|
+
*/
|
|
84
|
+
const mergeOptions = (options, added) => {
|
|
85
|
+
if (Array.isArray(options) && Array.isArray(added)) {
|
|
86
|
+
return [...options, ...added];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!isPlainObject(options) || !isPlainObject(added)) {
|
|
90
|
+
return added;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const merged = { ...options };
|
|
94
|
+
|
|
95
|
+
for (const [key, value] of Object.entries(added)) {
|
|
96
|
+
merged[key] = mergeOptions(options[key], value);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return merged;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Builds a list of a pipeline, with the configured plugins in place of its
|
|
104
|
+
* `'...'`. A configured plugin the list already has, or added earlier, isn't
|
|
105
|
+
* added again: its options merge into the ones it has.
|
|
106
|
+
*
|
|
107
|
+
* @param {Array<import('../configuration/types').PluginEntry | '...'>} [own] - The list
|
|
108
|
+
* @param {Array<import('../configuration/types').PluginEntry>} [configured] - The configured plugins
|
|
109
|
+
* @returns {Array<{ entry: import('../configuration/types').PluginEntry, added: boolean }>}
|
|
110
|
+
*/
|
|
111
|
+
const configureList = (own = [], configured = []) => {
|
|
112
|
+
const plugins = own.map(entry => ({ entry, added: false }));
|
|
113
|
+
const added = [];
|
|
114
|
+
|
|
115
|
+
// Each plugin by its specifier: the first listing of it takes the options
|
|
116
|
+
const listed = new Map();
|
|
117
|
+
|
|
118
|
+
for (const plugin of plugins) {
|
|
119
|
+
const [specifier] = enforceArray(plugin.entry);
|
|
120
|
+
|
|
121
|
+
if (!listed.has(specifier)) {
|
|
122
|
+
listed.set(specifier, plugin);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
for (const entry of configured) {
|
|
127
|
+
const [specifier, options] = enforceArray(entry);
|
|
128
|
+
const plugin = listed.get(specifier);
|
|
129
|
+
|
|
130
|
+
if (!plugin) {
|
|
131
|
+
const addedPlugin = { entry, added: true };
|
|
132
|
+
|
|
133
|
+
added.push(addedPlugin);
|
|
134
|
+
listed.set(specifier, addedPlugin);
|
|
135
|
+
} else if (options !== undefined) {
|
|
136
|
+
const [, listedOptions] = enforceArray(plugin.entry);
|
|
137
|
+
|
|
138
|
+
plugin.entry = [specifier, mergeOptions(listedOptions, options)];
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return plugins.flatMap(plugin =>
|
|
143
|
+
plugin.entry === CONFIGURED_PLUGINS ? added : [plugin]
|
|
144
|
+
);
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Imports a plugin as `processor.use()` takes it. Its module default-exports
|
|
149
|
+
* the plugin, a list of plugins, or a preset, or exports an async `load`,
|
|
150
|
+
* taking the plugin's options and returning it, for setup unified plugins
|
|
151
|
+
* can't do themselves.
|
|
152
|
+
*
|
|
153
|
+
* @param {import('../configuration/types').PluginEntry} entry - The plugin
|
|
154
|
+
* @returns {Promise<import('unified').Pluggable>}
|
|
155
|
+
*/
|
|
156
|
+
const importPlugin = async entry => {
|
|
157
|
+
const [specifier, ...options] = enforceArray(entry);
|
|
158
|
+
const { default: plugin, load } = await import(specifier);
|
|
159
|
+
|
|
160
|
+
if (typeof load === 'function') {
|
|
161
|
+
return [await load(...options)];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (typeof plugin === 'function') {
|
|
165
|
+
return [plugin, ...options];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// A list is used as a preset, not to be taken for a [plugin, options] pair
|
|
169
|
+
return Array.isArray(plugin) ? { plugins: plugin } : plugin;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Loads a generator's Markdown pipeline on the current thread, with the
|
|
174
|
+
* plugins configured for it. Loading the same configuration again does
|
|
175
|
+
* nothing.
|
|
176
|
+
*
|
|
177
|
+
* @param {GeneratorMetadata} generator - The generator
|
|
178
|
+
* @param {Partial<import('../configuration/types').MarkdownConfiguration>} [markdown] - The plugins configured for it
|
|
179
|
+
* @returns {Promise<void>}
|
|
180
|
+
*/
|
|
181
|
+
export const loadMarkdownPlugins = async (generator, markdown = {}) => {
|
|
182
|
+
if (!generator.markdown) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const key = JSON.stringify(markdown);
|
|
187
|
+
const loaded = loadedPipelines.get(generator.name);
|
|
188
|
+
|
|
189
|
+
if (loaded?.generator === generator && loaded.key === key) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const pipeline = resolveMarkdownPipeline(generator);
|
|
194
|
+
const configured = {};
|
|
195
|
+
const own = {};
|
|
196
|
+
|
|
197
|
+
// The lists import at once, so a slow plugin (Shiki) doesn't hold the rest
|
|
198
|
+
const loading = PLUGIN_LISTS.map(async list => {
|
|
199
|
+
const plugins = configureList(pipeline[list], markdown[list]);
|
|
200
|
+
const imports = plugins.map(({ entry }) => importPlugin(entry));
|
|
201
|
+
|
|
202
|
+
const imported = await Promise.all(imports);
|
|
203
|
+
|
|
204
|
+
configured[list] = imported;
|
|
205
|
+
own[list] = imported.filter((_, index) => !plugins[index].added);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
await Promise.all(loading);
|
|
209
|
+
|
|
210
|
+
loadedPipelines.set(generator.name, { generator, key, configured, own });
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Gets the plugins of a generator's Markdown pipeline, as loaded on the
|
|
215
|
+
* current thread: with the configured plugins, or only its own (with their
|
|
216
|
+
* configured options).
|
|
217
|
+
*
|
|
218
|
+
* @param {string} generator - The name of the generator
|
|
219
|
+
* @param {boolean} [configured] - Whether with the configured plugins
|
|
220
|
+
* @returns {MarkdownPlugins}
|
|
221
|
+
*/
|
|
222
|
+
export const getMarkdownPlugins = (generator, configured = true) => {
|
|
223
|
+
const loaded = loadedPipelines.get(generator);
|
|
224
|
+
|
|
225
|
+
if (!loaded) {
|
|
226
|
+
throw new Error(
|
|
227
|
+
`The Markdown pipeline of "${generator}" is not loaded on this thread`
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return configured ? loaded.configured : loaded.own;
|
|
232
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import remarkMdx from 'remark-mdx';
|
|
4
|
+
import { unified } from 'unified';
|
|
5
|
+
|
|
6
|
+
import { PLUGIN_LISTS } from './constants.mjs';
|
|
7
|
+
import { getMarkdownPlugins } from './plugins.mjs';
|
|
8
|
+
|
|
9
|
+
// The processors of each loaded pipeline, of Markdown and of MDX
|
|
10
|
+
const processors = new WeakMap();
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Creates the processor of a pipeline: its remark plugins, then its rehype
|
|
14
|
+
* ones, then its recma ones. A processor of MDX knows its syntax too, and
|
|
15
|
+
* tells plugins it processes MDX through `this.data('mdx')`.
|
|
16
|
+
*
|
|
17
|
+
* @param {import('./plugins.mjs').MarkdownPlugins} plugins - The pipeline
|
|
18
|
+
* @param {boolean} mdx - Whether it processes MDX
|
|
19
|
+
* @returns {import('unified').Processor}
|
|
20
|
+
*/
|
|
21
|
+
const createProcessor = (plugins, mdx) => {
|
|
22
|
+
const processor = unified().data('mdx', mdx);
|
|
23
|
+
|
|
24
|
+
if (mdx) {
|
|
25
|
+
processor.use(remarkMdx);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
for (const list of PLUGIN_LISTS) {
|
|
29
|
+
processor.use(plugins[list]);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return processor;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Gets the processor of a generator's Markdown pipeline, as loaded on the
|
|
37
|
+
* current thread (see `loadMarkdownPlugins`).
|
|
38
|
+
*
|
|
39
|
+
* @param {string} generator - The name of the generator
|
|
40
|
+
* @param {Object} [options]
|
|
41
|
+
* @param {boolean} [options.mdx] - Whether it processes MDX
|
|
42
|
+
* @param {boolean} [options.configured] - Whether it runs the configured
|
|
43
|
+
* plugins, rather than only the generator's own
|
|
44
|
+
* @returns {import('unified').Processor}
|
|
45
|
+
*/
|
|
46
|
+
export const getProcessor = (
|
|
47
|
+
generator,
|
|
48
|
+
{ mdx = false, configured = true } = {}
|
|
49
|
+
) => {
|
|
50
|
+
const plugins = getMarkdownPlugins(generator, configured);
|
|
51
|
+
|
|
52
|
+
if (!processors.has(plugins)) {
|
|
53
|
+
processors.set(plugins, {});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const created = processors.get(plugins);
|
|
57
|
+
const syntax = mdx ? 'mdx' : 'markdown';
|
|
58
|
+
|
|
59
|
+
return (created[syntax] ??= createProcessor(plugins, mdx));
|
|
60
|
+
};
|