@versatiles/release-tool 1.0.1 → 1.0.3
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/dist/index.d.ts +1 -1
- package/dist/index.js +5 -5
- package/dist/lib/command.d.ts +6 -1
- package/dist/lib/command.js +71 -30
- package/dist/lib/markdown.d.ts +19 -0
- package/dist/lib/markdown.js +164 -58
- package/dist/lib/typedoc.d.ts +3 -3
- package/dist/lib/typedoc.js +155 -81
- package/dist/lib/utils.d.ts +1 -0
- package/dist/lib/utils.js +10 -0
- package/package.json +5 -4
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
#!/usr/bin/env node --enable-source-maps
|
|
1
|
+
#!/usr/bin/env -S node --enable-source-maps
|
|
2
2
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
#!/usr/bin/env node --enable-source-maps
|
|
1
|
+
#!/usr/bin/env -S node --enable-source-maps
|
|
2
2
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { resolve } from 'node:path';
|
|
4
|
-
import {
|
|
4
|
+
import { generateTsMarkdownDoc } from './lib/typedoc.js';
|
|
5
5
|
import { injectMarkdown, updateTOC } from './lib/markdown.js';
|
|
6
6
|
import { Command, InvalidArgumentError } from 'commander';
|
|
7
7
|
import { cwd } from 'node:process';
|
|
@@ -15,14 +15,14 @@ program.command('ts2md')
|
|
|
15
15
|
.argument('<typescript>', 'Filename of the TypeScript file', checkFilename)
|
|
16
16
|
.argument('<tsconfig>', 'Filename of tsconfig.json', checkFilename)
|
|
17
17
|
.action(async (tsFilename, tsConfig) => {
|
|
18
|
-
const mdDocumentation = await
|
|
18
|
+
const mdDocumentation = await generateTsMarkdownDoc([tsFilename], tsConfig);
|
|
19
19
|
process.stdout.write(mdDocumentation);
|
|
20
20
|
});
|
|
21
21
|
program.command('cmd2md')
|
|
22
22
|
.description('documents a runnable command and outputs it to stdout')
|
|
23
23
|
.argument('<command>', 'command to run')
|
|
24
|
-
.action((command) => {
|
|
25
|
-
const mdDocumentation = generateCommandDocumentation(command);
|
|
24
|
+
.action(async (command) => {
|
|
25
|
+
const mdDocumentation = await generateCommandDocumentation(command);
|
|
26
26
|
process.stdout.write(mdDocumentation);
|
|
27
27
|
});
|
|
28
28
|
program.command('insertmd')
|
package/dist/lib/command.d.ts
CHANGED
|
@@ -1 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Generates documentation for a CLI command and its subcommands.
|
|
3
|
+
* @param command The base CLI command to document.
|
|
4
|
+
* @returns A Promise resolving to a string containing the generated Markdown documentation.
|
|
5
|
+
*/
|
|
6
|
+
export declare function generateCommandDocumentation(command: string): Promise<string>;
|
package/dist/lib/command.js
CHANGED
|
@@ -1,42 +1,83 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
import cp from 'child_process';
|
|
2
|
+
import { getErrorMessage } from './utils.js';
|
|
3
|
+
/**
|
|
4
|
+
* Generates documentation for a CLI command and its subcommands.
|
|
5
|
+
* @param command The base CLI command to document.
|
|
6
|
+
* @returns A Promise resolving to a string containing the generated Markdown documentation.
|
|
7
|
+
*/
|
|
8
|
+
export async function generateCommandDocumentation(command) {
|
|
9
|
+
// Get the base command's documentation and list of subcommands.
|
|
10
|
+
// eslint-disable-next-line prefer-const
|
|
11
|
+
let { markdown, subcommands } = await getCommandResults(command);
|
|
12
|
+
// Iterate over each subcommand to generate its documentation.
|
|
13
|
+
markdown += (await Promise.all(subcommands.map(async (subcommand) => {
|
|
14
|
+
const fullCommand = `${command} ${subcommand}`;
|
|
15
|
+
try {
|
|
16
|
+
// Get documentation for each subcommand.
|
|
17
|
+
const { markdown: subcommandMarkdown } = await getCommandResults(fullCommand);
|
|
18
|
+
return `\n# Subcommand: \`${fullCommand}\`\n\n${subcommandMarkdown}`;
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
// Handle errors in generating subcommand documentation.
|
|
22
|
+
throw new Error(`Error generating documentation for subcommand '${fullCommand}': ${getErrorMessage(error)}`);
|
|
23
|
+
}
|
|
24
|
+
}))).join('');
|
|
10
25
|
return markdown;
|
|
11
26
|
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
.split('
|
|
27
|
+
/**
|
|
28
|
+
* Executes a CLI command with the '--help' flag and parses the output.
|
|
29
|
+
* @param command The CLI command to execute.
|
|
30
|
+
* @returns A Promise resolving to an object containing the Markdown documentation and a list of subcommands.
|
|
31
|
+
*/
|
|
32
|
+
async function getCommandResults(command) {
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
// Spawn a child process to run the command with the '--help' flag.
|
|
35
|
+
const childProcess = cp.spawn('npx', [...command.split(' '), '--help']);
|
|
36
|
+
let output = '';
|
|
37
|
+
// Collect output data from the process.
|
|
38
|
+
childProcess.stdout.on('data', data => output += String(data));
|
|
39
|
+
childProcess.stderr.on('data', data => {
|
|
40
|
+
console.error(`stderr: ${data}`);
|
|
41
|
+
});
|
|
42
|
+
// Handle process errors.
|
|
43
|
+
childProcess.on('error', error => {
|
|
44
|
+
reject(new Error(`Failed to start subprocess: ${error.message}`));
|
|
45
|
+
});
|
|
46
|
+
// Handle process exit.
|
|
47
|
+
childProcess.on('close', (code) => {
|
|
48
|
+
if (code !== 0) {
|
|
49
|
+
reject(new Error(`Command failed with exit code ${code}`));
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const result = output.trim();
|
|
53
|
+
// Resolve with the formatted output and a list of subcommands.
|
|
54
|
+
resolve({
|
|
55
|
+
markdown: `\`\`\`console\n$ ${command}\n${result}\n\`\`\`\n`,
|
|
56
|
+
subcommands: extractSubcommands(result),
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Extracts a list of subcommands from the help output of a command.
|
|
63
|
+
* @param result The string output from a command's help flag.
|
|
64
|
+
* @returns An array of subcommand names.
|
|
65
|
+
*/
|
|
66
|
+
function extractSubcommands(result) {
|
|
67
|
+
return result
|
|
68
|
+
.replace(/.*\nCommands:/msgi, '') // Remove everything before the "Commands:" section.
|
|
69
|
+
.replace(/\n[a-z]+:.*/msi, '') // Remove everything after the subcommands list.
|
|
70
|
+
.split('\n') // Split by newline to process each line.
|
|
21
71
|
.flatMap((line) => {
|
|
72
|
+
// Extract subcommand names from each line.
|
|
22
73
|
const extract = /^ ([^ ]{2,})/.exec(line);
|
|
23
74
|
if (!extract)
|
|
24
75
|
return [];
|
|
25
|
-
|
|
26
|
-
|
|
76
|
+
const [, subcommand] = extract;
|
|
77
|
+
// Ignore the 'help' subcommand.
|
|
27
78
|
if (subcommand === 'help')
|
|
28
79
|
return [];
|
|
29
80
|
return [subcommand];
|
|
30
81
|
});
|
|
31
|
-
return {
|
|
32
|
-
markdown: [
|
|
33
|
-
'```console',
|
|
34
|
-
'$ ' + command,
|
|
35
|
-
result,
|
|
36
|
-
'```',
|
|
37
|
-
'',
|
|
38
|
-
].join('\n'),
|
|
39
|
-
subcommands,
|
|
40
|
-
};
|
|
41
82
|
}
|
|
42
83
|
//# sourceMappingURL=command.js.map
|
package/dist/lib/markdown.d.ts
CHANGED
|
@@ -1,2 +1,21 @@
|
|
|
1
|
+
import type { Root, PhrasingContent } from 'mdast';
|
|
2
|
+
/**
|
|
3
|
+
* Injects a Markdown segment under a specified heading in a Markdown document.
|
|
4
|
+
* Optionally, the injected segment can be made foldable for better readability.
|
|
5
|
+
* @param document The original Markdown document.
|
|
6
|
+
* @param segment The Markdown segment to be injected.
|
|
7
|
+
* @param heading The heading under which the segment is to be injected.
|
|
8
|
+
* @param foldable If true, makes the segment foldable.
|
|
9
|
+
* @returns The modified Markdown document.
|
|
10
|
+
*/
|
|
1
11
|
export declare function injectMarkdown(document: string, segment: string, heading: string, foldable?: boolean): string;
|
|
12
|
+
/**
|
|
13
|
+
* Updates the Table of Contents (TOC) of a Markdown document.
|
|
14
|
+
* The TOC is rebuilt based on the headings present in the document.
|
|
15
|
+
* @param main The main Markdown document.
|
|
16
|
+
* @param heading The heading under which the TOC is to be updated.
|
|
17
|
+
* @returns The Markdown document with an updated TOC.
|
|
18
|
+
*/
|
|
2
19
|
export declare function updateTOC(main: string, heading: string): string;
|
|
20
|
+
export declare function nodeToHtml(node: PhrasingContent): string;
|
|
21
|
+
export declare function parseMarkdown(document: string): Root;
|
package/dist/lib/markdown.js
CHANGED
|
@@ -1,79 +1,139 @@
|
|
|
1
1
|
import { remark } from 'remark';
|
|
2
|
+
import remarkGfm from 'remark-gfm';
|
|
3
|
+
import { getErrorMessage } from './utils.js';
|
|
4
|
+
/**
|
|
5
|
+
* Injects a Markdown segment under a specified heading in a Markdown document.
|
|
6
|
+
* Optionally, the injected segment can be made foldable for better readability.
|
|
7
|
+
* @param document The original Markdown document.
|
|
8
|
+
* @param segment The Markdown segment to be injected.
|
|
9
|
+
* @param heading The heading under which the segment is to be injected.
|
|
10
|
+
* @param foldable If true, makes the segment foldable.
|
|
11
|
+
* @returns The modified Markdown document.
|
|
12
|
+
*/
|
|
2
13
|
// eslint-disable-next-line @typescript-eslint/max-params
|
|
3
14
|
export function injectMarkdown(document, segment, heading, foldable) {
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
const
|
|
15
|
+
// Parse the input strings into Abstract Syntax Trees (ASTs).
|
|
16
|
+
const documentAst = parseMarkdown(document);
|
|
17
|
+
const segmentAst = parseMarkdown(segment);
|
|
18
|
+
const headingAst = parseMarkdown(heading);
|
|
19
|
+
// Initialize the start index of the injection.
|
|
7
20
|
let startIndex;
|
|
8
21
|
try {
|
|
9
|
-
|
|
22
|
+
// Find the start index where the new segment should be injected.
|
|
23
|
+
startIndex = findSegmentStartIndex(documentAst, headingAst);
|
|
10
24
|
}
|
|
11
25
|
catch (error) {
|
|
12
|
-
|
|
26
|
+
// Handle errors during the search for the start index.
|
|
27
|
+
console.error(`Error while searching for segment "${heading}": ${getErrorMessage(error)}`);
|
|
13
28
|
throw error;
|
|
14
29
|
}
|
|
30
|
+
// Get the depth of the specified heading to maintain the structure.
|
|
15
31
|
const depth = getHeadingDepth(documentAst, startIndex);
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
32
|
+
// Find the index of the next heading of the same depth to define the end of the segment.
|
|
33
|
+
const endIndex = findNextHeadingIndex(documentAst, startIndex + 1, depth);
|
|
34
|
+
// Adjust the indentation of the segment to align with the specified depth.
|
|
35
|
+
indentSegmentToDepth(segmentAst, depth);
|
|
36
|
+
// Convert the segment to a foldable section if required.
|
|
37
|
+
if (foldable === true)
|
|
38
|
+
convertToFoldable(segmentAst);
|
|
39
|
+
// Merge the original document AST with the new segment AST.
|
|
40
|
+
mergeSegments(documentAst, segmentAst, startIndex + 1, endIndex);
|
|
41
|
+
// Convert the modified AST back to a Markdown string and return.
|
|
21
42
|
return remark().stringify(documentAst);
|
|
22
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Updates the Table of Contents (TOC) of a Markdown document.
|
|
46
|
+
* The TOC is rebuilt based on the headings present in the document.
|
|
47
|
+
* @param main The main Markdown document.
|
|
48
|
+
* @param heading The heading under which the TOC is to be updated.
|
|
49
|
+
* @returns The Markdown document with an updated TOC.
|
|
50
|
+
*/
|
|
23
51
|
export function updateTOC(main, heading) {
|
|
24
|
-
|
|
25
|
-
const
|
|
52
|
+
// Parse the main document and the heading for the TOC.
|
|
53
|
+
const mainAst = parseMarkdown(main);
|
|
54
|
+
const headingText = extractTextFromMDAsHTML(parseMarkdown(heading));
|
|
55
|
+
// Build the TOC by iterating over each heading in the document.
|
|
26
56
|
const toc = mainAst.children
|
|
27
57
|
.flatMap(c => {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const text = getMDText(c);
|
|
31
|
-
if (text === headingText)
|
|
58
|
+
// Skip non-heading nodes and the specified heading.
|
|
59
|
+
if (c.type !== 'heading' || extractTextFromMDAsHTML(c) === headingText)
|
|
32
60
|
return [];
|
|
61
|
+
// Format each heading as a TOC entry.
|
|
33
62
|
const indention = ' '.repeat((c.depth - 1) * 2);
|
|
34
63
|
const anchor = getMDAnchor(c);
|
|
35
|
-
return `${indention}* [${
|
|
64
|
+
return `${indention}* [${extractTextFromMDAsHTML(c)}](#${anchor})\n`;
|
|
36
65
|
})
|
|
37
66
|
.join('');
|
|
67
|
+
// Inject the newly generated TOC under the specified heading in the document.
|
|
38
68
|
return injectMarkdown(main, toc, heading);
|
|
39
69
|
}
|
|
40
|
-
|
|
70
|
+
// Below are the helper functions used in the above main functions. Each helper function
|
|
71
|
+
// is designed for a specific operation and is provided with detailed comments for clarity.
|
|
72
|
+
/**
|
|
73
|
+
* Finds the start index of the segment under a specific heading in the AST.
|
|
74
|
+
* @param mainAst The AST of the main document.
|
|
75
|
+
* @param headingAst The AST of the heading under which the segment is to be inserted.
|
|
76
|
+
* @returns The start index of the segment in the main document AST.
|
|
77
|
+
* @throws Error if headingAst does not have exactly one child or the child is not a heading.
|
|
78
|
+
*/
|
|
79
|
+
function findSegmentStartIndex(mainAst, headingAst) {
|
|
80
|
+
// Verify the structure of the headingAst.
|
|
41
81
|
if (headingAst.children.length !== 1)
|
|
42
|
-
throw Error();
|
|
82
|
+
throw Error('headingAst.children.length !== 1');
|
|
43
83
|
if (headingAst.children[0].type !== 'heading')
|
|
44
|
-
throw Error();
|
|
84
|
+
throw Error('headingAst.children[0].type !== \'heading\'');
|
|
45
85
|
const sectionDepth = headingAst.children[0].depth;
|
|
46
|
-
const sectionText =
|
|
86
|
+
const sectionText = extractTextFromMDAsHTML(headingAst);
|
|
87
|
+
// Search for the index of the heading in the main document AST.
|
|
47
88
|
const indexes = mainAst.children.flatMap((c, index) => {
|
|
48
|
-
if ((c.type === 'heading') && (c.depth === sectionDepth) &&
|
|
89
|
+
if ((c.type === 'heading') && (c.depth === sectionDepth) && extractTextFromMDAsHTML(c).startsWith(sectionText)) {
|
|
49
90
|
return [index];
|
|
50
91
|
}
|
|
51
92
|
return [];
|
|
52
93
|
});
|
|
94
|
+
// Handle the cases of no match or multiple matches.
|
|
53
95
|
if (indexes.length < 1)
|
|
54
96
|
throw Error('section not found');
|
|
55
97
|
if (indexes.length > 1)
|
|
56
98
|
throw Error('too many sections found');
|
|
57
99
|
return indexes[0];
|
|
58
100
|
}
|
|
59
|
-
|
|
101
|
+
/**
|
|
102
|
+
* Finds the index of the next heading at the same depth in the AST.
|
|
103
|
+
* @param mainAst The AST of the main document.
|
|
104
|
+
* @param startIndex The index to start searching from.
|
|
105
|
+
* @param depth The depth of the headings to match.
|
|
106
|
+
* @returns The index of the next heading of the same depth, or the length of the children array if none is found.
|
|
107
|
+
*/
|
|
108
|
+
function findNextHeadingIndex(mainAst, startIndex, depth) {
|
|
109
|
+
// Iterate over the AST nodes starting from startIndex.
|
|
60
110
|
for (let i = startIndex; i < mainAst.children.length; i++) {
|
|
61
111
|
const child = mainAst.children[i];
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
continue;
|
|
66
|
-
return i;
|
|
112
|
+
// Return the index of the next heading at the same depth.
|
|
113
|
+
if (child.type === 'heading' && child.depth === depth)
|
|
114
|
+
return i;
|
|
67
115
|
}
|
|
68
116
|
return mainAst.children.length;
|
|
69
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* Gets the depth of the heading at a given index in the AST.
|
|
120
|
+
* @param mainAst The AST of the main document.
|
|
121
|
+
* @param index The index of the heading.
|
|
122
|
+
* @returns The depth of the heading.
|
|
123
|
+
* @throws Error if the node at the index is not a heading.
|
|
124
|
+
*/
|
|
70
125
|
function getHeadingDepth(mainAst, index) {
|
|
71
126
|
const node = mainAst.children[index];
|
|
72
127
|
if (node.type !== 'heading')
|
|
73
|
-
throw Error();
|
|
128
|
+
throw Error('node.type !== \'heading\'');
|
|
74
129
|
return node.depth;
|
|
75
130
|
}
|
|
76
|
-
|
|
131
|
+
/**
|
|
132
|
+
* Indents each segment in the AST to match a specified depth, modifying headings accordingly.
|
|
133
|
+
* @param segmentAst The AST of the segment to be indented.
|
|
134
|
+
* @param depth The depth to which the segment should be indented.
|
|
135
|
+
*/
|
|
136
|
+
function indentSegmentToDepth(segmentAst, depth) {
|
|
77
137
|
segmentAst.children.forEach(node => {
|
|
78
138
|
switch (node.type) {
|
|
79
139
|
case 'code':
|
|
@@ -91,18 +151,31 @@ function indentChapter(segmentAst, depth) {
|
|
|
91
151
|
}
|
|
92
152
|
});
|
|
93
153
|
}
|
|
154
|
+
/**
|
|
155
|
+
* Merges a segment AST into the main document AST at specified start and end indexes.
|
|
156
|
+
* @param mainAst The AST of the main document.
|
|
157
|
+
* @param segmentAst The AST of the segment to be merged.
|
|
158
|
+
* @param startIndex The start index in the main AST.
|
|
159
|
+
* @param endIndex The end index in the main AST.
|
|
160
|
+
*/
|
|
94
161
|
// eslint-disable-next-line @typescript-eslint/max-params
|
|
95
|
-
function
|
|
162
|
+
function mergeSegments(mainAst, segmentAst, startIndex, endIndex) {
|
|
96
163
|
mainAst.children.splice(startIndex, endIndex - startIndex, ...segmentAst.children);
|
|
97
164
|
}
|
|
98
|
-
|
|
165
|
+
/**
|
|
166
|
+
* Extracts the textual content from a node in the AST.
|
|
167
|
+
* @param node The AST node.
|
|
168
|
+
* @returns The extracted text content.
|
|
169
|
+
* @throws Error if the node type is unknown.
|
|
170
|
+
*/
|
|
171
|
+
function extractTextFromMDAsHTML(node) {
|
|
99
172
|
switch (node.type) {
|
|
100
173
|
case 'inlineCode':
|
|
101
174
|
case 'text':
|
|
102
|
-
return node.value;
|
|
175
|
+
return textToHtml(node.value);
|
|
103
176
|
case 'heading':
|
|
104
177
|
case 'root':
|
|
105
|
-
return node.children.map(
|
|
178
|
+
return node.children.map(extractTextFromMDAsHTML).join('');
|
|
106
179
|
case 'html':
|
|
107
180
|
return '';
|
|
108
181
|
default:
|
|
@@ -110,9 +183,16 @@ function getMDText(node) {
|
|
|
110
183
|
throw Error('unknown type: ' + node.type);
|
|
111
184
|
}
|
|
112
185
|
}
|
|
186
|
+
/**
|
|
187
|
+
* Generates an anchor ID for a Markdown heading based on its text content.
|
|
188
|
+
* @param node The heading node.
|
|
189
|
+
* @returns The generated anchor ID.
|
|
190
|
+
* @throws Error if the child node type is unknown.
|
|
191
|
+
*/
|
|
113
192
|
function getMDAnchor(node) {
|
|
114
193
|
let text = '';
|
|
115
194
|
for (const c of node.children) {
|
|
195
|
+
// Handle different types of child nodes to construct the anchor text.
|
|
116
196
|
switch (c.type) {
|
|
117
197
|
case 'html':
|
|
118
198
|
const match = /<a\s.*id\s*=\s*['"]([^'"]+)/i.exec(c.value);
|
|
@@ -128,13 +208,18 @@ function getMDAnchor(node) {
|
|
|
128
208
|
throw Error('unknown type: ' + c.type);
|
|
129
209
|
}
|
|
130
210
|
}
|
|
211
|
+
// Format the text to create a suitable anchor ID.
|
|
131
212
|
text = text.toLowerCase()
|
|
132
213
|
.replace(/[()]+/g, '')
|
|
133
214
|
.replace(/[^a-z0-9]+/g, '-')
|
|
134
215
|
.replace(/^\-+|\-+$/g, '');
|
|
135
216
|
return text;
|
|
136
217
|
}
|
|
137
|
-
|
|
218
|
+
/**
|
|
219
|
+
* Converts a segment of the AST into a foldable HTML element.
|
|
220
|
+
* @param ast The AST of the segment to be converted.
|
|
221
|
+
*/
|
|
222
|
+
function convertToFoldable(ast) {
|
|
138
223
|
const openDetails = [];
|
|
139
224
|
const children = [];
|
|
140
225
|
ast.children.forEach((c) => {
|
|
@@ -163,31 +248,52 @@ function makeFoldable(ast) {
|
|
|
163
248
|
}
|
|
164
249
|
}
|
|
165
250
|
}
|
|
166
|
-
function lineToHtml(
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
251
|
+
function lineToHtml(heading) {
|
|
252
|
+
return `<h${heading.depth}>${nodesToHtml(heading.children)}</h${heading.depth}>`;
|
|
253
|
+
}
|
|
254
|
+
export function nodeToHtml(node) {
|
|
255
|
+
switch (node.type) {
|
|
256
|
+
case 'html':
|
|
257
|
+
return node.value;
|
|
258
|
+
case 'text':
|
|
259
|
+
return textToHtml(node.value);
|
|
260
|
+
case 'inlineCode':
|
|
261
|
+
return `<code>${textToHtml(node.value)}</code>`;
|
|
262
|
+
case 'break':
|
|
263
|
+
return '<br />';
|
|
264
|
+
case 'delete':
|
|
265
|
+
return `<del>${nodesToHtml(node.children)}</del>`;
|
|
266
|
+
case 'emphasis':
|
|
267
|
+
return `<em>${nodesToHtml(node.children)}</em>`;
|
|
268
|
+
case 'strong':
|
|
269
|
+
return `<strong>${nodesToHtml(node.children)}</strong>`;
|
|
270
|
+
case 'link':
|
|
271
|
+
return `<a href="${node.url}"${node.title == null ? '' : ` title="${node.title}"`}>${nodesToHtml(node.children)}</a>`;
|
|
272
|
+
case 'linkReference':
|
|
273
|
+
throw new Error('"linkReference to html" not implemented');
|
|
274
|
+
case 'footnoteReference':
|
|
275
|
+
throw new Error('"footnoteReference to html" not implemented');
|
|
276
|
+
case 'image':
|
|
277
|
+
const attributes = [`src="${node.url}"`];
|
|
278
|
+
if (node.alt ?? '')
|
|
279
|
+
attributes.push(`alt="${node.alt}"`);
|
|
280
|
+
if (node.title ?? '')
|
|
281
|
+
attributes.push(`title="${node.title}"`);
|
|
282
|
+
return `<img ${attributes.join(' ')} />`;
|
|
283
|
+
case 'imageReference':
|
|
284
|
+
throw new Error('"imageReference to html" not implemented');
|
|
285
|
+
default:
|
|
286
|
+
console.log(node);
|
|
287
|
+
throw Error('unknown type');
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
function nodesToHtml(children) {
|
|
291
|
+
return children.map(nodeToHtml).join('');
|
|
189
292
|
}
|
|
190
293
|
function textToHtml(text) {
|
|
191
294
|
return text.replace(/[^a-z0-9 ,.-:_?@äöüß]/gi, c => `&#${c.charCodeAt(0)};`);
|
|
192
295
|
}
|
|
296
|
+
export function parseMarkdown(document) {
|
|
297
|
+
return remark().use(remarkGfm).parse(document);
|
|
298
|
+
}
|
|
193
299
|
//# sourceMappingURL=markdown.js.map
|
package/dist/lib/typedoc.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Generate markdown documentation from TypeScript files.
|
|
3
|
-
* @param
|
|
4
|
-
* @param
|
|
3
|
+
* @param sourceFilePaths - Array of absolute TypeScript file paths.
|
|
4
|
+
* @param tsConfigPath - Absolute file path of tsconfig.json.
|
|
5
5
|
*/
|
|
6
|
-
export declare function
|
|
6
|
+
export declare function generateTsMarkdownDoc(sourceFilePaths: string[], tsConfigPath: string): Promise<string>;
|
package/dist/lib/typedoc.js
CHANGED
|
@@ -1,31 +1,70 @@
|
|
|
1
1
|
import { Application, ReflectionKind, } from 'typedoc';
|
|
2
2
|
/**
|
|
3
3
|
* Generate markdown documentation from TypeScript files.
|
|
4
|
-
* @param
|
|
5
|
-
* @param
|
|
4
|
+
* @param sourceFilePaths - Array of absolute TypeScript file paths.
|
|
5
|
+
* @param tsConfigPath - Absolute file path of tsconfig.json.
|
|
6
6
|
*/
|
|
7
|
-
export async function
|
|
8
|
-
const app = await Application.bootstrap({ entryPoints, tsconfig });
|
|
7
|
+
export async function generateTsMarkdownDoc(sourceFilePaths, tsConfigPath) {
|
|
8
|
+
const app = await Application.bootstrap({ entryPoints: sourceFilePaths, tsconfig: tsConfigPath });
|
|
9
9
|
const project = await app.convert();
|
|
10
10
|
if (!project) {
|
|
11
|
-
throw new Error('Failed to convert project.');
|
|
11
|
+
throw new Error('Failed to convert TypeScript project.');
|
|
12
12
|
}
|
|
13
|
-
return Array.from(
|
|
13
|
+
return Array.from(documentProject(project)).join('\n');
|
|
14
14
|
}
|
|
15
|
-
function*
|
|
15
|
+
function* documentProject(project) {
|
|
16
16
|
if (!project.groups) {
|
|
17
|
-
throw new Error('No code to document found! Is this a lib?');
|
|
17
|
+
throw new Error('No TypeScript code to document found! Is this a lib?');
|
|
18
18
|
}
|
|
19
19
|
for (const group of project.groups) {
|
|
20
|
+
yield '\n# ' + group.title;
|
|
20
21
|
for (const declaration of group.children) {
|
|
21
|
-
|
|
22
|
+
switch (declaration.kind) {
|
|
23
|
+
case ReflectionKind.Class:
|
|
24
|
+
yield* documentClass(declaration);
|
|
25
|
+
break;
|
|
26
|
+
case ReflectionKind.Function:
|
|
27
|
+
yield* documentMethod(declaration, 2);
|
|
28
|
+
break;
|
|
29
|
+
case ReflectionKind.Interface:
|
|
30
|
+
yield* documentInterface(declaration);
|
|
31
|
+
break;
|
|
32
|
+
case ReflectionKind.TypeAlias:
|
|
33
|
+
yield* documentType(declaration);
|
|
34
|
+
break;
|
|
35
|
+
case ReflectionKind.Variable:
|
|
36
|
+
yield* documentVariable(declaration);
|
|
37
|
+
break;
|
|
38
|
+
default:
|
|
39
|
+
throw new Error('implement ' + declaration.kind);
|
|
40
|
+
}
|
|
22
41
|
}
|
|
23
42
|
}
|
|
24
43
|
}
|
|
25
|
-
function*
|
|
26
|
-
|
|
27
|
-
yield
|
|
28
|
-
yield
|
|
44
|
+
function* documentInterface(declaration) {
|
|
45
|
+
yield `\n## Interface: \`${declaration.name}\`<a id="${createAnchorId(declaration)}"></a>`;
|
|
46
|
+
yield '\n```typescript';
|
|
47
|
+
yield 'interface {';
|
|
48
|
+
for (const child of declaration.children ?? []) {
|
|
49
|
+
if (child.kind !== ReflectionKind.Property)
|
|
50
|
+
throw Error('should be a property inside an interface');
|
|
51
|
+
if (child.type == null)
|
|
52
|
+
throw Error('should have a type');
|
|
53
|
+
const name = child.name + (child.flags.isOptional ? '?' : '');
|
|
54
|
+
yield ` ${name}: ${formatTypeDeclaration(child.type)};`;
|
|
55
|
+
}
|
|
56
|
+
yield '}';
|
|
57
|
+
yield '```';
|
|
58
|
+
}
|
|
59
|
+
function* documentType(declaration) {
|
|
60
|
+
yield `\n## Type: \`${declaration.name}\`<a id="${createAnchorId(declaration)}"></a>`;
|
|
61
|
+
if (declaration.type) {
|
|
62
|
+
yield `\n**Type:** <code>${formatTypeDeclaration(declaration.type)}</code>`;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function* documentClass(declaration) {
|
|
66
|
+
yield `\n## Class: \`${declaration.name}\`<a id="${createAnchorId(declaration)}"></a>`;
|
|
67
|
+
yield* documentSummaryBlock(declaration);
|
|
29
68
|
for (const group of declaration.groups ?? []) {
|
|
30
69
|
const publicMembers = group.children.filter(member => !member.flags.isPrivate && !member.flags.isProtected);
|
|
31
70
|
if (publicMembers.length === 0)
|
|
@@ -35,80 +74,93 @@ function* renderDeclaration(declaration) {
|
|
|
35
74
|
switch (group.title) {
|
|
36
75
|
case 'Constructors':
|
|
37
76
|
if (publicMembers.length !== 1)
|
|
38
|
-
throw Error();
|
|
39
|
-
yield*
|
|
77
|
+
throw Error('publicMembers.length !== 1');
|
|
78
|
+
yield* documentMethod(publicMembers[0], 3, true);
|
|
79
|
+
continue;
|
|
80
|
+
case 'Accessors':
|
|
81
|
+
yield '\n### Accessors';
|
|
82
|
+
for (const member of publicMembers)
|
|
83
|
+
yield documentAccessor(member);
|
|
40
84
|
continue;
|
|
41
85
|
case 'Properties':
|
|
42
|
-
|
|
43
|
-
//yield '**Properties**';
|
|
86
|
+
yield '\n### Properties';
|
|
44
87
|
for (const member of publicMembers)
|
|
45
|
-
yield
|
|
88
|
+
yield documentProperty(member);
|
|
46
89
|
continue;
|
|
47
90
|
case 'Methods':
|
|
48
|
-
//yield '';
|
|
49
|
-
//yield '**Methods**';
|
|
50
91
|
for (const member of publicMembers)
|
|
51
|
-
yield*
|
|
92
|
+
yield* documentMethod(member, 3);
|
|
52
93
|
continue;
|
|
53
94
|
default:
|
|
54
95
|
console.log(group);
|
|
55
96
|
throw Error('Unknown group title');
|
|
56
97
|
}
|
|
57
98
|
}
|
|
58
|
-
if (declaration.type) {
|
|
59
|
-
yield `\n**Type:** <code>${formatType(declaration.type)}</code>`;
|
|
60
|
-
}
|
|
61
99
|
}
|
|
62
|
-
function*
|
|
63
|
-
if (
|
|
100
|
+
function* documentMethod(method, depth, isConstructor = false) {
|
|
101
|
+
if (method.signatures?.length !== 1)
|
|
64
102
|
throw Error('should be 1');
|
|
65
|
-
const [signature] =
|
|
66
|
-
const
|
|
103
|
+
const [signature] = method.signatures;
|
|
104
|
+
const methodName = signature.name;
|
|
67
105
|
const parameters = formatMethodParameters(signature.parameters ?? []);
|
|
68
106
|
const returnType = signature.type;
|
|
69
|
-
const
|
|
70
|
-
yield
|
|
71
|
-
yield
|
|
72
|
-
yield* renderSummaryBlock(signature);
|
|
107
|
+
const methodType = isConstructor ? 'Constructor' : 'Method';
|
|
108
|
+
yield `\n${'#'.repeat(depth)} ${methodType}: \`${methodName}(${parameters})\``;
|
|
109
|
+
yield* documentSummaryBlock(signature);
|
|
73
110
|
if (signature.parameters && signature.parameters.length > 0) {
|
|
74
111
|
yield '';
|
|
75
112
|
yield '**Parameters:**';
|
|
76
113
|
for (const parameter of signature.parameters) {
|
|
77
|
-
yield
|
|
114
|
+
yield documentProperty(parameter);
|
|
78
115
|
}
|
|
79
116
|
}
|
|
80
|
-
if (returnType) {
|
|
81
|
-
yield
|
|
82
|
-
yield `**Returns:** <code>${formatType(returnType)}</code>`;
|
|
117
|
+
if (returnType && !isConstructor) {
|
|
118
|
+
yield `\n**Returns:** <code>${formatTypeDeclaration(returnType)}</code>`;
|
|
83
119
|
}
|
|
84
120
|
}
|
|
85
121
|
function formatMethodParameters(parameters) {
|
|
86
122
|
return parameters.map(param => param.name).join(', ');
|
|
87
123
|
}
|
|
88
124
|
// Helper Functions
|
|
89
|
-
function
|
|
125
|
+
function getDeclarationKindName(kind) {
|
|
90
126
|
switch (kind) {
|
|
91
127
|
case ReflectionKind.Class: return 'Class';
|
|
128
|
+
case ReflectionKind.Function: return 'Function';
|
|
92
129
|
case ReflectionKind.Interface: return 'Interface';
|
|
93
130
|
case ReflectionKind.TypeAlias: return 'Type';
|
|
131
|
+
case ReflectionKind.Variable: return 'Variable';
|
|
94
132
|
default: throw new Error(`Unknown reflection kind: ${kind}`);
|
|
95
133
|
}
|
|
96
134
|
}
|
|
97
|
-
function
|
|
135
|
+
function documentProperty(ref) {
|
|
98
136
|
let line = ` - <code>${ref.name}${resolveTypeDeclaration(ref.type)}</code>`;
|
|
99
137
|
if (ref.flags.isOptional)
|
|
100
138
|
line += ' (optional)';
|
|
101
139
|
const summary = extractSummary(ref.comment);
|
|
102
|
-
if (summary)
|
|
140
|
+
if (summary != null)
|
|
141
|
+
line += ' \n ' + summary;
|
|
142
|
+
return line;
|
|
143
|
+
}
|
|
144
|
+
function* documentVariable(ref) {
|
|
145
|
+
const prefix = ref.flags.isConst ? 'const' : 'let';
|
|
146
|
+
yield `\n## \`${prefix} ${ref.name}\``;
|
|
147
|
+
const summary = extractSummary(ref.comment);
|
|
148
|
+
if (summary != null)
|
|
149
|
+
yield summary;
|
|
150
|
+
}
|
|
151
|
+
function documentAccessor(ref) {
|
|
152
|
+
let line = ` - <code>${ref.name}${resolveTypeDeclaration(ref.type)}</code>`;
|
|
153
|
+
const summary = extractSummary(ref.comment);
|
|
154
|
+
if (summary != null)
|
|
103
155
|
line += ' \n ' + summary;
|
|
104
156
|
return line;
|
|
105
157
|
}
|
|
106
158
|
function extractSummary(comment) {
|
|
107
159
|
if (!comment)
|
|
108
|
-
return
|
|
160
|
+
return null;
|
|
109
161
|
return comment.summary.map(line => line.text).join('');
|
|
110
162
|
}
|
|
111
|
-
function*
|
|
163
|
+
function* documentSummaryBlock(ref) {
|
|
112
164
|
yield '';
|
|
113
165
|
if (ref.comment) {
|
|
114
166
|
yield formatComment(ref.comment);
|
|
@@ -117,25 +169,31 @@ function* renderSummaryBlock(ref) {
|
|
|
117
169
|
const { type } = ref;
|
|
118
170
|
if (type?.type === 'reflection') {
|
|
119
171
|
if (type.declaration.signatures?.length !== 1)
|
|
120
|
-
throw Error();
|
|
172
|
+
throw Error('type.declaration.signatures?.length !== 1');
|
|
121
173
|
const [signature] = type.declaration.signatures;
|
|
122
174
|
if (signature.comment) {
|
|
123
175
|
yield formatComment(signature.comment);
|
|
124
176
|
return;
|
|
125
177
|
}
|
|
126
178
|
}
|
|
127
|
-
|
|
179
|
+
const sourceLink = createSourceLink(ref);
|
|
180
|
+
if (sourceLink != null)
|
|
181
|
+
yield sourceLink;
|
|
128
182
|
return;
|
|
129
183
|
function formatComment(comment) {
|
|
130
|
-
|
|
184
|
+
let summary = extractSummary(comment) ?? '';
|
|
185
|
+
const link = createSourceLink(ref);
|
|
186
|
+
if (link != null)
|
|
187
|
+
summary += ' ' + link;
|
|
188
|
+
return summary.replace(/\n/m, ' \n') + '\n';
|
|
131
189
|
}
|
|
132
190
|
}
|
|
133
191
|
function resolveTypeDeclaration(someType) {
|
|
134
192
|
if (!someType)
|
|
135
193
|
return '';
|
|
136
|
-
return `: ${
|
|
194
|
+
return `: ${formatTypeDeclaration(someType)}`;
|
|
137
195
|
}
|
|
138
|
-
function
|
|
196
|
+
function formatTypeDeclaration(someType) {
|
|
139
197
|
return getTypeRec(someType);
|
|
140
198
|
function getTypeRec(some) {
|
|
141
199
|
switch (some.type) {
|
|
@@ -146,7 +204,7 @@ function formatType(someType) {
|
|
|
146
204
|
case 'reference':
|
|
147
205
|
let result = some.name;
|
|
148
206
|
if (some.reflection)
|
|
149
|
-
result = `[${result}](#${
|
|
207
|
+
result = `[${result}](#${createAnchorId(some.reflection)})`;
|
|
150
208
|
if (some.typeArguments?.length ?? 0)
|
|
151
209
|
result += '<'
|
|
152
210
|
+ (some.typeArguments ?? [])
|
|
@@ -154,51 +212,67 @@ function formatType(someType) {
|
|
|
154
212
|
+ '>';
|
|
155
213
|
return result;
|
|
156
214
|
case 'reflection':
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
.map(p => {
|
|
165
|
-
return p.name + (p.type ? ': ' + getTypeRec(p.type) : '');
|
|
166
|
-
}).join(', ');
|
|
167
|
-
return `(${parameters}) => ${type}`;
|
|
215
|
+
switch (some.declaration.kind) {
|
|
216
|
+
case ReflectionKind.TypeLiteral: return decodeReflectionTypeLiteral(some.declaration);
|
|
217
|
+
default:
|
|
218
|
+
console.log('declarationKindName', getDeclarationKindName(some.declaration.kind));
|
|
219
|
+
console.dir(some, { depth: 4 });
|
|
220
|
+
throw Error();
|
|
221
|
+
}
|
|
168
222
|
case 'tuple':
|
|
169
223
|
return `[${some.elements.map(getTypeRec).join(', ')}]`;
|
|
170
224
|
case 'union':
|
|
171
225
|
return some.types.map(getTypeRec).join(' | ');
|
|
226
|
+
case 'array':
|
|
227
|
+
return getTypeRec(some.elementType) + '[]';
|
|
172
228
|
default:
|
|
173
229
|
console.log(some);
|
|
230
|
+
throw Error(some.type);
|
|
231
|
+
}
|
|
232
|
+
function decodeReflectionTypeLiteral(ref) {
|
|
233
|
+
try {
|
|
234
|
+
if (ref.variant !== 'declaration')
|
|
235
|
+
throw Error();
|
|
236
|
+
if (ref.groups && !ref.signatures) {
|
|
237
|
+
if (!Array.isArray(ref.groups))
|
|
238
|
+
throw Error();
|
|
239
|
+
if (ref.groups.length !== 1)
|
|
240
|
+
throw Error();
|
|
241
|
+
const [group] = ref.groups;
|
|
242
|
+
if (group.title !== 'Properties')
|
|
243
|
+
throw Error();
|
|
244
|
+
const properties = group.children.map(r => r.escapedName + ':?');
|
|
245
|
+
return `{${properties.join(', ')}}`;
|
|
246
|
+
}
|
|
247
|
+
if (!ref.groups && ref.signatures) {
|
|
248
|
+
if (ref.signatures.length !== 1)
|
|
249
|
+
throw Error('ref.signatures.length !== 1');
|
|
250
|
+
const [signature] = ref.signatures;
|
|
251
|
+
const returnType = signature.type ? getTypeRec(signature.type) : 'void';
|
|
252
|
+
const parameters = (signature.parameters ?? [])
|
|
253
|
+
.map(p => {
|
|
254
|
+
return p.name + (p.type ? ': ' + getTypeRec(p.type) : '');
|
|
255
|
+
}).join(', ');
|
|
256
|
+
return `(${parameters}) => ${returnType}`;
|
|
257
|
+
}
|
|
174
258
|
throw Error();
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
261
|
+
console.dir(ref, { depth: 3 });
|
|
262
|
+
throw error;
|
|
263
|
+
}
|
|
175
264
|
}
|
|
176
265
|
}
|
|
177
266
|
}
|
|
178
|
-
function
|
|
179
|
-
if (!
|
|
180
|
-
return
|
|
181
|
-
if (
|
|
182
|
-
throw Error();
|
|
183
|
-
const [source] =
|
|
267
|
+
function createSourceLink(reference) {
|
|
268
|
+
if (!reference.sources || reference.sources.length < 1)
|
|
269
|
+
return null;
|
|
270
|
+
if (reference.sources.length > 1)
|
|
271
|
+
throw Error('ref.sources.length > 1');
|
|
272
|
+
const [source] = reference.sources;
|
|
184
273
|
return `<sup><a href="${source.url}">[src]</a></sup>`;
|
|
185
274
|
}
|
|
186
|
-
function
|
|
187
|
-
|
|
188
|
-
switch (ref.kind) {
|
|
189
|
-
case ReflectionKind.Class:
|
|
190
|
-
typeName = 'class';
|
|
191
|
-
break;
|
|
192
|
-
case ReflectionKind.Interface:
|
|
193
|
-
typeName = 'interface';
|
|
194
|
-
break;
|
|
195
|
-
case ReflectionKind.TypeAlias:
|
|
196
|
-
typeName = 'type';
|
|
197
|
-
break;
|
|
198
|
-
default:
|
|
199
|
-
console.log(ref);
|
|
200
|
-
throw new Error('Unknown reflection kind');
|
|
201
|
-
}
|
|
202
|
-
return `${typeName}_${ref.name}`.toLowerCase();
|
|
275
|
+
function createAnchorId(reference) {
|
|
276
|
+
return `${getDeclarationKindName(reference.kind)}_${reference.name}`.toLowerCase();
|
|
203
277
|
}
|
|
204
278
|
//# sourceMappingURL=typedoc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function getErrorMessage(error: unknown): string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@versatiles/release-tool",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "VersaTiles release and documentation tools",
|
|
5
5
|
"bin": {
|
|
6
6
|
"vrt": "./dist/index.js"
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"check": "npm run lint && npm run test && npm run build",
|
|
15
15
|
"doc": "npx vrt cmd2md vrt | npx vrt insertmd README.md '# Command'",
|
|
16
16
|
"lint": "eslint . --color",
|
|
17
|
-
"
|
|
17
|
+
"prepack": "npm run build && npm run doc",
|
|
18
18
|
"test": "cd ..; NODE_OPTIONS=--experimental-vm-modules jest --testPathPattern versatiles-release-tool"
|
|
19
19
|
},
|
|
20
20
|
"author": "yetzt <node@yetzt.me>, Michael Kreil <versatiles@michael-kreil.de>",
|
|
@@ -26,13 +26,14 @@
|
|
|
26
26
|
},
|
|
27
27
|
"homepage": "https://github.com/versatiles-org/node-versatiles/blob/main/versatiles-release-tool/README.md",
|
|
28
28
|
"devDependencies": {
|
|
29
|
-
"@types/node": "^20.
|
|
30
|
-
"tsx": "^4.
|
|
29
|
+
"@types/node": "^20.10.0",
|
|
30
|
+
"tsx": "^4.4.0",
|
|
31
31
|
"typescript": "^5.2.2"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"commander": "^11.1.0",
|
|
35
35
|
"remark": "^15.0.1",
|
|
36
|
+
"remark-gfm": "^4.0.0",
|
|
36
37
|
"typedoc": "^0.25.3"
|
|
37
38
|
}
|
|
38
39
|
}
|