@doc-kit/core 0.0.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/LICENSE +21 -0
- package/README.md +36 -0
- package/package.json +88 -0
- package/shiki.config.mjs +50 -0
- package/src/caching.mjs +183 -0
- package/src/generators/ast/README.md +8 -0
- package/src/generators/ast/constants.mjs +1 -0
- package/src/generators/ast/generate.mjs +125 -0
- package/src/generators/ast/index.mjs +20 -0
- package/src/generators/ast/types.d.ts +7 -0
- package/src/generators/ast-js/README.md +8 -0
- package/src/generators/ast-js/generate.mjs +56 -0
- package/src/generators/ast-js/index.mjs +24 -0
- package/src/generators/ast-js/types.d.ts +5 -0
- package/src/generators/index.mjs +48 -0
- package/src/generators/json-simple/README.md +9 -0
- package/src/generators/json-simple/generate.mjs +33 -0
- package/src/generators/json-simple/index.mjs +23 -0
- package/src/generators/json-simple/types.d.ts +6 -0
- package/src/generators/loader.mjs +96 -0
- package/src/generators/metadata/README.md +8 -0
- package/src/generators/metadata/constants.mjs +68 -0
- package/src/generators/metadata/generate.mjs +40 -0
- package/src/generators/metadata/index.mjs +21 -0
- package/src/generators/metadata/maps/builtin.json +16 -0
- package/src/generators/metadata/maps/mdn.json +1114 -0
- package/src/generators/metadata/types.d.ts +145 -0
- package/src/generators/metadata/utils/parse.mjs +160 -0
- package/src/generators/metadata/utils/resolveTypes.mjs +204 -0
- package/src/generators/metadata/utils/slugger.mjs +39 -0
- package/src/generators/metadata/utils/transformers.mjs +109 -0
- package/src/generators/metadata/utils/visitors.mjs +134 -0
- package/src/generators/metadata/utils/yaml.mjs +58 -0
- package/src/generators/types.d.ts +129 -0
- package/src/generators.mjs +145 -0
- package/src/logger/constants.mjs +34 -0
- package/src/logger/index.mjs +27 -0
- package/src/logger/logger.mjs +177 -0
- package/src/logger/transports/console.mjs +54 -0
- package/src/logger/transports/github.mjs +42 -0
- package/src/logger/transports/index.mjs +11 -0
- package/src/logger/types.d.ts +29 -0
- package/src/logger/utils/colors.mjs +18 -0
- package/src/logger/utils/time.mjs +21 -0
- package/src/parsers/markdown.mjs +49 -0
- package/src/parsers/types.d.ts +7 -0
- package/src/threading/chunk-worker.mjs +23 -0
- package/src/threading/index.mjs +27 -0
- package/src/threading/parallel.mjs +140 -0
- package/src/utils/array.mjs +7 -0
- package/src/utils/configuration/index.mjs +263 -0
- package/src/utils/configuration/templates.mjs +21 -0
- package/src/utils/configuration/types.d.ts +64 -0
- package/src/utils/file.mjs +20 -0
- package/src/utils/generators.mjs +71 -0
- package/src/utils/highlighter.mjs +198 -0
- package/src/utils/html-minifier.mjs +12 -0
- package/src/utils/loaders.mjs +48 -0
- package/src/utils/misc.mjs +72 -0
- package/src/utils/queries/constants.mjs +3 -0
- package/src/utils/queries/index.mjs +93 -0
- package/src/utils/queries/utils.mjs +45 -0
- package/src/utils/remark.mjs +97 -0
- package/src/utils/signature/constants.mjs +12 -0
- package/src/utils/signature/parseList.mjs +133 -0
- package/src/utils/signature/parseSignature.mjs +208 -0
- package/src/utils/signature/types.d.ts +66 -0
- package/src/utils/swc.mjs +34 -0
- package/src/utils/type-annotations/hast.mjs +115 -0
- package/src/utils/type-annotations/mdast.mjs +75 -0
- package/src/utils/type-annotations/remark.mjs +24 -0
- package/src/utils/type-annotations/syntax.mjs +188 -0
- package/src/utils/unist.mjs +75 -0
- package/src/utils/url.mjs +27 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# @doc-kit/core
|
|
2
|
+
|
|
3
|
+
## 1.4.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#905](https://github.com/nodejs/doc-kit/pull/905) [`f214a00`](https://github.com/nodejs/doc-kit/commit/f214a009d5ceadddc2a3b8d83b94eb070eb6e790) Thanks [@avivkeller](https://github.com/avivkeller)! - Allow for the specification of dynamically generated configuration values
|
|
8
|
+
|
|
9
|
+
## 1.4.2
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [#855](https://github.com/nodejs/doc-kit/pull/855) [`d7bdf39`](https://github.com/nodejs/doc-kit/commit/d7bdf39cc60852aaec42b6846fbacb89aecf3eec) Thanks [@bmuenzenmeyer](https://github.com/bmuenzenmeyer)! - Adopt Changesets for releases: versioning and publishing are now driven by changeset files, which
|
|
14
|
+
produce a `CHANGELOG.md`, git tags, and GitHub Releases.
|
|
15
|
+
|
|
16
|
+
- [#895](https://github.com/nodejs/doc-kit/pull/895) [`9d7ce14`](https://github.com/nodejs/doc-kit/commit/9d7ce14c0fa11ee34b475cb5a752ce7e2cd44cb2) Thanks [@avivkeller](https://github.com/avivkeller)! - Avoids directly mutating the AST in `legacy-json`, as to ensure future generators do not run with a input different than they expect.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright Node.js Website WG contributors. All rights reserved.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to
|
|
7
|
+
deal in the Software without restriction, including without limitation the
|
|
8
|
+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
9
|
+
sell copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
20
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
21
|
+
IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# `@doc-kit/core`
|
|
2
|
+
|
|
3
|
+
The core of [doc-kit](https://github.com/nodejs/doc-kit): the engine that
|
|
4
|
+
turns API-shaped Markdown into documentation sites, JSON, man pages, and
|
|
5
|
+
more. It powers the Node.js API reference, and it can document any project.
|
|
6
|
+
|
|
7
|
+
The command-line interface lives in the companion
|
|
8
|
+
[`@doc-kit/cli`](https://www.npmjs.com/package/@doc-kit/cli) package.
|
|
9
|
+
|
|
10
|
+
## Generators
|
|
11
|
+
|
|
12
|
+
Output formats are provided by generators. This package ships the shared
|
|
13
|
+
pipeline stages and `json-simple`; the rest come from companion packages:
|
|
14
|
+
|
|
15
|
+
| Package | Generators |
|
|
16
|
+
| -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
|
|
17
|
+
| [`@doc-kit/generator-react`](https://www.npmjs.com/package/@doc-kit/generator-react) | `html` (the modern site), `orama-db`, `llms-txt`, `sitemap` |
|
|
18
|
+
| [`@node-core/doc-kit-legacy`](https://www.npmjs.com/package/@node-core/doc-kit-legacy) | `legacy-html`, `legacy-html-all`, `legacy-json`, `legacy-json-all` (Node.js-specific) |
|
|
19
|
+
| [`@node-core/doc-kit`](https://www.npmjs.com/package/@node-core/doc-kit) | `man-page`, `api-links`, `addon-verify` (Node.js-specific) |
|
|
20
|
+
|
|
21
|
+
Custom generators load by import specifier — any module whose default export
|
|
22
|
+
is a generator works as a `--target`.
|
|
23
|
+
|
|
24
|
+
## Contributing
|
|
25
|
+
|
|
26
|
+
This package lives in the [nodejs/doc-kit](https://github.com/nodejs/doc-kit)
|
|
27
|
+
monorepo. From this directory (or the repository root with
|
|
28
|
+
`npm run <script> --workspace @doc-kit/core`):
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
# Run this package's tests
|
|
32
|
+
npm test
|
|
33
|
+
|
|
34
|
+
# Re-run the tests on every change
|
|
35
|
+
npm run test:watch
|
|
36
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@doc-kit/core",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"description": "The doc-kit engine: the pipeline and shared APIs that power the doc-kit generators and CLI",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/nodejs/doc-kit.git",
|
|
9
|
+
"directory": "packages/core"
|
|
10
|
+
},
|
|
11
|
+
"main": "./src/generators.mjs",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": "./src/generators.mjs",
|
|
14
|
+
"./ast": "./src/generators/ast/index.mjs",
|
|
15
|
+
"./ast-js": "./src/generators/ast-js/index.mjs",
|
|
16
|
+
"./json-simple": "./src/generators/json-simple/index.mjs",
|
|
17
|
+
"./metadata": "./src/generators/metadata/index.mjs",
|
|
18
|
+
"./package.json": "./package.json",
|
|
19
|
+
"./shiki.config.mjs": "./shiki.config.mjs",
|
|
20
|
+
"./src/*": "./src/*",
|
|
21
|
+
"./*": "./src/*"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"src",
|
|
25
|
+
"!src/**/*.test.mjs",
|
|
26
|
+
"!src/**/__tests__",
|
|
27
|
+
"shiki.config.mjs",
|
|
28
|
+
"CHANGELOG.md",
|
|
29
|
+
"LICENSE",
|
|
30
|
+
"README.md"
|
|
31
|
+
],
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@actions/core": "^3.0.0",
|
|
34
|
+
"@node-core/rehype-shiki": "^1.4.3",
|
|
35
|
+
"@swc/html-wasm": "^1.15.46",
|
|
36
|
+
"@swc/wasm": "^1.15.46",
|
|
37
|
+
"acorn": "^8.17.0",
|
|
38
|
+
"cosmiconfig": "^9.0.2",
|
|
39
|
+
"dedent": "^1.7.2",
|
|
40
|
+
"github-slugger": "^2.0.0",
|
|
41
|
+
"glob-parent": "^6.0.2",
|
|
42
|
+
"hastscript": "^9.0.1",
|
|
43
|
+
"piscina": "^5.3.0",
|
|
44
|
+
"rehype-stringify": "^10.0.1",
|
|
45
|
+
"remark-gfm": "^4.0.1",
|
|
46
|
+
"remark-mdx": "^3.1.1",
|
|
47
|
+
"remark-parse": "^11.0.0",
|
|
48
|
+
"remark-rehype": "^11.1.2",
|
|
49
|
+
"remark-stringify": "^11.0.0",
|
|
50
|
+
"semver": "^7.8.5",
|
|
51
|
+
"shiki": "^4.3.0",
|
|
52
|
+
"tinyglobby": "^0.2.17",
|
|
53
|
+
"unified": "^11.0.5",
|
|
54
|
+
"unist-builder": "^4.0.0",
|
|
55
|
+
"unist-util-find-after": "^5.0.0",
|
|
56
|
+
"unist-util-position": "^5.0.0",
|
|
57
|
+
"unist-util-remove": "^4.0.0",
|
|
58
|
+
"unist-util-select": "^5.1.0",
|
|
59
|
+
"unist-util-visit": "^5.1.0",
|
|
60
|
+
"yaml": "^2.9.0"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"hast-util-to-html": "^9.0.5",
|
|
64
|
+
"hast-util-to-string": "^3.0.1"
|
|
65
|
+
},
|
|
66
|
+
"peerDependencies": {
|
|
67
|
+
"@node-core/doc-kit": "1.4.3",
|
|
68
|
+
"@node-core/doc-kit-legacy": "0.0.0",
|
|
69
|
+
"@doc-kit/generator-react": "0.0.0"
|
|
70
|
+
},
|
|
71
|
+
"peerDependenciesMeta": {
|
|
72
|
+
"@node-core/doc-kit": {
|
|
73
|
+
"optional": true
|
|
74
|
+
},
|
|
75
|
+
"@node-core/doc-kit-legacy": {
|
|
76
|
+
"optional": true
|
|
77
|
+
},
|
|
78
|
+
"@doc-kit/generator-react": {
|
|
79
|
+
"optional": true
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"scripts": {
|
|
83
|
+
"test": "node --test --experimental-test-module-mocks \"src/**/*.test.mjs\"",
|
|
84
|
+
"test:coverage": "c8 node --test --experimental-test-module-mocks \"src/**/*.test.mjs\"",
|
|
85
|
+
"test:update-snapshots": "node --test --experimental-test-module-mocks --test-update-snapshots \"src/**/*.test.mjs\"",
|
|
86
|
+
"test:watch": "node --test --experimental-test-module-mocks --watch \"src/**/*.test.mjs\""
|
|
87
|
+
}
|
|
88
|
+
}
|
package/shiki.config.mjs
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import cLanguage from 'shiki/langs/c.mjs';
|
|
4
|
+
import cmakeLanguage from 'shiki/langs/cmake.mjs';
|
|
5
|
+
import coffeeScriptLanguage from 'shiki/langs/coffeescript.mjs';
|
|
6
|
+
import cPlusPlusLanguage from 'shiki/langs/cpp.mjs';
|
|
7
|
+
import cssLanguage from 'shiki/langs/css.mjs';
|
|
8
|
+
import diffLanguage from 'shiki/langs/diff.mjs';
|
|
9
|
+
import dockerLanguage from 'shiki/langs/docker.mjs';
|
|
10
|
+
import htmlLanguage from 'shiki/langs/html.mjs';
|
|
11
|
+
import httpLanguage from 'shiki/langs/http.mjs';
|
|
12
|
+
import javaScriptLanguage from 'shiki/langs/javascript.mjs';
|
|
13
|
+
import jsonLanguage from 'shiki/langs/json.mjs';
|
|
14
|
+
import powershellLanguage from 'shiki/langs/powershell.mjs';
|
|
15
|
+
import pythonLanguage from 'shiki/langs/python.mjs';
|
|
16
|
+
import shellScriptLanguage from 'shiki/langs/shellscript.mjs';
|
|
17
|
+
import shellSessionLanguage from 'shiki/langs/shellsession.mjs';
|
|
18
|
+
import typeScriptLanguage from 'shiki/langs/typescript.mjs';
|
|
19
|
+
import lightTheme from 'shiki/themes/catppuccin-latte.mjs';
|
|
20
|
+
import darkTheme from 'shiki/themes/catppuccin-mocha.mjs';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Creates a Shiki configuration for the API Docs tooling
|
|
24
|
+
*
|
|
25
|
+
* @type {import('@shikijs/core').HighlighterCoreOptions}
|
|
26
|
+
*/
|
|
27
|
+
export default {
|
|
28
|
+
// Only register the themes we need, to support light/dark theme
|
|
29
|
+
themes: [lightTheme, darkTheme],
|
|
30
|
+
// Only register the languages that the API docs use
|
|
31
|
+
// and override the JavaScript language with the aliases
|
|
32
|
+
langs: [
|
|
33
|
+
...httpLanguage,
|
|
34
|
+
...jsonLanguage,
|
|
35
|
+
...typeScriptLanguage,
|
|
36
|
+
...shellScriptLanguage,
|
|
37
|
+
...powershellLanguage,
|
|
38
|
+
...shellSessionLanguage,
|
|
39
|
+
...dockerLanguage,
|
|
40
|
+
...diffLanguage,
|
|
41
|
+
...cLanguage,
|
|
42
|
+
...cPlusPlusLanguage,
|
|
43
|
+
...coffeeScriptLanguage,
|
|
44
|
+
...cmakeLanguage,
|
|
45
|
+
...pythonLanguage,
|
|
46
|
+
...htmlLanguage,
|
|
47
|
+
...cssLanguage,
|
|
48
|
+
{ ...javaScriptLanguage[0], aliases: ['mjs', 'cjs', 'js'] },
|
|
49
|
+
],
|
|
50
|
+
};
|
package/src/caching.mjs
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import logger from './logger/index.mjs';
|
|
4
|
+
import { isAsyncIterable } from './utils/misc.mjs';
|
|
5
|
+
|
|
6
|
+
const cachingLogger = logger.child('caching');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Collects all values from an async generator into a flat array.
|
|
10
|
+
* Each yielded chunk is spread into the results array.
|
|
11
|
+
*
|
|
12
|
+
* @template T
|
|
13
|
+
* @param {AsyncGenerator<T[]>} generator - Async generator yielding arrays
|
|
14
|
+
* @returns {Promise<T[]>} Flattened array of all yielded items
|
|
15
|
+
*/
|
|
16
|
+
export const collectAsyncGenerator = async generator => {
|
|
17
|
+
const results = [];
|
|
18
|
+
|
|
19
|
+
let chunkCount = 0;
|
|
20
|
+
|
|
21
|
+
for await (const chunk of generator) {
|
|
22
|
+
chunkCount++;
|
|
23
|
+
|
|
24
|
+
results.push(...chunk);
|
|
25
|
+
|
|
26
|
+
cachingLogger.debug(`Collected chunk ${chunkCount}`, {
|
|
27
|
+
itemsInChunk: chunk.length,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
cachingLogger.debug(`Collection complete`, {
|
|
32
|
+
totalItems: results.length,
|
|
33
|
+
chunks: chunkCount,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
return results;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Creates the cache that backs the generator pipeline. It is the single owner
|
|
41
|
+
* of every piece of caching the orchestrator needs.
|
|
42
|
+
*
|
|
43
|
+
* Generator caching: each generator's pending result, so a generator and its
|
|
44
|
+
* dependencies are only ever scheduled once.
|
|
45
|
+
*
|
|
46
|
+
* Stream caching: collecting an async generator's chunks into a single array
|
|
47
|
+
* exactly once, shared by every consumer.
|
|
48
|
+
*
|
|
49
|
+
* Dependency caching & eviction: tracking how many consumers still need each
|
|
50
|
+
* result and dropping it once the last one has read it, so intermediate results
|
|
51
|
+
* can be garbage collected while later generators keep running.
|
|
52
|
+
*/
|
|
53
|
+
export const createCache = () => {
|
|
54
|
+
/** @type {{ [key: string]: Promise<unknown> | AsyncGenerator }} */
|
|
55
|
+
const results = {};
|
|
56
|
+
|
|
57
|
+
/** @type {Map<string, Promise<unknown[]>>} */
|
|
58
|
+
const collections = new Map();
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Number of consumers that still need to read each result. Once it reaches
|
|
62
|
+
* zero the result is evicted.
|
|
63
|
+
*
|
|
64
|
+
* @type {{ [key: string]: number }}
|
|
65
|
+
*/
|
|
66
|
+
const remaining = {};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Collects an async generator's chunks, ensuring a single collection is
|
|
70
|
+
* shared across all consumers of the same key.
|
|
71
|
+
*
|
|
72
|
+
* @param {string} key - Cache key (resolved generator specifier)
|
|
73
|
+
* @param {AsyncGenerator<unknown[]>} generator - Generator to collect
|
|
74
|
+
* @returns {Promise<unknown[]>} Promise resolving to the collected items
|
|
75
|
+
*/
|
|
76
|
+
const collect = (key, generator) => {
|
|
77
|
+
const hasKey = collections.has(key);
|
|
78
|
+
|
|
79
|
+
if (!hasKey) {
|
|
80
|
+
collections.set(key, collectAsyncGenerator(generator));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
cachingLogger.debug(
|
|
84
|
+
hasKey
|
|
85
|
+
? `Using cached collection for "${key}"`
|
|
86
|
+
: `Starting collection for "${key}"`
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
return collections.get(key);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
/**
|
|
94
|
+
* Whether a generator has already been scheduled.
|
|
95
|
+
*
|
|
96
|
+
* @param {string} name - Cache key (resolved generator specifier)
|
|
97
|
+
* @returns {boolean}
|
|
98
|
+
*/
|
|
99
|
+
has: name => name in results,
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Registers a generator's pending result.
|
|
103
|
+
*
|
|
104
|
+
* @param {string} name - Cache key (resolved generator specifier)
|
|
105
|
+
* @param {Promise<unknown> | AsyncGenerator} result - Pending result
|
|
106
|
+
*/
|
|
107
|
+
store: (name, result) => {
|
|
108
|
+
results[name] = result;
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Computes and records consumer counts across the dependency closure of the
|
|
113
|
+
* requested generators. A generator is consumed once per dependent
|
|
114
|
+
* generator, plus once more when it is a requested target (read by the
|
|
115
|
+
* final collection). This drives eviction without coupling the cache to the
|
|
116
|
+
* generator registry: callers supply how to resolve a dependency.
|
|
117
|
+
*
|
|
118
|
+
* @param {string[]} targets - Requested generator cache keys
|
|
119
|
+
* @param {(name: string) => string | undefined} dependsOn - Resolves a
|
|
120
|
+
* generator's dependency name (if any)
|
|
121
|
+
*/
|
|
122
|
+
populateConsumerCounts: (targets, dependsOn) => {
|
|
123
|
+
const scheduled = new Set();
|
|
124
|
+
const stack = [...targets];
|
|
125
|
+
|
|
126
|
+
// Walk the dependency closure of the requested targets
|
|
127
|
+
while (stack.length > 0) {
|
|
128
|
+
const name = stack.pop();
|
|
129
|
+
|
|
130
|
+
if (scheduled.has(name)) {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
scheduled.add(name);
|
|
135
|
+
|
|
136
|
+
const dependency = dependsOn(name);
|
|
137
|
+
|
|
138
|
+
if (dependency) {
|
|
139
|
+
stack.push(dependency);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Each scheduled generator consumes its dependency exactly once
|
|
144
|
+
for (const name of scheduled) {
|
|
145
|
+
const dependency = dependsOn(name);
|
|
146
|
+
|
|
147
|
+
if (dependency) {
|
|
148
|
+
remaining[dependency] = (remaining[dependency] ?? 0) + 1;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Each requested target is consumed once more by the final collection
|
|
153
|
+
for (const name of targets) {
|
|
154
|
+
remaining[name] = (remaining[name] ?? 0) + 1;
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Reads a generator's result (collecting it first if it streams) and
|
|
160
|
+
* evicts it once every expected consumer has read it.
|
|
161
|
+
*
|
|
162
|
+
* @param {string} name - Cache key (resolved generator specifier) to consume
|
|
163
|
+
* @returns {Promise<unknown>}
|
|
164
|
+
*/
|
|
165
|
+
consume: async name => {
|
|
166
|
+
const result = await results[name];
|
|
167
|
+
|
|
168
|
+
const value = isAsyncIterable(result)
|
|
169
|
+
? await collect(name, result)
|
|
170
|
+
: result;
|
|
171
|
+
|
|
172
|
+
// Evict once the last consumer has retrieved the result
|
|
173
|
+
if (name in remaining && --remaining[name] <= 0) {
|
|
174
|
+
delete results[name];
|
|
175
|
+
collections.delete(name);
|
|
176
|
+
|
|
177
|
+
cachingLogger.debug(`Evicted "${name}" after final consumer`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return value;
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# `ast` Generator
|
|
2
|
+
|
|
3
|
+
The `ast` generator parses Markdown API documentation files into AST (Abstract Syntax Tree) representations, parallelizing the parsing across worker threads for better performance.
|
|
4
|
+
|
|
5
|
+
## Configuring
|
|
6
|
+
|
|
7
|
+
- `input` {string|string[]} Glob pattern(s) for the Markdown files to parse.
|
|
8
|
+
- `ignore` {string|string[]} Glob pattern(s) for files to exclude from parsing.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const STABILITY_INDEX_URL = 'documentation.html#stability-index';
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
4
|
+
import { relative, sep } from 'node:path/posix';
|
|
5
|
+
|
|
6
|
+
import globParent from 'glob-parent';
|
|
7
|
+
import { globSync } from 'tinyglobby';
|
|
8
|
+
import { parse as parseYaml } from 'yaml';
|
|
9
|
+
|
|
10
|
+
import { STABILITY_INDEX_URL } from './constants.mjs';
|
|
11
|
+
import getConfig from '../../utils/configuration/index.mjs';
|
|
12
|
+
import { withExt } from '../../utils/file.mjs';
|
|
13
|
+
import { QUERIES } from '../../utils/queries/index.mjs';
|
|
14
|
+
import { getRemark as remark, getRemarkMdx } from '../../utils/remark.mjs';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Determines whether a file should be parsed as MDX. A `.mdx` extension opts in
|
|
18
|
+
* by default, but an explicit `mdx` boolean in the file's `---` frontmatter
|
|
19
|
+
* always takes precedence (so a `.md` file can opt in, or a `.mdx` file out).
|
|
20
|
+
*
|
|
21
|
+
* @param {string} path - Absolute file path
|
|
22
|
+
* @param {string} content - Raw file contents
|
|
23
|
+
* @returns {boolean}
|
|
24
|
+
*/
|
|
25
|
+
const isMdxFile = (path, content) => {
|
|
26
|
+
const frontmatter = QUERIES.standardYamlFrontmatter.exec(content);
|
|
27
|
+
|
|
28
|
+
if (frontmatter) {
|
|
29
|
+
try {
|
|
30
|
+
const { mdx } = parseYaml(frontmatter[1]) ?? {};
|
|
31
|
+
|
|
32
|
+
if (typeof mdx === 'boolean') {
|
|
33
|
+
return mdx;
|
|
34
|
+
}
|
|
35
|
+
} catch {
|
|
36
|
+
// Node.js core docs use a non-standard frontmatter dialect that may fail
|
|
37
|
+
// to parse here; fall back to the extension check below.
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return path.endsWith('.mdx');
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Process a chunk of markdown files in a worker thread.
|
|
46
|
+
* Loads and parses markdown files into AST representations.
|
|
47
|
+
*
|
|
48
|
+
* @type {import('./types').Generator['processChunk']}
|
|
49
|
+
*/
|
|
50
|
+
export async function processChunk(inputSlice, itemIndices) {
|
|
51
|
+
const filePaths = itemIndices.map(idx => inputSlice[idx]);
|
|
52
|
+
|
|
53
|
+
const results = [];
|
|
54
|
+
|
|
55
|
+
for (const [path, parent] of filePaths) {
|
|
56
|
+
const content = await readFile(path, 'utf-8');
|
|
57
|
+
|
|
58
|
+
const mdx = isMdxFile(path, content);
|
|
59
|
+
|
|
60
|
+
// Stability indexes become links in both modes.
|
|
61
|
+
const withStabilityLinks = content.replace(
|
|
62
|
+
QUERIES.stabilityIndexPrefix,
|
|
63
|
+
match => `[${match}](${STABILITY_INDEX_URL})`
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
// The path is the relative path minus the extension
|
|
67
|
+
const relativePath = sep + withExt(relative(parent, path));
|
|
68
|
+
|
|
69
|
+
let tree;
|
|
70
|
+
|
|
71
|
+
if (mdx) {
|
|
72
|
+
// A `<!-- -->` HTML comment is invalid MDX, so the frontmatter can't be
|
|
73
|
+
// rewritten in-source. Strip it, parse as MDX, then re-attach it as the
|
|
74
|
+
// YAML `html` node the metadata stage expects.
|
|
75
|
+
const frontmatter = QUERIES.standardYamlFrontmatter.exec(content);
|
|
76
|
+
const source = withStabilityLinks.replace(
|
|
77
|
+
QUERIES.standardYamlFrontmatter,
|
|
78
|
+
''
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
tree = getRemarkMdx().parse(source);
|
|
82
|
+
|
|
83
|
+
if (frontmatter) {
|
|
84
|
+
tree.children.unshift({
|
|
85
|
+
type: 'html',
|
|
86
|
+
value: `<!-- YAML\n${frontmatter[1]}\n-->`,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
const value = withStabilityLinks.replace(
|
|
91
|
+
QUERIES.standardYamlFrontmatter,
|
|
92
|
+
(_, yaml) => `<!-- YAML\n${yaml}\n-->`
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
tree = remark().parse(value);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
results.push({ tree, path: relativePath, mdx });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return results;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Generates AST trees from markdown input files.
|
|
106
|
+
*
|
|
107
|
+
* @type {import('./types').Generator['generate']}
|
|
108
|
+
*/
|
|
109
|
+
export async function* generate(_, worker) {
|
|
110
|
+
const { ast: config } = getConfig();
|
|
111
|
+
|
|
112
|
+
const files = config.input.flatMap(input => {
|
|
113
|
+
const parent = globParent(input);
|
|
114
|
+
|
|
115
|
+
return globSync(input, { ignore: config.ignore }).map(child => [
|
|
116
|
+
child,
|
|
117
|
+
parent,
|
|
118
|
+
]);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Parse markdown files in parallel using worker threads
|
|
122
|
+
for await (const chunkResult of worker.stream(files)) {
|
|
123
|
+
yield chunkResult;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import { generate, processChunk } from './generate.mjs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* This generator parses Markdown API doc files into AST trees.
|
|
7
|
+
* It parallelizes the parsing across worker threads for better performance.
|
|
8
|
+
*
|
|
9
|
+
* @type {import('./types').Generator}
|
|
10
|
+
*/
|
|
11
|
+
export default {
|
|
12
|
+
name: 'ast',
|
|
13
|
+
|
|
14
|
+
description: 'Parses Markdown API doc files into AST trees',
|
|
15
|
+
|
|
16
|
+
hasParallelProcessor: true,
|
|
17
|
+
|
|
18
|
+
generate,
|
|
19
|
+
processChunk,
|
|
20
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# `ast-js` Generator
|
|
2
|
+
|
|
3
|
+
The `ast-js` generator parses JavaScript source files into AST (Abstract Syntax Tree) representations using the Acorn parser.
|
|
4
|
+
|
|
5
|
+
## Configuring
|
|
6
|
+
|
|
7
|
+
- `input` {string|string[]} Glob pattern(s) for the JavaScript files to parse.
|
|
8
|
+
- `ignore` {string|string[]} Glob pattern(s) for files to exclude from parsing.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
4
|
+
import { extname } from 'node:path';
|
|
5
|
+
|
|
6
|
+
import { parse } from 'acorn';
|
|
7
|
+
import { globSync } from 'tinyglobby';
|
|
8
|
+
|
|
9
|
+
import getConfig from '../../utils/configuration/index.mjs';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Process a chunk of JavaScript files in a worker thread.
|
|
13
|
+
* Parses JS source files into AST representations.
|
|
14
|
+
*
|
|
15
|
+
* @type {import('./types').Generator['processChunk']}
|
|
16
|
+
*/
|
|
17
|
+
export async function processChunk(inputSlice, itemIndices) {
|
|
18
|
+
const filePaths = itemIndices.map(idx => inputSlice[idx]);
|
|
19
|
+
|
|
20
|
+
const results = [];
|
|
21
|
+
|
|
22
|
+
for (const path of filePaths) {
|
|
23
|
+
const value = await readFile(path, 'utf-8');
|
|
24
|
+
|
|
25
|
+
const parsed = parse(value, {
|
|
26
|
+
allowReturnOutsideFunction: true,
|
|
27
|
+
ecmaVersion: 'latest',
|
|
28
|
+
locations: true,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
parsed.path = path;
|
|
32
|
+
|
|
33
|
+
results.push(parsed);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return results;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Generates a JavaScript AST from the input files.
|
|
41
|
+
*
|
|
42
|
+
* @type {import('./types').Generator['generate']}
|
|
43
|
+
*/
|
|
44
|
+
export async function* generate(_, worker) {
|
|
45
|
+
const config = getConfig('ast-js');
|
|
46
|
+
|
|
47
|
+
const files = globSync(config.input, { ignore: config.ignore }).filter(
|
|
48
|
+
p => extname(p) === '.js'
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// Parse the Javascript sources into ASTs in parallel using worker threads
|
|
52
|
+
// source is both the items list and the fullInput since we use sliceInput
|
|
53
|
+
for await (const chunkResult of worker.stream(files)) {
|
|
54
|
+
yield chunkResult;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import { generate, processChunk } from './generate.mjs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* This generator parses Javascript sources passed into the generator's input
|
|
7
|
+
* field. This is separate from the Markdown parsing step since it's not as
|
|
8
|
+
* commonly used and can take up a significant amount of memory.
|
|
9
|
+
*
|
|
10
|
+
* Putting this with the rest of the generators allows it to be lazily loaded
|
|
11
|
+
* so we're only parsing the Javascript sources when we need to.
|
|
12
|
+
*
|
|
13
|
+
* @type {import('./types').Generator}
|
|
14
|
+
*/
|
|
15
|
+
export default {
|
|
16
|
+
name: 'ast-js',
|
|
17
|
+
|
|
18
|
+
description: 'Parses Javascript source files passed into the input.',
|
|
19
|
+
|
|
20
|
+
hasParallelProcessor: true,
|
|
21
|
+
|
|
22
|
+
generate,
|
|
23
|
+
processChunk,
|
|
24
|
+
};
|