@doc-kit/core 1.3.0 → 1.3.1
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/package.json +3 -5
- package/src/generators/ast/generate.mjs +4 -6
- package/src/generators/ast-js/generate.mjs +4 -4
- package/src/generators/json-simple/generate.mjs +13 -5
- package/src/generators.mjs +5 -3
- package/src/threading/constants.mjs +14 -0
- package/src/threading/index.mjs +6 -2
- package/src/threading/parallel.mjs +28 -31
- package/src/utils/configuration/constants.mjs +4 -6
- package/src/utils/configuration/index.mjs +3 -1
- package/src/utils/glob.mjs +42 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @doc-kit/core
|
|
2
2
|
|
|
3
|
+
## 1.3.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#1187](https://github.com/nodejs/doc-kit/pull/1187) [`e49eb52`](https://github.com/nodejs/doc-kit/commit/e49eb52472dfc2707a5966ee25c4dc7201360850) Thanks [@dxnnystyles](https://github.com/dxnnystyles)! - Keep json-simple output generation from mutating shared metadata entries.
|
|
8
|
+
|
|
9
|
+
- [#1179](https://github.com/nodejs/doc-kit/pull/1179) [`b8d7b77`](https://github.com/nodejs/doc-kit/commit/b8d7b7725f0dbe085d9f32af60fddd7459555af9) Thanks [@ovflowd](https://github.com/ovflowd)! - Builds use less memory, most of all with few threads: each worker's young generation is limited to 96MB, a single thread processes one chunk at a time and lets go of the pages' code before rendering them, and Vite's native bundler gets as many threads as doc-kit and returns the memory it frees. On the Node.js API docs, `--threads 1` now builds with Node.js's `--max-old-space-size=512`, and no longer crashes when `legacy-html` is built alongside other targets.
|
|
10
|
+
|
|
11
|
+
- [#1180](https://github.com/nodejs/doc-kit/pull/1180) [`92efbbe`](https://github.com/nodejs/doc-kit/commit/92efbbeb727a38b178c9ccb63a3a4f9cfbc110a4) Thanks [@ovflowd](https://github.com/ovflowd)! - Find input files with Node.js's `fs.globSync` instead of `tinyglobby` and `glob-parent`.
|
|
12
|
+
|
|
13
|
+
- [#1082](https://github.com/nodejs/doc-kit/pull/1082) [`9e21017`](https://github.com/nodejs/doc-kit/commit/9e21017d9597e3f8b5a18a41f3db8e5de216213a) Thanks [@btea](https://github.com/btea)! - Add a `doc-kit preview` command that serves the generated docs locally, building first when any `generate` option is passed. The default output directory (`./out`) is now defined in the shared configuration, so `generate` also writes there when no `--output` is given.
|
|
14
|
+
|
|
15
|
+
- [#1193](https://github.com/nodejs/doc-kit/pull/1193) [`854a944`](https://github.com/nodejs/doc-kit/commit/854a944699b3a996cb3a8544deaec3fe916fd449) Thanks [@OKAN-SOFTWARE](https://github.com/OKAN-SOFTWARE)! - Keep worker results and discovered source files in deterministic order so repeated documentation builds produce stable output.
|
|
16
|
+
|
|
17
|
+
- [#1178](https://github.com/nodejs/doc-kit/pull/1178) [`0321911`](https://github.com/nodejs/doc-kit/commit/03219116c78e7f4a721b4e39bc71ba54cb6f0ba8) Thanks [@ovflowd](https://github.com/ovflowd)! - Install fewer packages: `@doc-kit/core` updates `cosmiconfig` to v10 (TypeScript config files now load through Node's type stripping) and no longer installs `dedent`, and `@doc-kit/generator-react` estimates reading time with `Intl.Segmenter` instead of the `reading-time` package and updates `@node-core/ui-components` to 1.8.1, which no longer installs `@vcarl/remark-headings`.
|
|
18
|
+
|
|
3
19
|
## 1.3.0
|
|
4
20
|
|
|
5
21
|
### Minor Changes
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@doc-kit/core",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.3.
|
|
4
|
+
"version": "1.3.1",
|
|
5
5
|
"description": "The doc-kit engine: the pipeline and shared APIs that power the doc-kit generators and CLI",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -50,10 +50,8 @@
|
|
|
50
50
|
"@swc/html-wasm": "^1.16.2",
|
|
51
51
|
"@swc/wasm": "^1.15.46",
|
|
52
52
|
"acorn": "^8.17.0",
|
|
53
|
-
"cosmiconfig": "^
|
|
54
|
-
"dedent": "^1.7.2",
|
|
53
|
+
"cosmiconfig": "^10.0.1",
|
|
55
54
|
"github-slugger": "^2.0.0",
|
|
56
|
-
"glob-parent": "^6.0.2",
|
|
57
55
|
"mdast-util-slice-markdown": "^2.0.1",
|
|
58
56
|
"mdast-util-to-string": "^4.0.0",
|
|
59
57
|
"piscina": "^5.3.2",
|
|
@@ -65,7 +63,6 @@
|
|
|
65
63
|
"remark-stringify": "^11.0.0",
|
|
66
64
|
"semver": "^7.8.5",
|
|
67
65
|
"shiki": "^4.4.3",
|
|
68
|
-
"tinyglobby": "^0.2.17",
|
|
69
66
|
"unified": "^11.0.5",
|
|
70
67
|
"unist-builder": "^4.0.0",
|
|
71
68
|
"unist-util-find-after": "^5.0.0",
|
|
@@ -77,6 +74,7 @@
|
|
|
77
74
|
},
|
|
78
75
|
"devDependencies": {
|
|
79
76
|
"ajv": "^8.20.0",
|
|
77
|
+
"dedent": "^1.7.2",
|
|
80
78
|
"hast-util-to-html": "^9.0.5",
|
|
81
79
|
"hast-util-to-string": "^3.0.1"
|
|
82
80
|
},
|
|
@@ -3,12 +3,11 @@
|
|
|
3
3
|
import { readFile } from 'node:fs/promises';
|
|
4
4
|
import { relative, sep } from 'node:path/posix';
|
|
5
5
|
|
|
6
|
-
import globParent from 'glob-parent';
|
|
7
|
-
import { globSync } from 'tinyglobby';
|
|
8
6
|
import { parse as parseYaml } from 'yaml';
|
|
9
7
|
|
|
10
8
|
import getConfig from '#utils/configuration/index.mjs';
|
|
11
9
|
import { withExt } from '#utils/file.mjs';
|
|
10
|
+
import { findFiles, globParent } from '#utils/glob.mjs';
|
|
12
11
|
import { getProcessor } from '#utils/markdown/processor.mjs';
|
|
13
12
|
import { QUERIES } from '#utils/queries/index.mjs';
|
|
14
13
|
|
|
@@ -117,10 +116,9 @@ export async function* generate(_, worker) {
|
|
|
117
116
|
const files = config.input.flatMap(input => {
|
|
118
117
|
const parent = globParent(input);
|
|
119
118
|
|
|
120
|
-
return
|
|
121
|
-
|
|
122
|
-
parent
|
|
123
|
-
]);
|
|
119
|
+
return findFiles(input, { ignore: config.ignore })
|
|
120
|
+
.sort()
|
|
121
|
+
.map(child => [child, parent]);
|
|
124
122
|
});
|
|
125
123
|
|
|
126
124
|
// Parse markdown files in parallel using worker threads
|
|
@@ -4,9 +4,9 @@ import { readFile } from 'node:fs/promises';
|
|
|
4
4
|
import { extname } from 'node:path';
|
|
5
5
|
|
|
6
6
|
import { parse } from 'acorn';
|
|
7
|
-
import { globSync } from 'tinyglobby';
|
|
8
7
|
|
|
9
8
|
import getConfig from '#utils/configuration/index.mjs';
|
|
9
|
+
import { findFiles } from '#utils/glob.mjs';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Process a chunk of JavaScript files in a worker thread.
|
|
@@ -44,9 +44,9 @@ export async function processChunk(inputSlice, itemIndices) {
|
|
|
44
44
|
export async function* generate(_, worker) {
|
|
45
45
|
const config = getConfig('ast-js');
|
|
46
46
|
|
|
47
|
-
const files =
|
|
48
|
-
p => extname(p) === '.js'
|
|
49
|
-
|
|
47
|
+
const files = findFiles(config.input, { ignore: config.ignore })
|
|
48
|
+
.filter(p => extname(p) === '.js')
|
|
49
|
+
.sort();
|
|
50
50
|
|
|
51
51
|
// Parse the Javascript sources into ASTs in parallel using worker threads
|
|
52
52
|
// source is both the items list and the fullInput since we use sliceInput
|
|
@@ -16,13 +16,21 @@ import { UNIST } from '#utils/queries/index.mjs';
|
|
|
16
16
|
export async function generate(input) {
|
|
17
17
|
const config = getConfig('json-simple');
|
|
18
18
|
|
|
19
|
-
input.
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
const output = input.map(entry => {
|
|
20
|
+
const copy = { ...entry, content: structuredClone(entry.content) };
|
|
21
|
+
|
|
22
|
+
remove(copy.content, [UNIST.isStabilityNode, UNIST.isHeading]);
|
|
23
|
+
|
|
24
|
+
return copy;
|
|
25
|
+
});
|
|
22
26
|
|
|
23
27
|
if (config.output) {
|
|
24
|
-
await writeJSON(
|
|
28
|
+
await writeJSON(
|
|
29
|
+
join(config.output, 'api-docs.json'),
|
|
30
|
+
output,
|
|
31
|
+
config.minify
|
|
32
|
+
);
|
|
25
33
|
}
|
|
26
34
|
|
|
27
|
-
return
|
|
35
|
+
return output;
|
|
28
36
|
}
|
package/src/generators.mjs
CHANGED
|
@@ -87,8 +87,10 @@ const createGenerator = () => {
|
|
|
87
87
|
? createParallelWorker(specifier, generator, pool, configuration)
|
|
88
88
|
: Promise.resolve(null);
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
|
|
90
|
+
// Returned rather than awaited, so this function doesn't hold on to the
|
|
91
|
+
// input while the generator runs, and the generator can let it go
|
|
92
|
+
return generate(dependencyInput, await worker);
|
|
93
|
+
})().then(result => {
|
|
92
94
|
// For streaming generators, "Completed" is logged when the cache
|
|
93
95
|
// finishes collecting, not here when the generator returns
|
|
94
96
|
if (!isAsyncIterable(result)) {
|
|
@@ -96,7 +98,7 @@ const createGenerator = () => {
|
|
|
96
98
|
}
|
|
97
99
|
|
|
98
100
|
return result;
|
|
99
|
-
})
|
|
101
|
+
})
|
|
100
102
|
);
|
|
101
103
|
};
|
|
102
104
|
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* The default `threads` ceiling. Each worker holds a heap of its own, with the
|
|
5
|
+
* libraries and the pages it is working on, so past a few threads memory, not
|
|
6
|
+
* CPU, is what runs out. `--threads` raises it.
|
|
7
|
+
*/
|
|
8
|
+
export const DEFAULT_MAX_THREADS = 4;
|
|
9
|
+
|
|
3
10
|
/**
|
|
4
11
|
* The heap size limit of each worker thread (V8's old space), in MB. V8 lets a
|
|
5
12
|
* heap grow to several times its live data before collecting it, the more the
|
|
@@ -8,3 +15,10 @@
|
|
|
8
15
|
* `--max-old-space-size` replaces it.
|
|
9
16
|
*/
|
|
10
17
|
export const WORKER_HEAP_SIZE = 512;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The young generation size limit of each worker thread, in MB. V8 sizes it
|
|
21
|
+
* from the machine's memory, as if each worker were the only one, so it made up
|
|
22
|
+
* over half of a worker's heap. Node.js's `--max-semi-space-size` replaces it.
|
|
23
|
+
*/
|
|
24
|
+
export const WORKER_GEN_SIZE = 96;
|
package/src/threading/index.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import Piscina from 'piscina';
|
|
|
2
2
|
|
|
3
3
|
import logger from '#logger/index.mjs';
|
|
4
4
|
|
|
5
|
-
import { WORKER_HEAP_SIZE } from './constants.mjs';
|
|
5
|
+
import { WORKER_HEAP_SIZE, WORKER_GEN_SIZE } from './constants.mjs';
|
|
6
6
|
|
|
7
7
|
const poolLogger = logger.child('WorkerPool');
|
|
8
8
|
|
|
@@ -27,7 +27,11 @@ export default function createWorkerPool(threads) {
|
|
|
27
27
|
// A worker idle for 250ms ends, so its heap is gone during a long stretch
|
|
28
28
|
// of work on the main thread, such as bundling the site
|
|
29
29
|
idleTimeout: 250,
|
|
30
|
-
resourceLimits: {
|
|
30
|
+
resourceLimits: {
|
|
31
|
+
maxOldGenerationSizeMb: WORKER_HEAP_SIZE,
|
|
32
|
+
maxYoungGenerationSizeMb: WORKER_GEN_SIZE,
|
|
33
|
+
},
|
|
34
|
+
recordTiming: false,
|
|
31
35
|
workerData: { logLevel: logger.getLogLevel() },
|
|
32
36
|
});
|
|
33
37
|
}
|
|
@@ -77,12 +77,12 @@ export default function createParallelWorker(
|
|
|
77
77
|
|
|
78
78
|
return {
|
|
79
79
|
/**
|
|
80
|
-
* Processes items in parallel, yielding results
|
|
80
|
+
* Processes items in parallel, yielding results in submission order.
|
|
81
81
|
*
|
|
82
82
|
* @template T
|
|
83
83
|
* @param {T[]} items - Items to process
|
|
84
84
|
* @param {object} extra - Extra options
|
|
85
|
-
* @yields {R[]} Chunk results
|
|
85
|
+
* @yields {R[]} Chunk results in input order
|
|
86
86
|
*/
|
|
87
87
|
async *stream(items, extra) {
|
|
88
88
|
if (items.length === 0) {
|
|
@@ -96,44 +96,41 @@ export default function createParallelWorker(
|
|
|
96
96
|
{ generator: name, chunks: chunks.length, chunkSize, threads }
|
|
97
97
|
);
|
|
98
98
|
|
|
99
|
-
|
|
99
|
+
// On this thread, one chunk at a time, so their work isn't all held at once
|
|
100
|
+
if (threads <= 1 || items.length <= 2) {
|
|
101
|
+
for (const indices of chunks) {
|
|
102
|
+
yield await generator.processChunk(items, indices, extra);
|
|
103
|
+
}
|
|
100
104
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
chunks.map(indices => {
|
|
104
|
-
if (runInOneGo) {
|
|
105
|
-
const promise = generator
|
|
106
|
-
.processChunk(items, indices, extra)
|
|
107
|
-
.then(result => ({ promise, result }));
|
|
108
|
-
|
|
109
|
-
return promise;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const promise = pool
|
|
113
|
-
.run(
|
|
114
|
-
createTask(items, indices, extra, configuration, specifier, name)
|
|
115
|
-
)
|
|
116
|
-
.then(result => ({ promise, result }));
|
|
117
|
-
|
|
118
|
-
return promise;
|
|
119
|
-
})
|
|
120
|
-
);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
121
107
|
|
|
122
|
-
//
|
|
123
|
-
|
|
108
|
+
// Start every chunk immediately, but emit results in submission order.
|
|
109
|
+
// Handle errors as each task settles so a later failure cannot become an
|
|
110
|
+
// unhandled rejection while earlier chunks are still running.
|
|
111
|
+
const tasks = chunks.map(indices => {
|
|
112
|
+
const task = pool.run(
|
|
113
|
+
createTask(items, indices, extra, configuration, specifier, name)
|
|
114
|
+
);
|
|
124
115
|
|
|
125
|
-
|
|
126
|
-
|
|
116
|
+
return task.then(
|
|
117
|
+
result => ({ result }),
|
|
118
|
+
error => ({ error })
|
|
119
|
+
);
|
|
120
|
+
});
|
|
127
121
|
|
|
128
|
-
|
|
122
|
+
for (const [index, task] of tasks.entries()) {
|
|
123
|
+
const outcome = await task;
|
|
129
124
|
|
|
130
|
-
|
|
125
|
+
if ('error' in outcome) {
|
|
126
|
+
throw outcome.error;
|
|
127
|
+
}
|
|
131
128
|
|
|
132
|
-
parallelLogger.debug(`Chunk ${
|
|
129
|
+
parallelLogger.debug(`Chunk ${index + 1}/${chunks.length} completed`, {
|
|
133
130
|
generator: name,
|
|
134
131
|
});
|
|
135
132
|
|
|
136
|
-
yield result;
|
|
133
|
+
yield outcome.result;
|
|
137
134
|
}
|
|
138
135
|
},
|
|
139
136
|
};
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* The default
|
|
5
|
-
* libraries and the pages it is working on, so past a few threads memory, not
|
|
6
|
-
* CPU, is what runs out. `--threads` raises it.
|
|
4
|
+
* The default number of items each worker task processes.
|
|
7
5
|
*/
|
|
8
|
-
export const
|
|
6
|
+
export const DEFAULT_CHUNK_SIZE = 10;
|
|
9
7
|
|
|
10
8
|
/**
|
|
11
|
-
* The default
|
|
9
|
+
* The default directory generators write to when no `output` is configured.
|
|
12
10
|
*/
|
|
13
|
-
export const
|
|
11
|
+
export const DEFAULT_OUTPUT_DIRECTORY = './out';
|
|
@@ -22,7 +22,8 @@ import {
|
|
|
22
22
|
import { resolveMarkdown } from '#utils/markdown/plugins.mjs';
|
|
23
23
|
import { deepMerge } from '#utils/misc.mjs';
|
|
24
24
|
|
|
25
|
-
import {
|
|
25
|
+
import { DEFAULT_MAX_THREADS } from '../../threading/constants.mjs';
|
|
26
|
+
import { DEFAULT_CHUNK_SIZE, DEFAULT_OUTPUT_DIRECTORY } from './constants.mjs';
|
|
26
27
|
|
|
27
28
|
const configExplorer = cosmiconfig('doc-kit');
|
|
28
29
|
|
|
@@ -63,6 +64,7 @@ export const getDefaultConfig = (generators, config) =>
|
|
|
63
64
|
project: detectProject() ?? 'API Docs',
|
|
64
65
|
version: process.version,
|
|
65
66
|
minify: false,
|
|
67
|
+
output: DEFAULT_OUTPUT_DIRECTORY,
|
|
66
68
|
ref: 'HEAD',
|
|
67
69
|
// Without release history there is nothing to build a version picker
|
|
68
70
|
// from, so generators render single-version output.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { globSync } from 'node:fs';
|
|
2
|
+
import { relative, resolve, sep } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
// A path segment holding any glob syntax (wildcards, classes, braces or extglobs)
|
|
6
|
+
const GLOB_SEGMENT = /[*?[{]|[!+@]\(/;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Finds the files matching glob patterns, as POSIX paths relative to `cwd`
|
|
10
|
+
* (absolute patterns included). Directories are left out.
|
|
11
|
+
*
|
|
12
|
+
* @param {string | string[]} patterns - Glob patterns to match
|
|
13
|
+
* @param {{ cwd?: string | URL, ignore?: string[] }} [options]
|
|
14
|
+
* @returns {string[]}
|
|
15
|
+
*/
|
|
16
|
+
export const findFiles = (patterns, { cwd = process.cwd(), ignore } = {}) => {
|
|
17
|
+
const root = cwd instanceof URL ? fileURLToPath(cwd) : cwd;
|
|
18
|
+
|
|
19
|
+
return globSync(patterns, { cwd: root, exclude: ignore, withFileTypes: true })
|
|
20
|
+
.filter(dirent => !dirent.isDirectory())
|
|
21
|
+
.map(dirent =>
|
|
22
|
+
relative(root, resolve(root, dirent.parentPath, dirent.name))
|
|
23
|
+
.split(sep)
|
|
24
|
+
.join('/')
|
|
25
|
+
);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Returns the directory part of a glob pattern before its first glob segment,
|
|
30
|
+
* e.g. `doc/api` for `doc/api/*.md`, or the parent directory of a plain path.
|
|
31
|
+
* Like `fs.globSync`, it reads backslashes as separators, never as escapes.
|
|
32
|
+
*
|
|
33
|
+
* @param {string} pattern - A glob pattern
|
|
34
|
+
* @returns {string}
|
|
35
|
+
*/
|
|
36
|
+
export const globParent = pattern => {
|
|
37
|
+
const segments = pattern.replaceAll('\\', '/').split('/');
|
|
38
|
+
const index = segments.findIndex(segment => GLOB_SEGMENT.test(segment));
|
|
39
|
+
const parent = segments.slice(0, index === -1 ? -1 : index).join('/');
|
|
40
|
+
|
|
41
|
+
return parent || (segments[0] === '' ? '/' : '.');
|
|
42
|
+
};
|