@versatiles/release-tool 2.5.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -4
- package/dist/commands/check.js +2 -2
- package/dist/commands/deps-graph.js +1 -1
- package/dist/commands/doc-command.js +6 -6
- package/dist/commands/doc-typescript.js +1 -5
- package/dist/commands/markdown.js +31 -15
- package/dist/commands/release-npm.d.ts +1 -1
- package/dist/commands/release-npm.js +49 -28
- package/dist/index.js +28 -13
- package/dist/lib/git.js +8 -8
- package/dist/lib/log.d.ts +4 -1
- package/dist/lib/log.js +13 -2
- package/dist/lib/shell.d.ts +6 -0
- package/dist/lib/shell.js +37 -27
- package/dist/lib/utils.js +14 -2
- package/package.json +19 -13
package/README.md
CHANGED
|
@@ -51,6 +51,7 @@ Node.js/TypeScript projects.
|
|
|
51
51
|
|
|
52
52
|
Options:
|
|
53
53
|
-h, --help display help for command
|
|
54
|
+
-v, --verbose Enable verbose output
|
|
54
55
|
|
|
55
56
|
Commands:
|
|
56
57
|
check Check repo for required scripts and other stuff.
|
|
@@ -61,7 +62,7 @@ Commands:
|
|
|
61
62
|
doc-toc <readme> [heading] Generate a Table of Contents (TOC) in a Markdown file.
|
|
62
63
|
doc-typescript [options] Generate documentation for a TypeScript project.
|
|
63
64
|
help [command] display help for command
|
|
64
|
-
release-npm [path]
|
|
65
|
+
release-npm [options] [path] Publish an npm package from the specified path to the npm registry.
|
|
65
66
|
```
|
|
66
67
|
|
|
67
68
|
## Subcommand: `vrt check`
|
|
@@ -178,11 +179,12 @@ Usage: vrt release-npm [options] [path]
|
|
|
178
179
|
Publish an npm package from the specified path to the npm registry.
|
|
179
180
|
|
|
180
181
|
Arguments:
|
|
181
|
-
path
|
|
182
|
-
|
|
182
|
+
path Root path of the Node.js project. Defaults to the current
|
|
183
|
+
directory.
|
|
183
184
|
|
|
184
185
|
Options:
|
|
185
|
-
-h, --help
|
|
186
|
+
-h, --help display help for command
|
|
187
|
+
-n, --dry-run Show what would be done without making any changes
|
|
186
188
|
```
|
|
187
189
|
|
|
188
190
|
# Development
|
|
@@ -220,6 +222,7 @@ end
|
|
|
220
222
|
5-->4
|
|
221
223
|
6-->4
|
|
222
224
|
6-->7
|
|
225
|
+
7-->4
|
|
223
226
|
8-->9
|
|
224
227
|
A-->4
|
|
225
228
|
B-->9
|
|
@@ -234,6 +237,7 @@ E-->8
|
|
|
234
237
|
E-->A
|
|
235
238
|
E-->B
|
|
236
239
|
E-->C
|
|
240
|
+
E-->4
|
|
237
241
|
|
|
238
242
|
class 0,1,3 subgraphs;
|
|
239
243
|
classDef subgraphs fill-opacity:0.1, fill:#888, color:#888, stroke:#888;
|
package/dist/commands/check.js
CHANGED
|
@@ -17,8 +17,8 @@ export function checkPackage(directory) {
|
|
|
17
17
|
info('scripts.doc is recommended');
|
|
18
18
|
if (!scripts.build)
|
|
19
19
|
warn('scripts.build is required');
|
|
20
|
-
else if (!scripts.build.includes(
|
|
21
|
-
warn(`scripts.build should include "npm run doc
|
|
20
|
+
else if (!scripts.build.includes('npm run doc')) {
|
|
21
|
+
warn(`scripts.build should include "npm run doc", but is "${scripts.build}"`);
|
|
22
22
|
}
|
|
23
23
|
if (!scripts.check)
|
|
24
24
|
warn('scripts.check is required');
|
|
@@ -6,7 +6,7 @@ export async function generateDependencyGraph(directory) {
|
|
|
6
6
|
cruiseResult = await cruise([directory], {
|
|
7
7
|
includeOnly: '^src',
|
|
8
8
|
outputType: 'mermaid',
|
|
9
|
-
exclude: [
|
|
9
|
+
exclude: ['\\.(test|d)\\.ts$', 'node_modules', '__mocks__/'],
|
|
10
10
|
});
|
|
11
11
|
}
|
|
12
12
|
catch (pError) {
|
|
@@ -36,18 +36,18 @@ async function getCommandResults(command) {
|
|
|
36
36
|
NODE_ENV: undefined,
|
|
37
37
|
NODE_DISABLE_COLORS: '1',
|
|
38
38
|
NO_COLORS: '1',
|
|
39
|
-
FORCE_COLOR: '0'
|
|
39
|
+
FORCE_COLOR: '0',
|
|
40
40
|
};
|
|
41
41
|
// Spawn a child process to run the command with the '--help' flag.
|
|
42
42
|
const childProcess = cp.spawn('npm', ['--offline', 'exec', '--', ...command.split(' '), '--help'], { env });
|
|
43
43
|
let output = '';
|
|
44
44
|
// Collect output data from the process.
|
|
45
|
-
childProcess.stdout.on('data', data => output += String(data));
|
|
46
|
-
childProcess.stderr.on('data', data => {
|
|
45
|
+
childProcess.stdout.on('data', (data) => (output += String(data)));
|
|
46
|
+
childProcess.stderr.on('data', (data) => {
|
|
47
47
|
console.error(`stderr: ${data}`);
|
|
48
48
|
});
|
|
49
49
|
// Handle process errors.
|
|
50
|
-
childProcess.on('error', error => {
|
|
50
|
+
childProcess.on('error', (error) => {
|
|
51
51
|
reject(new Error(`Failed to start subprocess: ${error.message}`));
|
|
52
52
|
});
|
|
53
53
|
// Handle process exit.
|
|
@@ -72,8 +72,8 @@ async function getCommandResults(command) {
|
|
|
72
72
|
*/
|
|
73
73
|
function extractSubcommands(result) {
|
|
74
74
|
return result
|
|
75
|
-
.replace(/.*\nCommands:/
|
|
76
|
-
.replace(/\n[a-z]+:.*/
|
|
75
|
+
.replace(/.*\nCommands:/gims, '') // Remove everything before the "Commands:" section.
|
|
76
|
+
.replace(/\n[a-z]+:.*/ims, '') // Remove everything after the subcommands list.
|
|
77
77
|
.split('\n') // Split by newline to process each line.
|
|
78
78
|
.flatMap((line) => {
|
|
79
79
|
// Extract subcommand names from each line.
|
|
@@ -16,11 +16,7 @@ export async function generateTypescriptDocs(options) {
|
|
|
16
16
|
logLevel: quiet ? 'Warn' : 'Info',
|
|
17
17
|
highlightLanguages: ['typescript', 'javascript', 'json', 'shell', 'bash', 'sh', 'css', 'html'],
|
|
18
18
|
groupOrder: ['Classes', 'Variables', 'Functions', '*'],
|
|
19
|
-
}, [
|
|
20
|
-
new td.TypeDocReader(),
|
|
21
|
-
new td.PackageJsonReader(),
|
|
22
|
-
new td.TSConfigReader(),
|
|
23
|
-
]);
|
|
19
|
+
}, [new td.TypeDocReader(), new td.PackageJsonReader(), new td.TSConfigReader()]);
|
|
24
20
|
app.options.setValue('readme', 'none');
|
|
25
21
|
if (isMarkdown) {
|
|
26
22
|
app.options.setValue('hidePageHeader', true);
|
|
@@ -2,6 +2,18 @@ import { remark } from 'remark';
|
|
|
2
2
|
import remarkGfm from 'remark-gfm';
|
|
3
3
|
import remarkStringify from 'remark-stringify';
|
|
4
4
|
import { getErrorMessage } from '../lib/utils.js';
|
|
5
|
+
// Custom blockquote handler that preserves GitHub alert syntax
|
|
6
|
+
function blockquoteHandler(node, _parent, state, info) {
|
|
7
|
+
const exit = state.enter('blockquote');
|
|
8
|
+
const tracker = state.createTracker(info);
|
|
9
|
+
tracker.move('> ');
|
|
10
|
+
tracker.shift(2);
|
|
11
|
+
let value = state.indentLines(state.containerFlow(node, tracker.current()), (line, _index, blank) => '>' + (blank ? '' : ' ') + line);
|
|
12
|
+
exit();
|
|
13
|
+
// Unescape GitHub alert markers that remark-stringify escapes
|
|
14
|
+
value = value.replace(/^(>\s*)\\\[!(NOTE|WARNING|TIP|IMPORTANT|CAUTION)\]/m, '$1[!$2]');
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
5
17
|
/**
|
|
6
18
|
* Injects a Markdown segment under a specified heading in a Markdown document.
|
|
7
19
|
* Optionally, the injected segment can be made foldable for better readability.
|
|
@@ -45,6 +57,9 @@ export function injectMarkdown(document, segment, heading, foldable) {
|
|
|
45
57
|
.use(remarkStringify, {
|
|
46
58
|
bullet: '-',
|
|
47
59
|
rule: '-',
|
|
60
|
+
handlers: {
|
|
61
|
+
blockquote: blockquoteHandler,
|
|
62
|
+
},
|
|
48
63
|
})
|
|
49
64
|
.stringify(documentAst);
|
|
50
65
|
return result;
|
|
@@ -62,7 +77,7 @@ export function updateTOC(main, heading) {
|
|
|
62
77
|
const headingText = extractTextFromMDAsHTML(parseMarkdown(heading));
|
|
63
78
|
// Build the TOC by iterating over each heading in the document.
|
|
64
79
|
const toc = mainAst.children
|
|
65
|
-
.flatMap(c => {
|
|
80
|
+
.flatMap((c) => {
|
|
66
81
|
// Skip non-heading nodes and the specified heading.
|
|
67
82
|
if (c.type !== 'heading' || extractTextFromMDAsHTML(c) === headingText)
|
|
68
83
|
return [];
|
|
@@ -89,12 +104,12 @@ function findSegmentStartIndex(mainAst, headingAst) {
|
|
|
89
104
|
if (headingAst.children.length !== 1)
|
|
90
105
|
throw Error('headingAst.children.length !== 1');
|
|
91
106
|
if (headingAst.children[0].type !== 'heading')
|
|
92
|
-
throw Error(
|
|
107
|
+
throw Error("headingAst.children[0].type !== 'heading'");
|
|
93
108
|
const sectionDepth = headingAst.children[0].depth;
|
|
94
109
|
const sectionText = extractTextFromMDAsHTML(headingAst);
|
|
95
110
|
// Search for the index of the heading in the main document AST.
|
|
96
111
|
const indexes = mainAst.children.flatMap((c, index) => {
|
|
97
|
-
if (
|
|
112
|
+
if (c.type === 'heading' && c.depth === sectionDepth && extractTextFromMDAsHTML(c).startsWith(sectionText)) {
|
|
98
113
|
return [index];
|
|
99
114
|
}
|
|
100
115
|
return [];
|
|
@@ -133,7 +148,7 @@ function findNextHeadingIndex(mainAst, startIndex, depth) {
|
|
|
133
148
|
function getHeadingDepth(mainAst, index) {
|
|
134
149
|
const node = mainAst.children[index];
|
|
135
150
|
if (node.type !== 'heading')
|
|
136
|
-
throw Error(
|
|
151
|
+
throw Error("node.type !== 'heading'");
|
|
137
152
|
return node.depth;
|
|
138
153
|
}
|
|
139
154
|
/**
|
|
@@ -142,9 +157,9 @@ function getHeadingDepth(mainAst, index) {
|
|
|
142
157
|
* @param depth The depth to which the segment should be indented.
|
|
143
158
|
*/
|
|
144
159
|
function indentSegmentToDepth(segmentAst, depth) {
|
|
145
|
-
segmentAst.children.forEach(node => {
|
|
160
|
+
segmentAst.children.forEach((node) => {
|
|
146
161
|
if (node.type == 'heading')
|
|
147
|
-
return node.depth += depth;
|
|
162
|
+
return (node.depth += depth);
|
|
148
163
|
});
|
|
149
164
|
}
|
|
150
165
|
/**
|
|
@@ -174,7 +189,6 @@ function extractTextFromMDAsHTML(node) {
|
|
|
174
189
|
case 'html':
|
|
175
190
|
return '';
|
|
176
191
|
default:
|
|
177
|
-
console.log(node);
|
|
178
192
|
throw Error('unknown type: ' + node.type);
|
|
179
193
|
}
|
|
180
194
|
}
|
|
@@ -200,12 +214,12 @@ function getMDAnchor(node) {
|
|
|
200
214
|
text += c.value;
|
|
201
215
|
break;
|
|
202
216
|
default:
|
|
203
|
-
console.log(c);
|
|
204
217
|
throw Error('unknown type: ' + c.type);
|
|
205
218
|
}
|
|
206
219
|
}
|
|
207
220
|
// Format the text to create a suitable anchor ID.
|
|
208
|
-
text = text
|
|
221
|
+
text = text
|
|
222
|
+
.toLowerCase()
|
|
209
223
|
.replace(/[()]+/g, '')
|
|
210
224
|
.replace(/[^a-z0-9]+/g, '-')
|
|
211
225
|
.replace(/^-+|-+$/g, '');
|
|
@@ -238,7 +252,7 @@ function convertToFoldable(ast) {
|
|
|
238
252
|
closeDetails(0);
|
|
239
253
|
ast.children = children;
|
|
240
254
|
function closeDetails(depth) {
|
|
241
|
-
while (
|
|
255
|
+
while (openDetails.length > 0 && openDetails[0] >= depth) {
|
|
242
256
|
children.push({ type: 'html', value: '</details>' });
|
|
243
257
|
openDetails.shift();
|
|
244
258
|
}
|
|
@@ -273,11 +287,13 @@ export function nodeToHtml(node) {
|
|
|
273
287
|
attributes.push(`title="${node.title}"`);
|
|
274
288
|
return `<img ${attributes.join(' ')} />`;
|
|
275
289
|
}
|
|
276
|
-
case 'footnoteReference':
|
|
277
|
-
|
|
278
|
-
case '
|
|
290
|
+
case 'footnoteReference':
|
|
291
|
+
throw new Error('Not implemented yet: "footnoteReference" case');
|
|
292
|
+
case 'imageReference':
|
|
293
|
+
throw new Error('Not implemented yet: "imageReference" case');
|
|
294
|
+
case 'linkReference':
|
|
295
|
+
throw new Error('Not implemented yet: "linkReference" case');
|
|
279
296
|
default:
|
|
280
|
-
console.log(node);
|
|
281
297
|
throw Error('unknown type');
|
|
282
298
|
}
|
|
283
299
|
}
|
|
@@ -285,7 +301,7 @@ function nodesToHtml(children) {
|
|
|
285
301
|
return children.map(nodeToHtml).join('');
|
|
286
302
|
}
|
|
287
303
|
function textToHtml(text) {
|
|
288
|
-
return text.replace(/[^a-z0-9 ,.\-:_?@äöüß]/gi, c => `&#${c.charCodeAt(0)};`);
|
|
304
|
+
return text.replace(/[^a-z0-9 ,.\-:_?@äöüß]/gi, (c) => `&#${c.charCodeAt(0)};`);
|
|
289
305
|
}
|
|
290
306
|
export function parseMarkdown(document) {
|
|
291
307
|
return remark().use(remarkGfm).parse(document);
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env npx tsx
|
|
2
|
-
export declare function release(directory: string, branch?: string): Promise<void>;
|
|
2
|
+
export declare function release(directory: string, branch?: string, dryRun?: boolean): Promise<void>;
|
|
@@ -5,10 +5,19 @@ import { check, info, panic, warn } from '../lib/log.js';
|
|
|
5
5
|
import { Shell } from '../lib/shell.js';
|
|
6
6
|
import { getGit } from '../lib/git.js';
|
|
7
7
|
import { resolve } from 'path';
|
|
8
|
-
|
|
8
|
+
function isValidPackageJson(pkg) {
|
|
9
|
+
if (typeof pkg !== 'object' || pkg === null)
|
|
10
|
+
return false;
|
|
11
|
+
if (!('version' in pkg) || typeof pkg.version !== 'string')
|
|
12
|
+
return false;
|
|
13
|
+
if (!('scripts' in pkg) || typeof pkg.scripts !== 'object' || pkg.scripts === null)
|
|
14
|
+
return false;
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
export async function release(directory, branch = 'main', dryRun = false) {
|
|
9
18
|
const shell = new Shell(directory);
|
|
10
19
|
const { getCommitsBetween, getCurrentGitHubCommit, getLastGitHubTag } = getGit(directory);
|
|
11
|
-
info('starting release process');
|
|
20
|
+
info(dryRun ? 'starting release process (dry-run)' : 'starting release process');
|
|
12
21
|
// git: check if in the correct branch
|
|
13
22
|
const currentBranch = await check('get branch name', shell.stdout('git rev-parse --abbrev-ref HEAD'));
|
|
14
23
|
if (currentBranch !== branch)
|
|
@@ -18,17 +27,14 @@ export async function release(directory, branch = 'main') {
|
|
|
18
27
|
// git: pull
|
|
19
28
|
await check('git pull', shell.run('git pull -t'));
|
|
20
29
|
// check package.json
|
|
21
|
-
const
|
|
22
|
-
if (
|
|
30
|
+
const pkgRaw = JSON.parse(readFileSync(resolve(directory, 'package.json'), 'utf8'));
|
|
31
|
+
if (!isValidPackageJson(pkgRaw))
|
|
23
32
|
panic('package.json is not valid');
|
|
24
|
-
if (!('
|
|
25
|
-
panic('package.json is missing "version"');
|
|
26
|
-
if (!('scripts' in pkg) || (typeof pkg.scripts !== 'object') || (pkg.scripts == null))
|
|
27
|
-
panic('package.json is missing "scripts"');
|
|
28
|
-
if (!('check' in pkg.scripts))
|
|
33
|
+
if (!('check' in pkgRaw.scripts))
|
|
29
34
|
panic('missing npm script "check" in package.json');
|
|
30
|
-
if (!('prepack' in
|
|
35
|
+
if (!('prepack' in pkgRaw.scripts))
|
|
31
36
|
panic('missing npm script "prepack" in package.json');
|
|
37
|
+
const pkg = pkgRaw;
|
|
32
38
|
// get last version
|
|
33
39
|
const tag = await check('get last github tag', getLastGitHubTag());
|
|
34
40
|
const shaLast = tag?.sha;
|
|
@@ -40,12 +46,31 @@ export async function release(directory, branch = 'main') {
|
|
|
40
46
|
const { sha: shaCurrent } = await check('get current github commit', getCurrentGitHubCommit());
|
|
41
47
|
// handle version
|
|
42
48
|
const nextVersion = await getNewVersion(versionLastPackage);
|
|
49
|
+
// prepare release notes
|
|
50
|
+
const releaseNotes = await check('prepare release notes', getReleaseNotes(nextVersion, shaLast, shaCurrent));
|
|
51
|
+
if (dryRun) {
|
|
52
|
+
info('Dry-run mode - the following actions would be performed:');
|
|
53
|
+
info(` Version: ${versionLastPackage} -> ${nextVersion}`);
|
|
54
|
+
info(' Release notes:');
|
|
55
|
+
releaseNotes.split('\n').forEach((line) => info(` ${line}`));
|
|
56
|
+
info(' Commands that would be executed:');
|
|
57
|
+
info(' npm run check');
|
|
58
|
+
info(' npm i --package-lock-only');
|
|
59
|
+
if (!('private' in pkg) || !pkg.private) {
|
|
60
|
+
info(' npm publish --access public');
|
|
61
|
+
}
|
|
62
|
+
info(' git add .');
|
|
63
|
+
info(` git commit -m "v${nextVersion}"`);
|
|
64
|
+
info(` git tag -f -a "v${nextVersion}" -m "new release: v${nextVersion}"`);
|
|
65
|
+
info(' git push --no-verify --follow-tags');
|
|
66
|
+
info(` gh release create/edit "v${nextVersion}"`);
|
|
67
|
+
info('Dry-run complete - no changes were made');
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
43
70
|
// test
|
|
44
71
|
await check('run checks', shell.run('npm run check'));
|
|
45
72
|
// update version
|
|
46
73
|
await check('update version', setNextVersion(nextVersion));
|
|
47
|
-
// prepare release notes
|
|
48
|
-
const releaseNotes = await check('prepare release notes', getReleaseNotes(nextVersion, shaLast, shaCurrent));
|
|
49
74
|
if (!('private' in pkg) || !pkg.private) {
|
|
50
75
|
// npm publish
|
|
51
76
|
await check('npm publish', shell.runInteractive('npm publish --access public'));
|
|
@@ -56,12 +81,12 @@ export async function release(directory, branch = 'main') {
|
|
|
56
81
|
await check('git tag', shell.run(`git tag -f -a "v${nextVersion}" -m "new release: v${nextVersion}"`));
|
|
57
82
|
await check('git push', shell.run('git push --no-verify --follow-tags'));
|
|
58
83
|
// github release
|
|
59
|
-
const
|
|
60
|
-
if (await check('check github release', shell.ok(
|
|
61
|
-
await check('edit release', shell.
|
|
84
|
+
const releaseTag = `v${nextVersion}`;
|
|
85
|
+
if (await check('check github release', shell.ok(`gh release view ${releaseTag}`))) {
|
|
86
|
+
await check('edit release', shell.exec('gh', ['release', 'edit', releaseTag, '--notes', releaseNotes]));
|
|
62
87
|
}
|
|
63
88
|
else {
|
|
64
|
-
await check('create release', shell.
|
|
89
|
+
await check('create release', shell.exec('gh', ['release', 'create', releaseTag, '--notes', releaseNotes]));
|
|
65
90
|
}
|
|
66
91
|
info('Finished');
|
|
67
92
|
return;
|
|
@@ -80,30 +105,26 @@ export async function release(directory, branch = 'main') {
|
|
|
80
105
|
}
|
|
81
106
|
async function getReleaseNotes(version, hashLast, hashCurrent) {
|
|
82
107
|
const commits = await getCommitsBetween(hashLast, hashCurrent);
|
|
83
|
-
let notes = commits
|
|
84
|
-
.
|
|
108
|
+
let notes = commits
|
|
109
|
+
.reverse()
|
|
110
|
+
.map((commit) => '- ' + commit.message.replace(/\s+/g, ' '))
|
|
85
111
|
.join('\n');
|
|
86
112
|
notes = `# Release v${version}\n\nchanges:\n${notes}\n\n`;
|
|
87
113
|
return notes;
|
|
88
114
|
}
|
|
89
115
|
async function getNewVersion(versionPackage) {
|
|
90
116
|
// ask for new version
|
|
91
|
-
const choices = [
|
|
92
|
-
|
|
93
|
-
{ ...bump(2) },
|
|
94
|
-
{ ...bump(1) },
|
|
95
|
-
{ ...bump(0) }
|
|
96
|
-
];
|
|
97
|
-
const versionNew = (await select({
|
|
117
|
+
const choices = [{ value: versionPackage }, { ...bump(2) }, { ...bump(1) }, { ...bump(0) }];
|
|
118
|
+
const versionNew = await select({
|
|
98
119
|
message: 'What should be the new version?',
|
|
99
120
|
choices,
|
|
100
121
|
default: choices[1].value,
|
|
101
|
-
})
|
|
122
|
+
});
|
|
102
123
|
if (!versionNew)
|
|
103
124
|
throw Error();
|
|
104
125
|
return versionNew;
|
|
105
126
|
function bump(index) {
|
|
106
|
-
const p = versionPackage.split('.').map(v => parseInt(v, 10));
|
|
127
|
+
const p = versionPackage.split('.').map((v) => parseInt(v, 10));
|
|
107
128
|
if (p.length !== 3)
|
|
108
129
|
throw Error();
|
|
109
130
|
switch (index) {
|
|
@@ -120,7 +141,7 @@ export async function release(directory, branch = 'main') {
|
|
|
120
141
|
p[2]++;
|
|
121
142
|
break;
|
|
122
143
|
}
|
|
123
|
-
const name = p.map((n, i) => (i == index
|
|
144
|
+
const name = p.map((n, i) => (i == index ? `\x1b[1m${n}` : `${n}`)).join('.') + '\x1b[22m';
|
|
124
145
|
const value = p.join('.');
|
|
125
146
|
return { name, value };
|
|
126
147
|
}
|
package/dist/index.js
CHANGED
|
@@ -11,6 +11,7 @@ import { upgradeDependencies } from './commands/deps-upgrade.js';
|
|
|
11
11
|
import { generateDependencyGraph } from './commands/deps-graph.js';
|
|
12
12
|
import { check } from './commands/check.js';
|
|
13
13
|
import { generateTypescriptDocs } from './commands/doc-typescript.js';
|
|
14
|
+
import { setVerbose } from './lib/log.js';
|
|
14
15
|
/**
|
|
15
16
|
* Main CLI program, configured with custom text styling for titles, commands, options, etc.
|
|
16
17
|
*/
|
|
@@ -26,12 +27,19 @@ program.configureHelp({
|
|
|
26
27
|
});
|
|
27
28
|
program
|
|
28
29
|
.name('vrt')
|
|
29
|
-
.description('CLI tool for releasing packages and generating documentation for Node.js/TypeScript projects.')
|
|
30
|
+
.description('CLI tool for releasing packages and generating documentation for Node.js/TypeScript projects.')
|
|
31
|
+
.option('-v, --verbose', 'Enable verbose output')
|
|
32
|
+
.hook('preAction', (thisCommand) => {
|
|
33
|
+
const opts = thisCommand.opts();
|
|
34
|
+
if (opts.verbose)
|
|
35
|
+
setVerbose(true);
|
|
36
|
+
});
|
|
30
37
|
/**
|
|
31
38
|
* Command: check-package
|
|
32
39
|
* Checks that the project's package.json includes certain required scripts/fields.
|
|
33
40
|
*/
|
|
34
|
-
program
|
|
41
|
+
program
|
|
42
|
+
.command('check')
|
|
35
43
|
.description('Check repo for required scripts and other stuff.')
|
|
36
44
|
.action(() => {
|
|
37
45
|
void check(process.cwd());
|
|
@@ -40,7 +48,8 @@ program.command('check')
|
|
|
40
48
|
* Command: deps-graph
|
|
41
49
|
* Analyzes the project’s files to produce a dependency graph (in Mermaid format).
|
|
42
50
|
*/
|
|
43
|
-
program
|
|
51
|
+
program
|
|
52
|
+
.command('deps-graph')
|
|
44
53
|
.description('Analyze project files and output a dependency graph as Mermaid markup.')
|
|
45
54
|
.action(() => {
|
|
46
55
|
void generateDependencyGraph(process.cwd());
|
|
@@ -49,7 +58,8 @@ program.command('deps-graph')
|
|
|
49
58
|
* Command: deps-upgrade
|
|
50
59
|
* Upgrades project dependencies in package.json to their latest versions and reinstalls them.
|
|
51
60
|
*/
|
|
52
|
-
program
|
|
61
|
+
program
|
|
62
|
+
.command('deps-upgrade')
|
|
53
63
|
.description('Upgrade all dependencies in the current project to their latest versions.')
|
|
54
64
|
.action(() => {
|
|
55
65
|
void upgradeDependencies(process.cwd());
|
|
@@ -58,7 +68,8 @@ program.command('deps-upgrade')
|
|
|
58
68
|
* Command: doc-command
|
|
59
69
|
* Generates Markdown documentation for a given CLI command.
|
|
60
70
|
*/
|
|
61
|
-
program
|
|
71
|
+
program
|
|
72
|
+
.command('doc-command')
|
|
62
73
|
.description('Generate Markdown documentation for a specified command and output the result.')
|
|
63
74
|
.argument('<command>', 'Command to document (e.g., "npm run build").')
|
|
64
75
|
.action(async (command) => {
|
|
@@ -70,7 +81,8 @@ program.command('doc-command')
|
|
|
70
81
|
* Inserts Markdown content from stdin into a specified Markdown file under a given heading.
|
|
71
82
|
* Optionally makes the inserted content foldable.
|
|
72
83
|
*/
|
|
73
|
-
program
|
|
84
|
+
program
|
|
85
|
+
.command('doc-insert')
|
|
74
86
|
.description('Insert Markdown from stdin into a specified section of a Markdown file.')
|
|
75
87
|
.argument('<readme>', 'Path to the target Markdown file (e.g., README.md).', checkFilename)
|
|
76
88
|
.argument('[heading]', 'Heading in the Markdown file where content should be placed. Default is "# API".', '# API')
|
|
@@ -80,8 +92,7 @@ program.command('doc-insert')
|
|
|
80
92
|
for await (const data of process.stdin) {
|
|
81
93
|
buffers.push(data);
|
|
82
94
|
}
|
|
83
|
-
const mdContent = '<!--- This chapter is generated automatically --->\n'
|
|
84
|
-
+ Buffer.concat(buffers).toString();
|
|
95
|
+
const mdContent = '<!--- This chapter is generated automatically --->\n' + Buffer.concat(buffers).toString();
|
|
85
96
|
let mdFile = readFileSync(mdFilename, 'utf8');
|
|
86
97
|
mdFile = injectMarkdown(mdFile, mdContent, heading, foldable);
|
|
87
98
|
writeFileSync(mdFilename, mdFile);
|
|
@@ -90,7 +101,8 @@ program.command('doc-insert')
|
|
|
90
101
|
* Command: doc-toc
|
|
91
102
|
* Updates or generates a Table of Contents in a Markdown file under a specified heading.
|
|
92
103
|
*/
|
|
93
|
-
program
|
|
104
|
+
program
|
|
105
|
+
.command('doc-toc')
|
|
94
106
|
.description('Generate a Table of Contents (TOC) in a Markdown file.')
|
|
95
107
|
.argument('<readme>', 'Path to the Markdown file (e.g., README.md).', checkFilename)
|
|
96
108
|
.argument('[heading]', 'Heading in the Markdown file where TOC should be inserted. Default is "# Table of Content".', '# Table of Content')
|
|
@@ -104,7 +116,8 @@ program.command('doc-toc')
|
|
|
104
116
|
* Generates documentation for a TypeScript project.
|
|
105
117
|
* Allows specifying entry point and output location.
|
|
106
118
|
*/
|
|
107
|
-
program
|
|
119
|
+
program
|
|
120
|
+
.command('doc-typescript')
|
|
108
121
|
.description('Generate documentation for a TypeScript project.')
|
|
109
122
|
.option('-i, --input <entryPoint>', 'Entry point of the TypeScript project. Default is "./src/index.ts".')
|
|
110
123
|
.option('-o, --output <outputPath>', 'Output path for the generated documentation. Default is "./docs".')
|
|
@@ -116,11 +129,13 @@ program.command('doc-typescript')
|
|
|
116
129
|
* Command: release-npm
|
|
117
130
|
* Releases/publishes an npm package from a specified project path to the npm registry.
|
|
118
131
|
*/
|
|
119
|
-
program
|
|
132
|
+
program
|
|
133
|
+
.command('release-npm')
|
|
120
134
|
.description('Publish an npm package from the specified path to the npm registry.')
|
|
135
|
+
.option('-n, --dry-run', 'Show what would be done without making any changes')
|
|
121
136
|
.argument('[path]', 'Root path of the Node.js project. Defaults to the current directory.')
|
|
122
|
-
.action((path) => {
|
|
123
|
-
void release(resolve(path ?? '.'
|
|
137
|
+
.action((path, options) => {
|
|
138
|
+
void release(resolve(process.cwd(), path ?? '.'), 'main', options.dryRun ?? false);
|
|
124
139
|
});
|
|
125
140
|
if (process.env.NODE_ENV !== 'test') {
|
|
126
141
|
await program.parseAsync();
|
package/dist/lib/git.js
CHANGED
|
@@ -9,19 +9,19 @@ export function getGit(cwd) {
|
|
|
9
9
|
async function getLastGitHubTag() {
|
|
10
10
|
const commits = await getAllCommits();
|
|
11
11
|
const result = commits
|
|
12
|
-
.map(commit => ({
|
|
12
|
+
.map((commit) => ({
|
|
13
13
|
sha: commit.sha,
|
|
14
|
-
version: commit.tag?.match(/^v(\d+\.\d+\.\d+)$/)?.[1],
|
|
14
|
+
version: commit.tag?.match(/^v(\d+\.\d+\.\d+(?:-[\w.]+)?(?:\+[\w.]+)?)$/)?.[1],
|
|
15
15
|
}))
|
|
16
|
-
.find(r => r.version);
|
|
16
|
+
.find((r) => r.version);
|
|
17
17
|
return result;
|
|
18
18
|
}
|
|
19
19
|
async function getAllCommits() {
|
|
20
|
-
const result = await shell.stdout(
|
|
20
|
+
const result = await shell.stdout("git log --pretty=format:'⍃%H⍄%s⍄%D⍄'");
|
|
21
21
|
return result
|
|
22
22
|
.split('⍃')
|
|
23
|
-
.filter(line => line.length > 2)
|
|
24
|
-
.map(line => {
|
|
23
|
+
.filter((line) => line.length > 2)
|
|
24
|
+
.map((line) => {
|
|
25
25
|
const obj = line.split('⍄');
|
|
26
26
|
return {
|
|
27
27
|
sha: obj[0],
|
|
@@ -35,10 +35,10 @@ export function getGit(cwd) {
|
|
|
35
35
|
}
|
|
36
36
|
async function getCommitsBetween(shaLast, shaCurrent) {
|
|
37
37
|
let commits = await getAllCommits();
|
|
38
|
-
const start = commits.findIndex(commit => commit.sha === shaCurrent);
|
|
38
|
+
const start = commits.findIndex((commit) => commit.sha === shaCurrent);
|
|
39
39
|
if (start >= 0)
|
|
40
40
|
commits = commits.slice(start);
|
|
41
|
-
const end = commits.findIndex(commit => commit.sha === shaLast);
|
|
41
|
+
const end = commits.findIndex((commit) => commit.sha === shaLast);
|
|
42
42
|
if (end >= 0)
|
|
43
43
|
commits = commits.slice(0, end);
|
|
44
44
|
return commits;
|
package/dist/lib/log.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
export declare function setVerbose(enabled: boolean): void;
|
|
2
|
+
export declare function isVerbose(): boolean;
|
|
1
3
|
export declare function panic(text: string): never;
|
|
2
4
|
export declare function warn(text: string): void;
|
|
3
5
|
export declare function info(text: string): void;
|
|
6
|
+
export declare function debug(text: string): void;
|
|
4
7
|
export declare function abort(): never;
|
|
5
|
-
export declare function check<T>(message: string, promise:
|
|
8
|
+
export declare function check<T>(message: string, promise: Promise<T> | (() => Promise<T>)): Promise<T>;
|
package/dist/lib/log.js
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
let verboseMode = false;
|
|
2
|
+
export function setVerbose(enabled) {
|
|
3
|
+
verboseMode = enabled;
|
|
4
|
+
}
|
|
5
|
+
export function isVerbose() {
|
|
6
|
+
return verboseMode;
|
|
7
|
+
}
|
|
1
8
|
export function panic(text) {
|
|
2
9
|
process.stderr.write(`\x1b[1;31m! ERROR: ${text}\x1b[0m\n`);
|
|
3
10
|
abort();
|
|
@@ -8,9 +15,14 @@ export function warn(text) {
|
|
|
8
15
|
export function info(text) {
|
|
9
16
|
process.stderr.write(`\x1b[0mi ${text}\n`);
|
|
10
17
|
}
|
|
18
|
+
export function debug(text) {
|
|
19
|
+
if (verboseMode) {
|
|
20
|
+
process.stderr.write(`\x1b[0;90m ${text}\x1b[0m\n`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
11
23
|
export function abort() {
|
|
12
24
|
info('abort');
|
|
13
|
-
process.exit();
|
|
25
|
+
process.exit(1);
|
|
14
26
|
}
|
|
15
27
|
export async function check(message, promise) {
|
|
16
28
|
process.stderr.write(`\x1b[0;90m\u2B95 ${message}\x1b[0m`);
|
|
@@ -22,7 +34,6 @@ export async function check(message, promise) {
|
|
|
22
34
|
catch (error) {
|
|
23
35
|
process.stderr.write(`\r\x1b[0;91m\u2718 ${message}\x1b[0m\n`);
|
|
24
36
|
panic(error.message);
|
|
25
|
-
throw error;
|
|
26
37
|
}
|
|
27
38
|
}
|
|
28
39
|
//# sourceMappingURL=log.js.map
|
package/dist/lib/shell.d.ts
CHANGED
|
@@ -7,6 +7,12 @@ export declare class Shell {
|
|
|
7
7
|
stdout: string;
|
|
8
8
|
stderr: string;
|
|
9
9
|
}>;
|
|
10
|
+
exec(command: string, args: string[], errorOnCodeNonZero?: boolean, skipLog?: boolean): Promise<{
|
|
11
|
+
code: number | null;
|
|
12
|
+
signal: string | null;
|
|
13
|
+
stdout: string;
|
|
14
|
+
stderr: string;
|
|
15
|
+
}>;
|
|
10
16
|
runInteractive(command: string, errorOnCodeNonZero?: boolean): Promise<{
|
|
11
17
|
code: number | null;
|
|
12
18
|
signal: string | null;
|
package/dist/lib/shell.js
CHANGED
|
@@ -1,38 +1,48 @@
|
|
|
1
1
|
import { spawn } from 'child_process';
|
|
2
|
+
import { debug, isVerbose } from './log.js';
|
|
2
3
|
export class Shell {
|
|
3
4
|
cwd;
|
|
4
5
|
constructor(cwd) {
|
|
5
6
|
this.cwd = cwd;
|
|
6
7
|
}
|
|
7
8
|
async run(command, errorOnCodeNonZero = true) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const result = {
|
|
16
|
-
code,
|
|
17
|
-
signal,
|
|
18
|
-
stdout: Buffer.concat(stdout).toString(),
|
|
19
|
-
stderr: Buffer.concat(stderr).toString(),
|
|
20
|
-
};
|
|
21
|
-
if (errorOnCodeNonZero && code !== 0) {
|
|
22
|
-
reject(result);
|
|
23
|
-
}
|
|
24
|
-
else {
|
|
25
|
-
resolve(result);
|
|
26
|
-
}
|
|
27
|
-
});
|
|
28
|
-
cp.stdout.on('data', chunk => stdout.push(chunk));
|
|
29
|
-
cp.stderr.on('data', chunk => stderr.push(chunk));
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
catch (error) {
|
|
33
|
-
console.error(error);
|
|
34
|
-
throw error;
|
|
9
|
+
debug(`$ ${command}`);
|
|
10
|
+
return this.exec('bash', ['-c', command], errorOnCodeNonZero, true);
|
|
11
|
+
}
|
|
12
|
+
// Execute a command with arguments directly, avoiding shell escaping issues
|
|
13
|
+
async exec(command, args, errorOnCodeNonZero = true, skipLog = false) {
|
|
14
|
+
if (!skipLog) {
|
|
15
|
+
debug(`$ ${command} ${args.join(' ')}`);
|
|
35
16
|
}
|
|
17
|
+
return await new Promise((resolve, reject) => {
|
|
18
|
+
const stdout = [];
|
|
19
|
+
const stderr = [];
|
|
20
|
+
const cp = spawn(command, args, { cwd: this.cwd })
|
|
21
|
+
.on('error', (error) => reject(error))
|
|
22
|
+
.on('close', (code, signal) => {
|
|
23
|
+
const result = {
|
|
24
|
+
code,
|
|
25
|
+
signal,
|
|
26
|
+
stdout: Buffer.concat(stdout).toString(),
|
|
27
|
+
stderr: Buffer.concat(stderr).toString(),
|
|
28
|
+
};
|
|
29
|
+
if (isVerbose()) {
|
|
30
|
+
if (result.stdout)
|
|
31
|
+
result.stdout.split('\n').forEach((line) => debug(` stdout: ${line}`));
|
|
32
|
+
if (result.stderr)
|
|
33
|
+
result.stderr.split('\n').forEach((line) => debug(` stderr: ${line}`));
|
|
34
|
+
debug(` exit code: ${code}`);
|
|
35
|
+
}
|
|
36
|
+
if (errorOnCodeNonZero && code !== 0) {
|
|
37
|
+
reject(result);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
resolve(result);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
cp.stdout.on('data', (chunk) => stdout.push(chunk));
|
|
44
|
+
cp.stderr.on('data', (chunk) => stderr.push(chunk));
|
|
45
|
+
});
|
|
36
46
|
}
|
|
37
47
|
// Runs a command interactively, so the user can interact with stdin/stdout/stderr directly.
|
|
38
48
|
async runInteractive(command, errorOnCodeNonZero = true) {
|
package/dist/lib/utils.js
CHANGED
|
@@ -24,10 +24,22 @@ export function prettyStyleJSON(inputData) {
|
|
|
24
24
|
return singleLine(data);
|
|
25
25
|
if (typeof data === 'object') {
|
|
26
26
|
if (Array.isArray(data)) {
|
|
27
|
-
return '[\n\t' +
|
|
27
|
+
return ('[\n\t' +
|
|
28
|
+
prefix +
|
|
29
|
+
data.map((value) => recursive(value, prefix + '\t', path + '[]')).join(',\n\t' + prefix) +
|
|
30
|
+
'\n' +
|
|
31
|
+
prefix +
|
|
32
|
+
']');
|
|
28
33
|
}
|
|
29
34
|
if (data) {
|
|
30
|
-
return '{\n\t' +
|
|
35
|
+
return ('{\n\t' +
|
|
36
|
+
prefix +
|
|
37
|
+
Object.entries(data)
|
|
38
|
+
.map(([key, value]) => '"' + key + '": ' + recursive(value, prefix + '\t', path + '.' + key))
|
|
39
|
+
.join(',\n\t' + prefix) +
|
|
40
|
+
'\n' +
|
|
41
|
+
prefix +
|
|
42
|
+
'}');
|
|
31
43
|
}
|
|
32
44
|
}
|
|
33
45
|
return singleLine(data);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@versatiles/release-tool",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.0",
|
|
4
4
|
"description": "VersaTiles release and documentation tools",
|
|
5
5
|
"bin": {
|
|
6
6
|
"vrt": "./dist/index.js"
|
|
@@ -12,11 +12,13 @@
|
|
|
12
12
|
"scripts": {
|
|
13
13
|
"build": "npm run build-node && npm run doc",
|
|
14
14
|
"build-node": "rm -rf dist && tsc -p tsconfig.build.json && chmod +x dist/index.js",
|
|
15
|
-
"check": "npm run lint && npm run build && npm run test",
|
|
15
|
+
"check": "npm run lint && npm run build && npm run test && npm run format:check",
|
|
16
16
|
"dev": "tsx src/index.ts",
|
|
17
17
|
"doc": "npm run doc-command && npm run doc-graph",
|
|
18
18
|
"doc-command": "tsx src/index.ts doc-command vrt | tsx src/index.ts doc-insert README.md '# Command'",
|
|
19
19
|
"doc-graph": "tsx src/index.ts deps-graph | tsx src/index.ts doc-insert README.md '## Dependency Graph'",
|
|
20
|
+
"format": "prettier --write .",
|
|
21
|
+
"format:check": "prettier --check .",
|
|
20
22
|
"lint": "eslint . --color",
|
|
21
23
|
"prepack": "npm run build",
|
|
22
24
|
"release": "tsx src/index.ts release-npm",
|
|
@@ -27,6 +29,9 @@
|
|
|
27
29
|
"author": "Michael Kreil <versatiles@michael-kreil.de>",
|
|
28
30
|
"license": "Unlicense",
|
|
29
31
|
"type": "module",
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=20"
|
|
34
|
+
},
|
|
30
35
|
"repository": {
|
|
31
36
|
"type": "git",
|
|
32
37
|
"url": "git+https://github.com/versatiles-org/node-release-tool.git"
|
|
@@ -34,24 +39,25 @@
|
|
|
34
39
|
"homepage": "https://github.com/versatiles-org/node-release-tool",
|
|
35
40
|
"devDependencies": {
|
|
36
41
|
"@schemastore/package": "^0.0.10",
|
|
37
|
-
"@types/node": "^
|
|
38
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
39
|
-
"@typescript-eslint/parser": "^8.
|
|
40
|
-
"@vitest/coverage-v8": "^4.0.
|
|
41
|
-
"eslint": "^9.39.
|
|
42
|
+
"@types/node": "^25.1.0",
|
|
43
|
+
"@typescript-eslint/eslint-plugin": "^8.54.0",
|
|
44
|
+
"@typescript-eslint/parser": "^8.54.0",
|
|
45
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
46
|
+
"eslint": "^9.39.2",
|
|
47
|
+
"prettier": "^3.8.1",
|
|
42
48
|
"tsx": "^4.21.0",
|
|
43
49
|
"typescript": "^5.9.3",
|
|
44
|
-
"typescript-eslint": "^8.
|
|
45
|
-
"vitest": "^4.0.
|
|
50
|
+
"typescript-eslint": "^8.54.0",
|
|
51
|
+
"vitest": "^4.0.18"
|
|
46
52
|
},
|
|
47
53
|
"dependencies": {
|
|
48
|
-
"@inquirer/select": "^5.0.
|
|
54
|
+
"@inquirer/select": "^5.0.4",
|
|
49
55
|
"commander": "^14.0.2",
|
|
50
|
-
"dependency-cruiser": "^17.3.
|
|
51
|
-
"npm-check-updates": "^19.
|
|
56
|
+
"dependency-cruiser": "^17.3.7",
|
|
57
|
+
"npm-check-updates": "^19.3.2",
|
|
52
58
|
"remark": "^15.0.1",
|
|
53
59
|
"remark-gfm": "^4.0.1",
|
|
54
|
-
"typedoc": "^0.28.
|
|
60
|
+
"typedoc": "^0.28.16",
|
|
55
61
|
"typedoc-github-theme": "^0.3.1",
|
|
56
62
|
"typedoc-github-wiki-theme": "^2.1.0",
|
|
57
63
|
"typedoc-plugin-markdown": "^4.9.0"
|