@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.
Files changed (75) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/LICENSE +21 -0
  3. package/README.md +36 -0
  4. package/package.json +88 -0
  5. package/shiki.config.mjs +50 -0
  6. package/src/caching.mjs +183 -0
  7. package/src/generators/ast/README.md +8 -0
  8. package/src/generators/ast/constants.mjs +1 -0
  9. package/src/generators/ast/generate.mjs +125 -0
  10. package/src/generators/ast/index.mjs +20 -0
  11. package/src/generators/ast/types.d.ts +7 -0
  12. package/src/generators/ast-js/README.md +8 -0
  13. package/src/generators/ast-js/generate.mjs +56 -0
  14. package/src/generators/ast-js/index.mjs +24 -0
  15. package/src/generators/ast-js/types.d.ts +5 -0
  16. package/src/generators/index.mjs +48 -0
  17. package/src/generators/json-simple/README.md +9 -0
  18. package/src/generators/json-simple/generate.mjs +33 -0
  19. package/src/generators/json-simple/index.mjs +23 -0
  20. package/src/generators/json-simple/types.d.ts +6 -0
  21. package/src/generators/loader.mjs +96 -0
  22. package/src/generators/metadata/README.md +8 -0
  23. package/src/generators/metadata/constants.mjs +68 -0
  24. package/src/generators/metadata/generate.mjs +40 -0
  25. package/src/generators/metadata/index.mjs +21 -0
  26. package/src/generators/metadata/maps/builtin.json +16 -0
  27. package/src/generators/metadata/maps/mdn.json +1114 -0
  28. package/src/generators/metadata/types.d.ts +145 -0
  29. package/src/generators/metadata/utils/parse.mjs +160 -0
  30. package/src/generators/metadata/utils/resolveTypes.mjs +204 -0
  31. package/src/generators/metadata/utils/slugger.mjs +39 -0
  32. package/src/generators/metadata/utils/transformers.mjs +109 -0
  33. package/src/generators/metadata/utils/visitors.mjs +134 -0
  34. package/src/generators/metadata/utils/yaml.mjs +58 -0
  35. package/src/generators/types.d.ts +129 -0
  36. package/src/generators.mjs +145 -0
  37. package/src/logger/constants.mjs +34 -0
  38. package/src/logger/index.mjs +27 -0
  39. package/src/logger/logger.mjs +177 -0
  40. package/src/logger/transports/console.mjs +54 -0
  41. package/src/logger/transports/github.mjs +42 -0
  42. package/src/logger/transports/index.mjs +11 -0
  43. package/src/logger/types.d.ts +29 -0
  44. package/src/logger/utils/colors.mjs +18 -0
  45. package/src/logger/utils/time.mjs +21 -0
  46. package/src/parsers/markdown.mjs +49 -0
  47. package/src/parsers/types.d.ts +7 -0
  48. package/src/threading/chunk-worker.mjs +23 -0
  49. package/src/threading/index.mjs +27 -0
  50. package/src/threading/parallel.mjs +140 -0
  51. package/src/utils/array.mjs +7 -0
  52. package/src/utils/configuration/index.mjs +263 -0
  53. package/src/utils/configuration/templates.mjs +21 -0
  54. package/src/utils/configuration/types.d.ts +64 -0
  55. package/src/utils/file.mjs +20 -0
  56. package/src/utils/generators.mjs +71 -0
  57. package/src/utils/highlighter.mjs +198 -0
  58. package/src/utils/html-minifier.mjs +12 -0
  59. package/src/utils/loaders.mjs +48 -0
  60. package/src/utils/misc.mjs +72 -0
  61. package/src/utils/queries/constants.mjs +3 -0
  62. package/src/utils/queries/index.mjs +93 -0
  63. package/src/utils/queries/utils.mjs +45 -0
  64. package/src/utils/remark.mjs +97 -0
  65. package/src/utils/signature/constants.mjs +12 -0
  66. package/src/utils/signature/parseList.mjs +133 -0
  67. package/src/utils/signature/parseSignature.mjs +208 -0
  68. package/src/utils/signature/types.d.ts +66 -0
  69. package/src/utils/swc.mjs +34 -0
  70. package/src/utils/type-annotations/hast.mjs +115 -0
  71. package/src/utils/type-annotations/mdast.mjs +75 -0
  72. package/src/utils/type-annotations/remark.mjs +24 -0
  73. package/src/utils/type-annotations/syntax.mjs +188 -0
  74. package/src/utils/unist.mjs +75 -0
  75. package/src/utils/url.mjs +27 -0
@@ -0,0 +1,177 @@
1
+ 'use strict';
2
+
3
+ import { LogLevel } from './constants.mjs';
4
+
5
+ /**
6
+ * @typedef {import('./types').Metadata} Metadata
7
+ * @typedef {import('./types').LogMessage} LogMessage
8
+ */
9
+
10
+ /**
11
+ * Creates a logger instance with the specified transport, log level and an
12
+ * optional module name. Child loggers share the parent's log level.
13
+ *
14
+ * @param {import('./types').Transport} transport - Function to handle log output.
15
+ * @param {number} [loggerLevel] - Minimum log level to output.
16
+ * @param {string} [module] - Optional module name for the logger.
17
+ */
18
+ export const createLogger = (
19
+ transport,
20
+ loggerLevel = LogLevel.info,
21
+ module
22
+ ) => {
23
+ /** @type {number} */
24
+ let currentLevel = loggerLevel;
25
+
26
+ /** @type {Set<ReturnType<typeof createLogger>>} */
27
+ const children = new Set();
28
+
29
+ /**
30
+ * Checks if the given log level should be logged based on the current logger
31
+ * level.
32
+ *
33
+ * @param {number} level - Log level to check.
34
+ * @returns {boolean}
35
+ */
36
+ const shouldLog = level => level >= currentLevel;
37
+
38
+ /**
39
+ * Logs a message at the given level with optional metadata.
40
+ *
41
+ * @param {number} level - Log level for the message.
42
+ * @param {LogMessage} message - Message to log.
43
+ * @param {Metadata} metadata - Additional metadata
44
+ * @returns {void}
45
+ */
46
+ const log = (level, message, metadata = {}) => {
47
+ if (!shouldLog(level)) {
48
+ return;
49
+ }
50
+
51
+ if (Array.isArray(message)) {
52
+ return message.forEach(msg => log(level, msg, metadata));
53
+ }
54
+
55
+ const timestamp = Date.now();
56
+
57
+ // Extract message string from Error object or use message as-is
58
+ let msg;
59
+
60
+ if (message instanceof Error) {
61
+ msg = message.message;
62
+
63
+ metadata.stack = message.stack;
64
+ } else {
65
+ msg = message;
66
+ }
67
+
68
+ transport({
69
+ level,
70
+ message: msg,
71
+ timestamp,
72
+ metadata,
73
+ module,
74
+ });
75
+ };
76
+
77
+ /**
78
+ * Logs an info message.
79
+ *
80
+ * @param {LogMessage} message - Info message to log.
81
+ * @param {Metadata} metadata - Additional metadata
82
+ * @returns {void}
83
+ */
84
+ const info = (message, metadata = {}) =>
85
+ log(LogLevel.info, message, metadata);
86
+
87
+ /**
88
+ * Logs a warning message.
89
+ *
90
+ * @param {LogMessage} message - Warning message to log.
91
+ * @param {Metadata} metadata - Additional metadata
92
+ * @returns {void}
93
+ */
94
+ const warn = (message, metadata = {}) =>
95
+ log(LogLevel.warn, message, metadata);
96
+
97
+ /**
98
+ * Logs an error message or Error object.
99
+ *
100
+ * @param {LogMessage} message - Error message or Error object to log.
101
+ * @param {Metadata} metadata - Additional metadata
102
+ * @returns {void}
103
+ */
104
+ const error = (message, metadata = {}) =>
105
+ log(LogLevel.error, message, metadata);
106
+
107
+ /**
108
+ * Logs a fatal error message or Error object.
109
+ *
110
+ * @param {LogMessage} message - Fatal error message or Error object to log.
111
+ * @param {Metadata} metadata - Additional metadata
112
+ * @returns {void}
113
+ */
114
+ const fatal = (message, metadata = {}) =>
115
+ log(LogLevel.fatal, message, metadata);
116
+
117
+ /**
118
+ * Logs a debug message.
119
+ *
120
+ * @param {LogMessage} message - Debug message to log.
121
+ * @param {Metadata} metadata - Additional metadata
122
+ * @returns {void}
123
+ */
124
+ const debug = (message, metadata = {}) =>
125
+ log(LogLevel.debug, message, metadata);
126
+
127
+ /**
128
+ * Creates a child logger for a specific module. Child loggers share the
129
+ * parent's log level.
130
+ *
131
+ * @param {string} childModule - Module name for the child logger.
132
+ * @returns {ReturnType<typeof createLogger>}
133
+ */
134
+ const child = childModule => {
135
+ const childLogger = createLogger(transport, currentLevel, childModule);
136
+
137
+ children.add(childLogger);
138
+
139
+ return childLogger;
140
+ };
141
+
142
+ /**
143
+ * Sets the log level for this logger instance and all child loggers.
144
+ *
145
+ * @param {number | string} level - Log level (number) or level name (string)
146
+ */
147
+ const setLogLevel = level => {
148
+ let newLogLevel = level;
149
+
150
+ if (typeof newLogLevel === 'string') {
151
+ newLogLevel = newLogLevel.toLowerCase();
152
+
153
+ if (newLogLevel in LogLevel === false) {
154
+ return;
155
+ }
156
+
157
+ newLogLevel = LogLevel[newLogLevel];
158
+ }
159
+
160
+ currentLevel = newLogLevel;
161
+
162
+ // Propagate to all child loggers
163
+ for (const childLogger of children) {
164
+ childLogger.setLogLevel(currentLevel);
165
+ }
166
+ };
167
+
168
+ return {
169
+ info,
170
+ warn,
171
+ error,
172
+ fatal,
173
+ debug,
174
+ child,
175
+ setLogLevel,
176
+ };
177
+ };
@@ -0,0 +1,54 @@
1
+ 'use strict';
2
+
3
+ import { styleText } from 'node:util';
4
+
5
+ import { prettifyLevel } from '../utils/colors.mjs';
6
+ import { prettifyTimestamp } from '../utils/time.mjs';
7
+
8
+ /**
9
+ * Logs a formatted message to stdout for human-friendly CLI output.
10
+ *
11
+ * @param {import('../types').TransportContext} context
12
+ * @returns {void}
13
+ */
14
+ const console = ({ level, message, timestamp, metadata = {}, module }) => {
15
+ const { file, stack, ...rest } = metadata;
16
+
17
+ const time = prettifyTimestamp(timestamp);
18
+
19
+ process.stdout.write(`[${time}]`);
20
+
21
+ const prettyLevel = prettifyLevel(level);
22
+
23
+ process.stdout.write(` ${prettyLevel}`);
24
+
25
+ if (module) {
26
+ process.stdout.write(` (${module})`);
27
+ }
28
+
29
+ process.stdout.write(`: ${message}`);
30
+
31
+ if (file) {
32
+ process.stdout.write(` at ${file.path}`);
33
+ }
34
+
35
+ if (file?.position) {
36
+ const position = `(${file.position.start.line}:${file.position.end.line})`;
37
+
38
+ process.stdout.write(position);
39
+ }
40
+
41
+ // Print remaining metadata inline in purple
42
+ if (Object.keys(rest).length > 0) {
43
+ const metaStr = styleText('magenta', JSON.stringify(rest));
44
+ process.stdout.write(` ${metaStr}`);
45
+ }
46
+
47
+ process.stdout.write('\n');
48
+
49
+ if (stack) {
50
+ process.stdout.write(stack);
51
+ }
52
+ };
53
+
54
+ export default console;
@@ -0,0 +1,42 @@
1
+ 'use strict';
2
+
3
+ import { debug, notice, warning, error } from '@actions/core';
4
+
5
+ import { LogLevel } from '../constants.mjs';
6
+ import { prettifyLevel } from '../utils/colors.mjs';
7
+ import { prettifyTimestamp } from '../utils/time.mjs';
8
+
9
+ const actions = {
10
+ [LogLevel.debug]: debug,
11
+ [LogLevel.info]: notice,
12
+ [LogLevel.warn]: warning,
13
+ [LogLevel.error]: error,
14
+ [LogLevel.fatal]: error,
15
+ };
16
+
17
+ /**
18
+ * Logs messages to GitHub Actions with formatted output and file info with
19
+ * appropriate gh actions based on level.
20
+ *
21
+ * @param {import('../types').TransportContext} context
22
+ * @returns {void}
23
+ */
24
+ const github = ({ level, message, timestamp, metadata = {}, module }) => {
25
+ const { file } = metadata;
26
+
27
+ const time = prettifyTimestamp(timestamp);
28
+
29
+ const prettyLevel = prettifyLevel(level);
30
+
31
+ const logMessage = `[${time}] ${prettyLevel}${module ? ` (${module})` : ''}: ${message}`;
32
+
33
+ const logFn = actions[level] ?? notice;
34
+
35
+ logFn(logMessage, {
36
+ file: file?.path,
37
+ startLine: file?.position?.start.line,
38
+ endLine: file?.position?.end.line,
39
+ });
40
+ };
41
+
42
+ export default github;
@@ -0,0 +1,11 @@
1
+ 'use strict';
2
+
3
+ import console from './console.mjs';
4
+ import github from './github.mjs';
5
+
6
+ export const transports = {
7
+ console,
8
+ github,
9
+ };
10
+
11
+ export const availableTransports = Object.keys(transports);
@@ -0,0 +1,29 @@
1
+ export type LogLevel = 'info' | 'warn' | 'error' | 'fatal' | 'trace' | 'debug';
2
+
3
+ export type LogMessage = string | Error | string[];
4
+
5
+ export interface Position {
6
+ start: { line: number };
7
+ end: { line: number };
8
+ }
9
+
10
+ export interface File {
11
+ path: string;
12
+ position?: Position;
13
+ }
14
+
15
+ interface Metadata {
16
+ file?: File;
17
+ // Stack trace if the message is an error
18
+ stack?: string;
19
+ }
20
+
21
+ interface TransportContext {
22
+ level: number;
23
+ message: string;
24
+ timestamp: number;
25
+ metadata?: Metadata;
26
+ module?: string;
27
+ }
28
+
29
+ export type Transport = (context: TransportContext) => void;
@@ -0,0 +1,18 @@
1
+ 'use strict';
2
+
3
+ import { styleText } from 'node:util';
4
+
5
+ import { levelTags, levelToColorMap } from '../constants.mjs';
6
+
7
+ /**
8
+ * Returns a styled, uppercase log level tag for CLI output with color mapping
9
+ * for better readability.
10
+ *
11
+ * @param {number} level
12
+ * @returns {string}
13
+ */
14
+ export const prettifyLevel = level => {
15
+ const tag = levelTags[level] ?? String(level);
16
+
17
+ return styleText(levelToColorMap[level], tag.toUpperCase());
18
+ };
@@ -0,0 +1,21 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Formats a Unix timestamp in milliseconds as a human-readable time string
5
+ * in UTC timezone for CLI output.
6
+ *
7
+ * @param {number} timestamp
8
+ * @returns {string}
9
+ */
10
+ export const prettifyTimestamp = timestamp => {
11
+ const date = new Date(timestamp);
12
+
13
+ return new Intl.DateTimeFormat('en-US', {
14
+ timeZone: 'UTC',
15
+ hour12: false,
16
+ hour: '2-digit',
17
+ minute: '2-digit',
18
+ second: '2-digit',
19
+ fractionalSecondDigits: 3,
20
+ }).format(date);
21
+ };
@@ -0,0 +1,49 @@
1
+ 'use strict';
2
+
3
+ import { coerce } from 'semver';
4
+
5
+ import { loadFromURL } from '../utils/loaders.mjs';
6
+
7
+ // A ReGeX for retrieving Node.js version headers from the CHANGELOG.md
8
+ const NODE_VERSIONS_REGEX = /\* \[Node\.js ([0-9.]+)\]\S+ (.*)\r?\n/g;
9
+
10
+ // A ReGeX for retrieving the list items in the index document
11
+ const LIST_ITEM_REGEX = /\* \[(.*?)\]\((.*?)\.md\)/g;
12
+
13
+ // A ReGeX for checking if a Node.js version is an LTS release
14
+ const NODE_LTS_VERSION_REGEX = /Long Term Support/i;
15
+
16
+ /**
17
+ * Retrieves all Node.js major versions from the provided CHANGELOG.md file
18
+ * and returns an array of objects containing the version and LTS status.
19
+ * @param {string|URL} path Path to changelog
20
+ * @returns {Promise<Array<import('./types').ReleaseEntry>>}
21
+ */
22
+ export const parseChangelog = async path => {
23
+ const changelog = await loadFromURL(path);
24
+
25
+ const nodeMajors = Array.from(changelog.matchAll(NODE_VERSIONS_REGEX));
26
+
27
+ return nodeMajors.map(match => ({
28
+ version: coerce(match[1]),
29
+ isLts: NODE_LTS_VERSION_REGEX.test(match[2]),
30
+ }));
31
+ };
32
+
33
+ /**
34
+ * Retrieves all the document titles for sidebar generation.
35
+ *
36
+ * @param {string|URL} path Path to changelog
37
+ * @returns {Promise<Array<{ section: string, api: string }>>}
38
+ */
39
+ export const parseIndex = async path => {
40
+ if (!path || !path.length) {
41
+ return [];
42
+ }
43
+
44
+ const index = await loadFromURL(path);
45
+
46
+ const items = Array.from(index.matchAll(LIST_ITEM_REGEX));
47
+
48
+ return items.map(([, section, api]) => ({ section, api }));
49
+ };
@@ -0,0 +1,7 @@
1
+ import type { SemVer } from 'semver';
2
+
3
+ export interface ReleaseEntry {
4
+ version: SemVer;
5
+ isLts: boolean;
6
+ isCurrent: boolean;
7
+ }
@@ -0,0 +1,23 @@
1
+ import { loadGenerator } from '../generators/loader.mjs';
2
+ import { setConfig } from '../utils/configuration/index.mjs';
3
+
4
+ /**
5
+ * Processes a chunk of items using the specified generator's processChunk method.
6
+ * This is the worker entry point for Piscina.
7
+ *
8
+ * @param {ParallelTaskOptions} opts - Task options from Piscina
9
+ * @returns {Promise<unknown>} The processed result
10
+ */
11
+ export default async ({
12
+ generatorSpecifier,
13
+ input,
14
+ itemIndices,
15
+ extra,
16
+ configuration,
17
+ }) => {
18
+ await setConfig(configuration);
19
+
20
+ const generator = await loadGenerator(generatorSpecifier);
21
+
22
+ return generator.processChunk(input, itemIndices, extra);
23
+ };
@@ -0,0 +1,27 @@
1
+ import Piscina from 'piscina';
2
+
3
+ import logger from '../logger/index.mjs';
4
+
5
+ const poolLogger = logger.child('WorkerPool');
6
+
7
+ const workerScript = import.meta.resolve('./chunk-worker.mjs');
8
+
9
+ /**
10
+ * Creates a Piscina worker pool for parallel processing.
11
+ *
12
+ * @param {number} threads - Maximum number of worker threads
13
+ * @returns {import('piscina').Piscina} Configured Piscina instance
14
+ */
15
+ export default function createWorkerPool(threads) {
16
+ poolLogger.debug(`WorkerPool initialized`, {
17
+ threads,
18
+ workerScript,
19
+ });
20
+
21
+ return new Piscina({
22
+ filename: workerScript,
23
+ minThreads: 0,
24
+ maxThreads: threads,
25
+ idleTimeout: 1_000,
26
+ });
27
+ }
@@ -0,0 +1,140 @@
1
+ 'use strict';
2
+
3
+ import logger from '../logger/index.mjs';
4
+
5
+ const parallelLogger = logger.child('parallel');
6
+
7
+ /**
8
+ * Splits items into chunks of specified size.
9
+ *
10
+ * @param {number} count - Total number of items
11
+ * @param {number} size - Maximum items per chunk
12
+ * @returns {number[][]} Array of index arrays for each chunk
13
+ */
14
+ const createChunks = (count, size) => {
15
+ const chunks = [];
16
+
17
+ for (let i = 0; i < count; i += size) {
18
+ chunks.push(
19
+ Array.from({ length: Math.min(size, count - i) }, (_, j) => i + j)
20
+ );
21
+ }
22
+
23
+ return chunks;
24
+ };
25
+
26
+ /**
27
+ * Prepares task data for a chunk, slicing input to only include relevant items.
28
+ *
29
+ * @param {unknown[]} fullInput - Full input array
30
+ * @param {number[]} indices - Indices to process
31
+ * @param {Object} extra - Stuff to pass to the worker
32
+ * @param {import('../utils/configuration/types').Configuration} configuration - Serialized options
33
+ * @param {string} generatorSpecifier - Resolved specifier of the generator
34
+ * @param {string} generatorName - Name of the generator
35
+ * @returns {ParallelTaskOptions} Task data for Piscina
36
+ */
37
+ const createTask = (
38
+ fullInput,
39
+ indices,
40
+ extra,
41
+ configuration,
42
+ generatorSpecifier,
43
+ generatorName
44
+ ) => {
45
+ return {
46
+ generatorSpecifier,
47
+ // Only send the items needed for this chunk (reduces serialization overhead)
48
+ input: indices.map(i => fullInput[i]),
49
+ // Remap indices to 0-based for the sliced array
50
+ itemIndices: indices.map((_, i) => i),
51
+ extra,
52
+ // Only pass the needed configuration to this generator
53
+ configuration: {
54
+ [generatorName]: configuration[generatorName],
55
+ },
56
+ };
57
+ };
58
+
59
+ /**
60
+ * Creates a parallel worker that distributes work across a Piscina thread pool.
61
+ *
62
+ * @param {string} specifier - Resolved generator specifier (importable from workers)
63
+ * @param {GeneratorMetadata} generator - The loaded generator
64
+ * @param {import('piscina').Piscina} pool - Piscina instance
65
+ * @param {import('../utils/configuration/types').Configuration} configuration - Generator options
66
+ * @returns {ParallelWorker}
67
+ */
68
+ export default function createParallelWorker(
69
+ specifier,
70
+ generator,
71
+ pool,
72
+ configuration
73
+ ) {
74
+ const { threads, chunkSize } = configuration;
75
+
76
+ const { name } = generator;
77
+
78
+ return {
79
+ /**
80
+ * Processes items in parallel, yielding results as chunks complete.
81
+ *
82
+ * @template T
83
+ * @param {T[]} items - Items to process
84
+ * @param {object} extra - Extra options
85
+ * @yields {R[]} Chunk results as they complete
86
+ */
87
+ async *stream(items, extra) {
88
+ if (items.length === 0) {
89
+ return;
90
+ }
91
+
92
+ const chunks = createChunks(items.length, chunkSize);
93
+
94
+ parallelLogger.debug(
95
+ `Distributing ${items.length} items across ${chunks.length} chunks`,
96
+ { generator: name, chunks: chunks.length, chunkSize, threads }
97
+ );
98
+
99
+ const runInOneGo = threads <= 1 || items.length <= 2;
100
+
101
+ // Submit all tasks to Piscina - each promise resolves to itself for removal
102
+ const pending = new Set(
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
+ );
121
+
122
+ // Yield results as they complete (true parallel collection)
123
+ let completed = 0;
124
+
125
+ while (pending.size > 0) {
126
+ const { promise, result } = await Promise.race(pending);
127
+
128
+ pending.delete(promise);
129
+
130
+ completed++;
131
+
132
+ parallelLogger.debug(`Chunk ${completed}/${chunks.length} completed`, {
133
+ generator: name,
134
+ });
135
+
136
+ yield result;
137
+ }
138
+ },
139
+ };
140
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Converts a value to an array.
3
+ * @template T
4
+ * @param {T | T[]} val - The value to convert.
5
+ * @returns {T[]} The value as an array.
6
+ */
7
+ export const enforceArray = val => (Array.isArray(val) ? val : [val]);