@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 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 { generateMarkdownDocumentation } from './lib/typedoc.js';
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 generateMarkdownDocumentation([tsFilename], tsConfig);
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')
@@ -1 +1,6 @@
1
- export declare function generateCommandDocumentation(command: string): string;
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>;
@@ -1,42 +1,83 @@
1
- import { spawnSync } from 'node:child_process';
2
- export function generateCommandDocumentation(command) {
3
- const result = getCommandResults(command);
4
- let { markdown } = result;
5
- const { subcommands } = result;
6
- for (const subcommand of subcommands) {
7
- const subResult = getCommandResults(command + ' ' + subcommand);
8
- markdown += `\n# Subcommand: \`${command} ${subcommand}\`\n\n${subResult.markdown}`;
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
- function getCommandResults(command) {
13
- const cp = spawnSync('npx', [...command.split(' '), '--help']);
14
- if (cp.error)
15
- throw Error(cp.error.toString());
16
- const result = cp.stdout.toString().trim();
17
- const subcommands = result
18
- .replace(/.*\nCommands:/msgi, '')
19
- .replace(/\n[a-z]+:.*/msi, '')
20
- .split('\n')
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
- // eslint-disable-next-line @typescript-eslint/prefer-destructuring
26
- const subcommand = extract[1];
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
@@ -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;
@@ -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
- const documentAst = remark().parse(document);
5
- const segmentAst = remark().parse(segment);
6
- const headingAst = remark().parse(heading);
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
- startIndex = findSegmentStart(documentAst, headingAst);
22
+ // Find the start index where the new segment should be injected.
23
+ startIndex = findSegmentStartIndex(documentAst, headingAst);
10
24
  }
11
25
  catch (error) {
12
- console.error(`While searching for segment "${heading}" …`);
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
- const endIndex = findNextHeading(documentAst, startIndex + 1, depth);
17
- indentChapter(segmentAst, depth);
18
- if (foldable ?? false)
19
- makeFoldable(segmentAst);
20
- spliceAst(documentAst, segmentAst, startIndex + 1, endIndex);
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
- const mainAst = remark().parse(main);
25
- const headingText = getMDText(remark().parse(heading));
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
- if (c.type !== 'heading')
29
- return [];
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}* [${text}](#${anchor})\n`;
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
- function findSegmentStart(mainAst, headingAst) {
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 = getMDText(headingAst);
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) && getMDText(c).startsWith(sectionText)) {
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
- function findNextHeading(mainAst, startIndex, depth) {
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
- if (child.type !== 'heading')
63
- continue;
64
- if (child.depth !== depth)
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
- function indentChapter(segmentAst, depth) {
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 spliceAst(mainAst, segmentAst, startIndex, endIndex) {
162
+ function mergeSegments(mainAst, segmentAst, startIndex, endIndex) {
96
163
  mainAst.children.splice(startIndex, endIndex - startIndex, ...segmentAst.children);
97
164
  }
98
- function getMDText(node) {
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(getMDText).join('');
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
- function makeFoldable(ast) {
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(child) {
167
- const html = child.children.map(c => {
168
- switch (c.type) {
169
- case 'html': return c.value;
170
- case 'text': return c.value;
171
- case 'inlineCode': return `<code>${textToHtml(c.value)}</code>`;
172
- //case "break": { throw new Error('Not implemented yet: "break" case') }
173
- //case "delete": { throw new Error('Not implemented yet: "delete" case') }
174
- //case "emphasis": { throw new Error('Not implemented yet: "emphasis" case') }
175
- //case "footnoteReference": { throw new Error('Not implemented yet: "footnoteReference" case') }
176
- //case "image": { throw new Error('Not implemented yet: "image" case') }
177
- //case "imageReference": { throw new Error('Not implemented yet: "imageReference" case') }
178
- //case "inlineCode": { throw new Error('Not implemented yet: "inlineCode" case') }
179
- //case "link": { throw new Error('Not implemented yet: "link" case') }
180
- //case "linkReference": { throw new Error('Not implemented yet: "linkReference" case') }
181
- //case "strong": { throw new Error('Not implemented yet: "strong" case') }
182
- //case "text": { throw new Error('Not implemented yet: "text" case') }
183
- default:
184
- console.log(c);
185
- throw Error(`unknown type "${c.type}"`);
186
- }
187
- });
188
- return `<h${child.depth}>${html.join('')}</h${child.depth}>`;
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
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Generate markdown documentation from TypeScript files.
3
- * @param entryPoints - Array of absolute TypeScript file paths.
4
- * @param tsconfig - Absolute file path of tsconfig.json.
3
+ * @param sourceFilePaths - Array of absolute TypeScript file paths.
4
+ * @param tsConfigPath - Absolute file path of tsconfig.json.
5
5
  */
6
- export declare function generateMarkdownDocumentation(entryPoints: string[], tsconfig: string): Promise<string>;
6
+ export declare function generateTsMarkdownDoc(sourceFilePaths: string[], tsConfigPath: string): Promise<string>;
@@ -1,31 +1,31 @@
1
1
  import { Application, ReflectionKind, } from 'typedoc';
2
2
  /**
3
3
  * Generate markdown documentation from TypeScript files.
4
- * @param entryPoints - Array of absolute TypeScript file paths.
5
- * @param tsconfig - Absolute file path of tsconfig.json.
4
+ * @param sourceFilePaths - Array of absolute TypeScript file paths.
5
+ * @param tsConfigPath - Absolute file path of tsconfig.json.
6
6
  */
7
- export async function generateMarkdownDocumentation(entryPoints, tsconfig) {
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(renderProjectDocumentation(project)).join('\n');
13
+ return Array.from(documentProject(project)).join('\n');
14
14
  }
15
- function* renderProjectDocumentation(project) {
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 declaration of group.children) {
21
- yield* renderDeclaration(declaration);
20
+ for (const item of group.children) {
21
+ yield* documentDeclaration(item);
22
22
  }
23
23
  }
24
24
  }
25
- function* renderDeclaration(declaration) {
26
- const typeName = formatTypeName(declaration.kind);
27
- yield `# ${typeName}: \`${declaration.name}\`<a id="${generateAnchor(declaration)}"></a>`;
28
- yield* renderSummaryBlock(declaration);
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* renderMethod(publicMembers[0], true);
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 formatParameter(member);
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* renderMethod(member);
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>${formatType(declaration.type)}</code>`;
59
+ yield `\n**Type:** <code>${formatTypeDeclaration(declaration.type)}</code>`;
60
60
  }
61
61
  }
62
- function* renderMethod(declaration, isConstructor = false) {
63
- if (declaration.signatures?.length !== 1)
62
+ function* documentMethod(method, isConstructor = false) {
63
+ if (method.signatures?.length !== 1)
64
64
  throw Error('should be 1');
65
- const [signature] = declaration.signatures;
66
- const functionName = signature.name;
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 prefix = isConstructor ? 'Constructor' : 'Method';
70
- yield `## ${prefix}: \`${functionName}(${parameters})\``;
69
+ const methodType = isConstructor ? 'Constructor' : 'Method';
70
+ yield `## ${methodType}: \`${methodName}(${parameters})\``;
71
71
  yield '';
72
- yield* renderSummaryBlock(signature);
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 formatParameter(parameter);
77
+ yield documentProperty(parameter);
78
78
  }
79
79
  }
80
80
  if (returnType) {
81
81
  yield '';
82
- yield `**Returns:** <code>${formatType(returnType)}</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 formatTypeName(kind) {
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 formatParameter(ref) {
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* renderSummaryBlock(ref) {
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 generateSourceLink(ref) + '\n';
129
+ yield createSourceLink(ref) + '\n';
128
130
  return;
129
131
  function formatComment(comment) {
130
- return (extractSummary(comment) + ' ' + generateSourceLink(ref)).replace(/\n/m, ' \n') + '\n';
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 `: ${formatType(someType)}`;
138
+ return `: ${formatTypeDeclaration(someType)}`;
137
139
  }
138
- function formatType(someType) {
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}](#${generateAnchor(some.reflection)})`;
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 generateSourceLink(ref) {
179
- if (!ref.sources || ref.sources.length < 1)
180
+ function createSourceLink(reference) {
181
+ if (!reference.sources || reference.sources.length < 1)
180
182
  return '';
181
- if (ref.sources.length > 1)
182
- throw Error();
183
- const [source] = ref.sources;
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 generateAnchor(ref) {
187
- let typeName;
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;
@@ -0,0 +1,10 @@
1
+ export function getErrorMessage(error) {
2
+ if (error == null)
3
+ return 'unknown';
4
+ if (typeof error === 'object') {
5
+ if ('message' in error)
6
+ return String(error.message);
7
+ }
8
+ return 'unknown';
9
+ }
10
+ //# sourceMappingURL=utils.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@versatiles/release-tool",
3
- "version": "1.0.1",
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
- "prepublish": "npm run build && npm run doc",
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.9.2",
30
- "tsx": "^4.1.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
  }