@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
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import { SKIP } from 'unist-util-visit';
|
|
4
|
+
|
|
5
|
+
import { transformUnixManualToLink } from './transformers.mjs';
|
|
6
|
+
import { extractYamlContent, parseYAMLIntoMetadata } from './yaml.mjs';
|
|
7
|
+
import { QUERIES } from '../../../utils/queries/index.mjs';
|
|
8
|
+
import { getRemark as remark } from '../../../utils/remark.mjs';
|
|
9
|
+
import { transformNodesToString } from '../../../utils/unist.mjs';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Updates a Markdown link into a HTML link for API docs
|
|
13
|
+
* @param {import('@types/mdast').Link} node A Markdown link node
|
|
14
|
+
*/
|
|
15
|
+
export const visitMarkdownLink = node => {
|
|
16
|
+
node.url = node.url.replace(
|
|
17
|
+
QUERIES.markdownUrl,
|
|
18
|
+
(_, filename, hash = '') => `${filename}.html${hash}`
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
return [SKIP];
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Updates a reference
|
|
26
|
+
* @param {import('@types/mdast').Text} node The current node
|
|
27
|
+
* @param {import('@types/mdast').Parent} parent The parent node
|
|
28
|
+
* @param {string|RegExp} query The search query
|
|
29
|
+
* @param {Function} transformer The function to transform the reference
|
|
30
|
+
*/
|
|
31
|
+
const updateReferences = (query, transformer, node, parent) => {
|
|
32
|
+
const replacedTypes = node.value
|
|
33
|
+
.replace(query, transformer)
|
|
34
|
+
// Remark doesn't handle leading / trailing spaces, so replace them with
|
|
35
|
+
// HTML entities.
|
|
36
|
+
.replace(/^\s/, ' ')
|
|
37
|
+
.replace(/\s$/, ' ');
|
|
38
|
+
|
|
39
|
+
// This changes the type into a link by splitting it up into several nodes,
|
|
40
|
+
// and adding those nodes to the parent.
|
|
41
|
+
const {
|
|
42
|
+
children: [newNode],
|
|
43
|
+
} = remark().parse(replacedTypes);
|
|
44
|
+
|
|
45
|
+
// Find the index of the original node in the parent
|
|
46
|
+
const index = parent.children.indexOf(node);
|
|
47
|
+
|
|
48
|
+
// Replace the original node with the new node(s)
|
|
49
|
+
parent.children.splice(index, 1, ...newNode.children);
|
|
50
|
+
|
|
51
|
+
return [SKIP];
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Updates unix manual references to be markdown links
|
|
56
|
+
* @param {import('@types/mdast').Text} node The text node
|
|
57
|
+
* @param {import('@types/mdast').Parent} parent The parent node
|
|
58
|
+
*/
|
|
59
|
+
export const visitTextWithUnixManualNode = (node, parent) =>
|
|
60
|
+
updateReferences(
|
|
61
|
+
QUERIES.unixManualPage,
|
|
62
|
+
transformUnixManualToLink,
|
|
63
|
+
node,
|
|
64
|
+
parent
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Updates a Markdown Link Reference into an actual Link to the Definition
|
|
69
|
+
* @param {import('@types/mdast').LinkReference} node A link reference node
|
|
70
|
+
* @param {Array<import('@types/mdast').Definition>} definitions The Definitions of the API Doc
|
|
71
|
+
*/
|
|
72
|
+
export const visitLinkReference = (node, definitions) => {
|
|
73
|
+
const definition = definitions.find(
|
|
74
|
+
({ identifier }) => identifier === node.identifier
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
node.type = 'link';
|
|
78
|
+
node.url = definition.url;
|
|
79
|
+
|
|
80
|
+
return [SKIP];
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Parses a Stability Index Entry and updates the current Metadata
|
|
85
|
+
* @param {import('@types/mdast').Blockquote} node The blockquote node
|
|
86
|
+
* @param {import('../types').MetadataEntry} metadata The metadata object to update
|
|
87
|
+
*/
|
|
88
|
+
export const visitStability = (node, metadata) => {
|
|
89
|
+
// `node` is a `blockquote` node, and the first child will always be
|
|
90
|
+
// a `paragraph` node, so we can safely access the children of the first child
|
|
91
|
+
// which we use as the prefix and description of the Stability Index
|
|
92
|
+
const stabilityPrefix = transformNodesToString(node.children[0].children);
|
|
93
|
+
|
|
94
|
+
// Attempts to grab the Stability index and description from the prefix
|
|
95
|
+
const matches = QUERIES.stabilityIndex.exec(stabilityPrefix);
|
|
96
|
+
|
|
97
|
+
// Ensures that the matches are valid and that we have at least 3 entries
|
|
98
|
+
if (matches && matches.length === 3) {
|
|
99
|
+
// Updates the `data` property of the Stability Index node
|
|
100
|
+
// so that the original node data can also be inferred
|
|
101
|
+
node.data = {
|
|
102
|
+
// The 2nd match should be the group that matches the Stability Index
|
|
103
|
+
index: matches[1],
|
|
104
|
+
// The 3rd match should be the group containing all the remaining text
|
|
105
|
+
// which is used as a description (we trim it to an one liner)
|
|
106
|
+
description: matches[2].replace(/\n/g, ' ').trim(),
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// Adds the Stability Index metadata to the current Metadata entry
|
|
110
|
+
if (metadata) {
|
|
111
|
+
metadata.stability = node;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return [SKIP];
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Helper function to parse YAML from a node
|
|
120
|
+
* @param {import('@types/mdast').Blockquote} node The blockquote node
|
|
121
|
+
* @param {Object} metadata The metadata object to update
|
|
122
|
+
*/
|
|
123
|
+
export const visitYAML = (node, metadata) => {
|
|
124
|
+
const yaml = extractYamlContent(node);
|
|
125
|
+
const meta = parseYAMLIntoMetadata(yaml);
|
|
126
|
+
|
|
127
|
+
Object.entries(meta).forEach(([key, value]) => {
|
|
128
|
+
if (Array.isArray(metadata[key])) {
|
|
129
|
+
return metadata[key].push(...value);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
metadata[key] = value;
|
|
133
|
+
});
|
|
134
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import yaml from 'yaml';
|
|
4
|
+
|
|
5
|
+
import { QUERIES } from '../../../utils/queries/index.mjs';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Extracts raw YAML content from a node
|
|
9
|
+
*
|
|
10
|
+
* @param {import('mdast').Node} node A HTML node containing the YAML content
|
|
11
|
+
* @returns {string} The extracted raw YAML content
|
|
12
|
+
*/
|
|
13
|
+
export const extractYamlContent = node => {
|
|
14
|
+
return node.value.replace(
|
|
15
|
+
QUERIES.yamlInnerContent,
|
|
16
|
+
// Either capture a YAML multinline block, or a simple single-line YAML block
|
|
17
|
+
(_, simple, yaml) => simple || yaml
|
|
18
|
+
);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Normalizes YAML syntax by fixing some non-cool formatted properties of the
|
|
23
|
+
* docs schema
|
|
24
|
+
*
|
|
25
|
+
* @param {string} yamlContent The raw YAML content to normalize
|
|
26
|
+
* @returns {string} The normalized YAML content
|
|
27
|
+
*/
|
|
28
|
+
export const normalizeYamlSyntax = yamlContent => {
|
|
29
|
+
return yamlContent
|
|
30
|
+
.replace('introduced_in=', 'introduced_in: ')
|
|
31
|
+
.replace('source_link=', 'source_link: ')
|
|
32
|
+
.replace('type=', 'type: ')
|
|
33
|
+
.replace('name=', 'name: ')
|
|
34
|
+
.replace('llm_description=', 'llm_description: ')
|
|
35
|
+
.replace(/^[\r\n]+|[\r\n]+$/g, ''); // Remove initial and final line breaks
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Parses Markdown YAML source into a JavaScript object containing all the metadata
|
|
40
|
+
* (this is forwarded to the parser so it knows what to do with said metadata)
|
|
41
|
+
*
|
|
42
|
+
* @param {string} yamlString The YAML string to be parsed
|
|
43
|
+
* @returns {import('../types').YAMLProperties} The parsed YAML metadata
|
|
44
|
+
*/
|
|
45
|
+
export const parseYAMLIntoMetadata = yamlString => {
|
|
46
|
+
const normalizedYaml = normalizeYamlSyntax(yamlString);
|
|
47
|
+
|
|
48
|
+
// Ensures that the parsed YAML is an object, because even if it is not
|
|
49
|
+
// i.e. a plain string or an array, it will simply not result into anything
|
|
50
|
+
let parsedYaml = yaml.parse(normalizedYaml);
|
|
51
|
+
// Ensure that only Objects get parsed on Object.keys(), since some `<!--`
|
|
52
|
+
// comments, might be just plain strings and not even a valid YAML metadata
|
|
53
|
+
if (typeof parsedYaml === 'string') {
|
|
54
|
+
return { tags: [parsedYaml] };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return parsedYaml;
|
|
58
|
+
};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import type { publicGenerators, allGenerators } from './index.mjs';
|
|
2
|
+
|
|
3
|
+
declare global {
|
|
4
|
+
// Public generators exposed to the CLI
|
|
5
|
+
export type AvailableGenerators = typeof publicGenerators;
|
|
6
|
+
|
|
7
|
+
// All generators including internal ones (metadata, jsx-ast, ast-js)
|
|
8
|
+
export type AllGenerators = typeof allGenerators;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* ParallelWorker interface for distributing work across Node.js worker threads.
|
|
12
|
+
* Streams results as chunks complete, enabling pipeline parallelism.
|
|
13
|
+
*/
|
|
14
|
+
export interface ParallelWorker {
|
|
15
|
+
/**
|
|
16
|
+
* Processes items in parallel across worker threads and yields results
|
|
17
|
+
* as each chunk completes. Enables downstream processing to begin
|
|
18
|
+
* while upstream chunks are still being processed.
|
|
19
|
+
*
|
|
20
|
+
* @param items - Items to process (determines chunk distribution)
|
|
21
|
+
* @param opts - Additional options to pass to workers
|
|
22
|
+
* @yields Each chunk's results as they complete
|
|
23
|
+
*/
|
|
24
|
+
stream<T, R>(
|
|
25
|
+
items: T[],
|
|
26
|
+
opts?: Record<string, unknown>
|
|
27
|
+
): AsyncGenerator<R[], void, unknown>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ParallelTaskOptions {
|
|
31
|
+
/** Resolved import specifier the worker loads the generator from */
|
|
32
|
+
generatorSpecifier: string;
|
|
33
|
+
input: unknown[];
|
|
34
|
+
itemIndices: number[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Type for the generate function of a generator
|
|
39
|
+
* @template I - Input type
|
|
40
|
+
* @template O - Output type (can be AsyncGenerator or Promise)
|
|
41
|
+
*/
|
|
42
|
+
export type Generate<I, O> = (
|
|
43
|
+
input: I,
|
|
44
|
+
worker: ParallelWorker
|
|
45
|
+
) => O extends AsyncGenerator ? O : Promise<O>;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Type for the optional processChunk function of a generator
|
|
49
|
+
* @template I - Input type
|
|
50
|
+
* @template O - Output type
|
|
51
|
+
* @template D - Dependencies type
|
|
52
|
+
*/
|
|
53
|
+
export type ProcessChunk<I, O, D = {}> = (
|
|
54
|
+
slicedInput: I[],
|
|
55
|
+
itemIndices: number[],
|
|
56
|
+
dependencies: D
|
|
57
|
+
) => Promise<O>;
|
|
58
|
+
|
|
59
|
+
export type GeneratorMetadata<
|
|
60
|
+
C extends any,
|
|
61
|
+
G extends Generate<any, any>,
|
|
62
|
+
P extends ProcessChunk<any, any, any> | undefined = undefined,
|
|
63
|
+
> = {
|
|
64
|
+
readonly defaultConfiguration: C;
|
|
65
|
+
|
|
66
|
+
// The name of the Generator. Must match the Key in AllGenerators
|
|
67
|
+
name: keyof AllGenerators;
|
|
68
|
+
|
|
69
|
+
description: string;
|
|
70
|
+
|
|
71
|
+
hasParallelProcessor?: boolean;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The immediate generator that this generator depends on.
|
|
75
|
+
* For example, the `html` generator depends on the `react` generator.
|
|
76
|
+
*
|
|
77
|
+
* If a given generator has no "before" generator, it will be considered a top-level
|
|
78
|
+
* generator, and run in parallel.
|
|
79
|
+
*
|
|
80
|
+
* Assume you pass to the `createGenerator`: ['json', 'html'] as the generators,
|
|
81
|
+
* this means both the 'json' and the 'html' generators will be executed and generate their
|
|
82
|
+
* own outputs in parallel. If the 'html' generator depends on the 'react' generator, then
|
|
83
|
+
* the 'react' generator will be executed first, then the 'html' generator.
|
|
84
|
+
*
|
|
85
|
+
* But both 'json' and 'html' generators will be executed in parallel.
|
|
86
|
+
*
|
|
87
|
+
* If you pass `createGenerator` with ['react', 'html'], the 'react' generator will be executed first,
|
|
88
|
+
* as it is a top level generator and then the 'html' generator would be executed after the 'react' generator.
|
|
89
|
+
*
|
|
90
|
+
* The 'ast' generator is the top-level parser for markdown files. It has no dependencies.
|
|
91
|
+
*
|
|
92
|
+
* The `ast-js` generator is the top-level parser for JavaScript files. It
|
|
93
|
+
* passes the ASTs for any JavaScript files given in the input.
|
|
94
|
+
*
|
|
95
|
+
* Declared as an import specifier (e.g. `@doc-kit/core/metadata`),
|
|
96
|
+
* so a dependency may live in any installed package.
|
|
97
|
+
*/
|
|
98
|
+
dependsOn: string | undefined;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Generators are abstract and the different generators have different sort of inputs and outputs.
|
|
102
|
+
* For example, a MDX generator would take the raw AST and output MDX with React Components;
|
|
103
|
+
* Whereas a JSON generator would take the raw AST and output JSON;
|
|
104
|
+
* Then a React generator could receive either the raw AST or the MDX output and output React Components.
|
|
105
|
+
* (depending if they support such I/O)
|
|
106
|
+
*
|
|
107
|
+
* Hence you can combine different generators to achieve different outputs.
|
|
108
|
+
*/
|
|
109
|
+
generate: G;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Optional method for chunk-level parallelization using real worker threads.
|
|
113
|
+
* Called by chunk-worker.mjs when processing items in parallel.
|
|
114
|
+
*
|
|
115
|
+
* Generators that implement this method can have their work distributed
|
|
116
|
+
* across multiple worker threads for true parallel processing.
|
|
117
|
+
*
|
|
118
|
+
* Input is automatically sliced to only include items at the specified indices,
|
|
119
|
+
* reducing serialization overhead. The itemIndices are remapped to 0-based
|
|
120
|
+
* indices into the sliced array.
|
|
121
|
+
*
|
|
122
|
+
* @param slicedInput - Sliced input containing only items for this chunk
|
|
123
|
+
* @param itemIndices - Array of 0-based indices into slicedInput
|
|
124
|
+
* @param dependencies - Generator options (without worker, which isn't serializable)
|
|
125
|
+
* @returns Array of results for the processed items
|
|
126
|
+
*/
|
|
127
|
+
processChunk?: P;
|
|
128
|
+
};
|
|
129
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import { createCache } from './caching.mjs';
|
|
4
|
+
import {
|
|
5
|
+
loadGenerators,
|
|
6
|
+
resolveGeneratorSpecifier,
|
|
7
|
+
} from './generators/loader.mjs';
|
|
8
|
+
import logger from './logger/index.mjs';
|
|
9
|
+
import createWorkerPool from './threading/index.mjs';
|
|
10
|
+
import createParallelWorker from './threading/parallel.mjs';
|
|
11
|
+
import { isAsyncIterable } from './utils/misc.mjs';
|
|
12
|
+
|
|
13
|
+
const generatorsLogger = logger.child('generators');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Creates a generator orchestration system that manages the execution of
|
|
17
|
+
* documentation generators in dependency order, with support for parallel
|
|
18
|
+
* processing and streaming results.
|
|
19
|
+
*/
|
|
20
|
+
const createGenerator = () => {
|
|
21
|
+
// Owns all caching: generator results, stream collection, and the
|
|
22
|
+
// consumer-count bookkeeping that drives eviction.
|
|
23
|
+
const cache = createCache();
|
|
24
|
+
|
|
25
|
+
/** @type {import('piscina').Piscina} */
|
|
26
|
+
let pool;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Gets the collected input from a dependency generator.
|
|
30
|
+
*
|
|
31
|
+
* @param {string | undefined} dependsOn - Resolved dependency specifier
|
|
32
|
+
* @returns {Promise<unknown>}
|
|
33
|
+
*/
|
|
34
|
+
const getDependencyInput = async dependsOn => {
|
|
35
|
+
if (!dependsOn) {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return cache.consume(dependsOn);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Schedules a generator and its dependencies for execution.
|
|
44
|
+
*
|
|
45
|
+
* @param {string} specifier - Resolved generator specifier to schedule
|
|
46
|
+
* @param {Map<string, GeneratorMetadata>} generators - Loaded generators
|
|
47
|
+
* @param {import('./utils/configuration/types').Configuration} configuration - Runtime options
|
|
48
|
+
*/
|
|
49
|
+
const scheduleGenerator = (specifier, generators, configuration) => {
|
|
50
|
+
if (cache.has(specifier)) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const generator = generators.get(specifier);
|
|
55
|
+
const { name, generate, hasParallelProcessor } = generator;
|
|
56
|
+
|
|
57
|
+
const dependsOn =
|
|
58
|
+
generator.dependsOn && resolveGeneratorSpecifier(generator.dependsOn);
|
|
59
|
+
|
|
60
|
+
// Schedule dependency first
|
|
61
|
+
if (dependsOn && !cache.has(dependsOn)) {
|
|
62
|
+
scheduleGenerator(dependsOn, generators, configuration);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
generatorsLogger.debug(`Scheduling "${name}"`, {
|
|
66
|
+
dependsOn: dependsOn || 'none',
|
|
67
|
+
streaming: hasParallelProcessor,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Schedule the generator
|
|
71
|
+
cache.store(
|
|
72
|
+
specifier,
|
|
73
|
+
(async () => {
|
|
74
|
+
const dependencyInput = await getDependencyInput(dependsOn);
|
|
75
|
+
|
|
76
|
+
generatorsLogger.debug(`Starting "${name}"`);
|
|
77
|
+
|
|
78
|
+
// Create parallel worker for streaming generators
|
|
79
|
+
const worker = hasParallelProcessor
|
|
80
|
+
? createParallelWorker(specifier, generator, pool, configuration)
|
|
81
|
+
: Promise.resolve(null);
|
|
82
|
+
|
|
83
|
+
const result = await generate(dependencyInput, await worker);
|
|
84
|
+
|
|
85
|
+
// For streaming generators, "Completed" is logged when the cache
|
|
86
|
+
// finishes collecting, not here when the generator returns
|
|
87
|
+
if (!isAsyncIterable(result)) {
|
|
88
|
+
generatorsLogger.debug(`Completed "${name}"`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return result;
|
|
92
|
+
})()
|
|
93
|
+
);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Runs all requested generators with their dependencies.
|
|
98
|
+
*
|
|
99
|
+
* @param {import('./utils/configuration/types').Configuration} configuration - Runtime options
|
|
100
|
+
* @returns {Promise<unknown[]>} Results of all requested generators
|
|
101
|
+
*/
|
|
102
|
+
const runGenerators = async configuration => {
|
|
103
|
+
const { target, threads } = configuration;
|
|
104
|
+
|
|
105
|
+
// Resolve shorthand names and load the full dependency closure up front,
|
|
106
|
+
// so scheduling below is fully synchronous.
|
|
107
|
+
const targets = target.map(resolveGeneratorSpecifier);
|
|
108
|
+
const generators = await loadGenerators(targets);
|
|
109
|
+
|
|
110
|
+
generatorsLogger.debug(`Starting pipeline`, {
|
|
111
|
+
generators: targets.join(', '),
|
|
112
|
+
threads,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Compute consumer counts up front so dependencies can be evicted as soon
|
|
116
|
+
// 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
|
+
});
|
|
122
|
+
|
|
123
|
+
// Create worker pool
|
|
124
|
+
pool = createWorkerPool(threads);
|
|
125
|
+
|
|
126
|
+
// Schedule all generators
|
|
127
|
+
for (const specifier of targets) {
|
|
128
|
+
scheduleGenerator(specifier, generators, configuration);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Start all collections in parallel (don't await sequentially). Consuming
|
|
132
|
+
// through the shared path lets the final read also trigger eviction.
|
|
133
|
+
const results = await Promise.all(
|
|
134
|
+
targets.map(specifier => cache.consume(specifier))
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
await pool.destroy();
|
|
138
|
+
|
|
139
|
+
return results;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
return { runGenerators };
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
export default createGenerator;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Numeric log level definitions.
|
|
5
|
+
*/
|
|
6
|
+
export const LogLevel = {
|
|
7
|
+
debug: 10,
|
|
8
|
+
info: 20,
|
|
9
|
+
warn: 30,
|
|
10
|
+
error: 40,
|
|
11
|
+
fatal: 50,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Maps log level numbers to their string tags.
|
|
16
|
+
*/
|
|
17
|
+
export const levelTags = {
|
|
18
|
+
[LogLevel.debug]: 'DEBUG',
|
|
19
|
+
[LogLevel.info]: 'INFO',
|
|
20
|
+
[LogLevel.warn]: 'WARN',
|
|
21
|
+
[LogLevel.error]: 'ERROR',
|
|
22
|
+
[LogLevel.fatal]: 'FATAL',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Maps log level numbers to CLI color names.
|
|
27
|
+
*/
|
|
28
|
+
export const levelToColorMap = {
|
|
29
|
+
[LogLevel.debug]: 'blue',
|
|
30
|
+
[LogLevel.info]: 'green',
|
|
31
|
+
[LogLevel.warn]: 'yellow',
|
|
32
|
+
[LogLevel.error]: 'magenta',
|
|
33
|
+
[LogLevel.fatal]: 'red',
|
|
34
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import { createLogger } from './logger.mjs';
|
|
4
|
+
import { transports } from './transports/index.mjs';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {ReturnType<typeof createLogger>} LoggerInstance
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Creates a new logger instance with the specified transport.
|
|
12
|
+
*
|
|
13
|
+
* @param {string} [transportName='console'] - Name of the transport to use.
|
|
14
|
+
* @returns {LoggerInstance}
|
|
15
|
+
*/
|
|
16
|
+
export const Logger = (transportName = 'console') => {
|
|
17
|
+
const transport = transports[transportName];
|
|
18
|
+
|
|
19
|
+
if (!transport) {
|
|
20
|
+
throw new Error(`Transport '${transportName}' not found.`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return createLogger(transport);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Default logger instance using console transport
|
|
27
|
+
export default Logger();
|