@versatiles/release-tool 1.0.1 → 1.0.2
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 +49 -62
- 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,31 @@
|
|
|
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
|
-
for (const
|
|
21
|
-
yield*
|
|
20
|
+
for (const item of group.children) {
|
|
21
|
+
yield* documentDeclaration(item);
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
|
-
function*
|
|
26
|
-
const
|
|
27
|
-
yield `# ${
|
|
28
|
-
yield*
|
|
25
|
+
function* documentDeclaration(declaration) {
|
|
26
|
+
const declarationType = getDeclarationTypeName(declaration.kind);
|
|
27
|
+
yield `# ${declarationType}: \`${declaration.name}\`<a id="${createAnchorId(declaration)}"></a>`;
|
|
28
|
+
yield* documentSummaryBlock(declaration);
|
|
29
29
|
for (const group of declaration.groups ?? []) {
|
|
30
30
|
const publicMembers = group.children.filter(member => !member.flags.isPrivate && !member.flags.isProtected);
|
|
31
31
|
if (publicMembers.length === 0)
|
|
@@ -35,20 +35,20 @@ function* renderDeclaration(declaration) {
|
|
|
35
35
|
switch (group.title) {
|
|
36
36
|
case 'Constructors':
|
|
37
37
|
if (publicMembers.length !== 1)
|
|
38
|
-
throw Error();
|
|
39
|
-
yield*
|
|
38
|
+
throw Error('publicMembers.length !== 1');
|
|
39
|
+
yield* documentMethod(publicMembers[0], true);
|
|
40
40
|
continue;
|
|
41
41
|
case 'Properties':
|
|
42
42
|
//yield '';
|
|
43
43
|
//yield '**Properties**';
|
|
44
44
|
for (const member of publicMembers)
|
|
45
|
-
yield
|
|
45
|
+
yield documentProperty(member);
|
|
46
46
|
continue;
|
|
47
47
|
case 'Methods':
|
|
48
48
|
//yield '';
|
|
49
49
|
//yield '**Methods**';
|
|
50
50
|
for (const member of publicMembers)
|
|
51
|
-
yield*
|
|
51
|
+
yield* documentMethod(member);
|
|
52
52
|
continue;
|
|
53
53
|
default:
|
|
54
54
|
console.log(group);
|
|
@@ -56,45 +56,47 @@ function* renderDeclaration(declaration) {
|
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
if (declaration.type) {
|
|
59
|
-
yield `\n**Type:** <code>${
|
|
59
|
+
yield `\n**Type:** <code>${formatTypeDeclaration(declaration.type)}</code>`;
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
|
-
function*
|
|
63
|
-
if (
|
|
62
|
+
function* documentMethod(method, isConstructor = false) {
|
|
63
|
+
if (method.signatures?.length !== 1)
|
|
64
64
|
throw Error('should be 1');
|
|
65
|
-
const [signature] =
|
|
66
|
-
const
|
|
65
|
+
const [signature] = method.signatures;
|
|
66
|
+
const methodName = signature.name;
|
|
67
67
|
const parameters = formatMethodParameters(signature.parameters ?? []);
|
|
68
68
|
const returnType = signature.type;
|
|
69
|
-
const
|
|
70
|
-
yield `## ${
|
|
69
|
+
const methodType = isConstructor ? 'Constructor' : 'Method';
|
|
70
|
+
yield `## ${methodType}: \`${methodName}(${parameters})\``;
|
|
71
71
|
yield '';
|
|
72
|
-
yield*
|
|
72
|
+
yield* documentSummaryBlock(signature);
|
|
73
73
|
if (signature.parameters && signature.parameters.length > 0) {
|
|
74
74
|
yield '';
|
|
75
75
|
yield '**Parameters:**';
|
|
76
76
|
for (const parameter of signature.parameters) {
|
|
77
|
-
yield
|
|
77
|
+
yield documentProperty(parameter);
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
if (returnType) {
|
|
81
81
|
yield '';
|
|
82
|
-
yield `**Returns:** <code>${
|
|
82
|
+
yield `**Returns:** <code>${formatTypeDeclaration(returnType)}</code>`;
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
function formatMethodParameters(parameters) {
|
|
86
86
|
return parameters.map(param => param.name).join(', ');
|
|
87
87
|
}
|
|
88
88
|
// Helper Functions
|
|
89
|
-
function
|
|
89
|
+
function getDeclarationTypeName(kind) {
|
|
90
90
|
switch (kind) {
|
|
91
91
|
case ReflectionKind.Class: return 'Class';
|
|
92
|
+
case ReflectionKind.Function: return 'Function';
|
|
92
93
|
case ReflectionKind.Interface: return 'Interface';
|
|
93
94
|
case ReflectionKind.TypeAlias: return 'Type';
|
|
95
|
+
case ReflectionKind.Variable: return 'Variable';
|
|
94
96
|
default: throw new Error(`Unknown reflection kind: ${kind}`);
|
|
95
97
|
}
|
|
96
98
|
}
|
|
97
|
-
function
|
|
99
|
+
function documentProperty(ref) {
|
|
98
100
|
let line = ` - <code>${ref.name}${resolveTypeDeclaration(ref.type)}</code>`;
|
|
99
101
|
if (ref.flags.isOptional)
|
|
100
102
|
line += ' (optional)';
|
|
@@ -108,7 +110,7 @@ function extractSummary(comment) {
|
|
|
108
110
|
return '';
|
|
109
111
|
return comment.summary.map(line => line.text).join('');
|
|
110
112
|
}
|
|
111
|
-
function*
|
|
113
|
+
function* documentSummaryBlock(ref) {
|
|
112
114
|
yield '';
|
|
113
115
|
if (ref.comment) {
|
|
114
116
|
yield formatComment(ref.comment);
|
|
@@ -117,25 +119,25 @@ function* renderSummaryBlock(ref) {
|
|
|
117
119
|
const { type } = ref;
|
|
118
120
|
if (type?.type === 'reflection') {
|
|
119
121
|
if (type.declaration.signatures?.length !== 1)
|
|
120
|
-
throw Error();
|
|
122
|
+
throw Error('type.declaration.signatures?.length !== 1');
|
|
121
123
|
const [signature] = type.declaration.signatures;
|
|
122
124
|
if (signature.comment) {
|
|
123
125
|
yield formatComment(signature.comment);
|
|
124
126
|
return;
|
|
125
127
|
}
|
|
126
128
|
}
|
|
127
|
-
yield
|
|
129
|
+
yield createSourceLink(ref) + '\n';
|
|
128
130
|
return;
|
|
129
131
|
function formatComment(comment) {
|
|
130
|
-
return (extractSummary(comment) + ' ' +
|
|
132
|
+
return (extractSummary(comment) + ' ' + createSourceLink(ref)).replace(/\n/m, ' \n') + '\n';
|
|
131
133
|
}
|
|
132
134
|
}
|
|
133
135
|
function resolveTypeDeclaration(someType) {
|
|
134
136
|
if (!someType)
|
|
135
137
|
return '';
|
|
136
|
-
return `: ${
|
|
138
|
+
return `: ${formatTypeDeclaration(someType)}`;
|
|
137
139
|
}
|
|
138
|
-
function
|
|
140
|
+
function formatTypeDeclaration(someType) {
|
|
139
141
|
return getTypeRec(someType);
|
|
140
142
|
function getTypeRec(some) {
|
|
141
143
|
switch (some.type) {
|
|
@@ -146,7 +148,7 @@ function formatType(someType) {
|
|
|
146
148
|
case 'reference':
|
|
147
149
|
let result = some.name;
|
|
148
150
|
if (some.reflection)
|
|
149
|
-
result = `[${result}](#${
|
|
151
|
+
result = `[${result}](#${createAnchorId(some.reflection)})`;
|
|
150
152
|
if (some.typeArguments?.length ?? 0)
|
|
151
153
|
result += '<'
|
|
152
154
|
+ (some.typeArguments ?? [])
|
|
@@ -155,9 +157,9 @@ function formatType(someType) {
|
|
|
155
157
|
return result;
|
|
156
158
|
case 'reflection':
|
|
157
159
|
if (!some.declaration.signatures)
|
|
158
|
-
throw Error();
|
|
160
|
+
throw Error('!some.declaration.signatures');
|
|
159
161
|
if (some.declaration.signatures.length !== 1)
|
|
160
|
-
throw Error();
|
|
162
|
+
throw Error('some.declaration.signatures.length !== 1');
|
|
161
163
|
const [signature] = some.declaration.signatures;
|
|
162
164
|
const type = signature.type ? getTypeRec(signature.type) : 'void';
|
|
163
165
|
const parameters = (signature.parameters ?? [])
|
|
@@ -171,34 +173,19 @@ function formatType(someType) {
|
|
|
171
173
|
return some.types.map(getTypeRec).join(' | ');
|
|
172
174
|
default:
|
|
173
175
|
console.log(some);
|
|
174
|
-
throw Error();
|
|
176
|
+
throw Error(some.type);
|
|
175
177
|
}
|
|
176
178
|
}
|
|
177
179
|
}
|
|
178
|
-
function
|
|
179
|
-
if (!
|
|
180
|
+
function createSourceLink(reference) {
|
|
181
|
+
if (!reference.sources || reference.sources.length < 1)
|
|
180
182
|
return '';
|
|
181
|
-
if (
|
|
182
|
-
throw Error();
|
|
183
|
-
const [source] =
|
|
183
|
+
if (reference.sources.length > 1)
|
|
184
|
+
throw Error('ref.sources.length > 1');
|
|
185
|
+
const [source] = reference.sources;
|
|
184
186
|
return `<sup><a href="${source.url}">[src]</a></sup>`;
|
|
185
187
|
}
|
|
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();
|
|
188
|
+
function createAnchorId(reference) {
|
|
189
|
+
return `${getDeclarationTypeName(reference.kind)}_${reference.name}`.toLowerCase();
|
|
203
190
|
}
|
|
204
191
|
//# 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.2",
|
|
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
|
}
|