@versatiles/release-tool 1.0.2 → 1.1.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 +19 -0
- package/dist/{lib → commands}/command.js +4 -2
- package/dist/{lib → commands}/markdown.js +3 -10
- package/dist/commands/release.d.ts +2 -0
- package/dist/commands/release.js +134 -0
- package/dist/{lib → commands}/typedoc.js +127 -40
- package/dist/index.d.ts +2 -1
- package/dist/index.js +14 -5
- package/dist/lib/git.d.ts +14 -0
- package/dist/lib/git.js +47 -0
- package/dist/lib/log.d.ts +5 -0
- package/dist/lib/log.js +28 -0
- package/dist/lib/shell.d.ts +12 -0
- package/dist/lib/shell.js +42 -0
- package/dist/lib/utils.d.ts +1 -0
- package/dist/lib/utils.js +31 -2
- package/package.json +18 -7
- /package/dist/{lib → commands}/command.d.ts +0 -0
- /package/dist/{lib → commands}/markdown.d.ts +0 -0
- /package/dist/{lib → commands}/typedoc.d.ts +0 -0
package/README.md
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
[](https://codecov.io/gh/versatiles-org/node-release-tool)
|
|
2
|
+
[](https://github.com/versatiles-org/node-release-tool/actions/workflows/ci.yml)
|
|
3
|
+
|
|
1
4
|
# VersaTiles Release Tools
|
|
2
5
|
|
|
3
6
|
Tools used internally for:
|
|
@@ -25,6 +28,7 @@ Commands:
|
|
|
25
28
|
cmd2md <command> documents a runnable command and outputs it to stdout
|
|
26
29
|
insertmd <readme> [heading] [foldable] takes Markdown from stdin and insert it into a Markdown file
|
|
27
30
|
inserttoc <readme> [heading] updates the TOC in a Markdown file
|
|
31
|
+
release-npm [path] release a npm package
|
|
28
32
|
help [command] display help for command
|
|
29
33
|
```
|
|
30
34
|
|
|
@@ -91,3 +95,18 @@ Arguments:
|
|
|
91
95
|
Options:
|
|
92
96
|
-h, --help display help for command
|
|
93
97
|
```
|
|
98
|
+
|
|
99
|
+
## Subcommand: `vrt release-npm`
|
|
100
|
+
|
|
101
|
+
```console
|
|
102
|
+
$ vrt release-npm
|
|
103
|
+
Usage: vrt release-npm [options] [path]
|
|
104
|
+
|
|
105
|
+
release a npm package
|
|
106
|
+
|
|
107
|
+
Arguments:
|
|
108
|
+
path root path of the Node.js project
|
|
109
|
+
|
|
110
|
+
Options:
|
|
111
|
+
-h, --help display help for command
|
|
112
|
+
```
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import cp from 'child_process';
|
|
2
|
-
import { getErrorMessage } from '
|
|
2
|
+
import { getErrorMessage } from '../lib/utils.js';
|
|
3
3
|
/**
|
|
4
4
|
* Generates documentation for a CLI command and its subcommands.
|
|
5
5
|
* @param command The base CLI command to document.
|
|
@@ -31,8 +31,10 @@ export async function generateCommandDocumentation(command) {
|
|
|
31
31
|
*/
|
|
32
32
|
async function getCommandResults(command) {
|
|
33
33
|
return new Promise((resolve, reject) => {
|
|
34
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
35
|
+
const env = { ...process.env, NODE_ENV: undefined };
|
|
34
36
|
// Spawn a child process to run the command with the '--help' flag.
|
|
35
|
-
const childProcess = cp.spawn('npx', [...command.split(' '), '--help']);
|
|
37
|
+
const childProcess = cp.spawn('npx', [...command.split(' '), '--help'], { env });
|
|
36
38
|
let output = '';
|
|
37
39
|
// Collect output data from the process.
|
|
38
40
|
childProcess.stdout.on('data', data => output += String(data));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { remark } from 'remark';
|
|
2
2
|
import remarkGfm from 'remark-gfm';
|
|
3
|
-
import { getErrorMessage } from '
|
|
3
|
+
import { getErrorMessage } from '../lib/utils.js';
|
|
4
4
|
/**
|
|
5
5
|
* Injects a Markdown segment under a specified heading in a Markdown document.
|
|
6
6
|
* Optionally, the injected segment can be made foldable for better readability.
|
|
@@ -24,8 +24,7 @@ export function injectMarkdown(document, segment, heading, foldable) {
|
|
|
24
24
|
}
|
|
25
25
|
catch (error) {
|
|
26
26
|
// Handle errors during the search for the start index.
|
|
27
|
-
|
|
28
|
-
throw error;
|
|
27
|
+
throw new Error(`Error while searching for segment "${heading}": ${getErrorMessage(error)}`);
|
|
29
28
|
}
|
|
30
29
|
// Get the depth of the specified heading to maintain the structure.
|
|
31
30
|
const depth = getHeadingDepth(documentAst, startIndex);
|
|
@@ -110,7 +109,7 @@ function findNextHeadingIndex(mainAst, startIndex, depth) {
|
|
|
110
109
|
for (let i = startIndex; i < mainAst.children.length; i++) {
|
|
111
110
|
const child = mainAst.children[i];
|
|
112
111
|
// Return the index of the next heading at the same depth.
|
|
113
|
-
if (child.type === 'heading' && child.depth
|
|
112
|
+
if (child.type === 'heading' && child.depth <= depth)
|
|
114
113
|
return i;
|
|
115
114
|
}
|
|
116
115
|
return mainAst.children.length;
|
|
@@ -269,10 +268,6 @@ export function nodeToHtml(node) {
|
|
|
269
268
|
return `<strong>${nodesToHtml(node.children)}</strong>`;
|
|
270
269
|
case 'link':
|
|
271
270
|
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
271
|
case 'image':
|
|
277
272
|
const attributes = [`src="${node.url}"`];
|
|
278
273
|
if (node.alt ?? '')
|
|
@@ -280,8 +275,6 @@ export function nodeToHtml(node) {
|
|
|
280
275
|
if (node.title ?? '')
|
|
281
276
|
attributes.push(`title="${node.title}"`);
|
|
282
277
|
return `<img ${attributes.join(' ')} />`;
|
|
283
|
-
case 'imageReference':
|
|
284
|
-
throw new Error('"imageReference to html" not implemented');
|
|
285
278
|
default:
|
|
286
279
|
console.log(node);
|
|
287
280
|
throw Error('unknown type');
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import inquirer from 'inquirer';
|
|
4
|
+
import { check, info, panic, warn } from '../lib/log.js';
|
|
5
|
+
import { getShell } from '../lib/shell.js';
|
|
6
|
+
import { getGit } from '../lib/git.js';
|
|
7
|
+
import { resolve } from 'node:path';
|
|
8
|
+
export async function release(directory, branch = 'main') {
|
|
9
|
+
const shell = getShell(directory);
|
|
10
|
+
const { getCommitsBetween, getCurrentGitHubCommit, getLastGitHubTag } = getGit(directory);
|
|
11
|
+
info('starting release process');
|
|
12
|
+
// git: check if in the correct branch
|
|
13
|
+
const currentBranch = await check('get branch name', shell.stdout('git rev-parse --abbrev-ref HEAD'));
|
|
14
|
+
if (currentBranch !== branch)
|
|
15
|
+
panic(`current branch is "${currentBranch}" but should be "${branch}"`);
|
|
16
|
+
// git: check if no changes
|
|
17
|
+
await check('are all changes committed?', checkThatNoUncommittedChanges());
|
|
18
|
+
// git: pull
|
|
19
|
+
await check('git pull', shell.run('git pull -t'));
|
|
20
|
+
// check package.json
|
|
21
|
+
const pkg = JSON.parse(readFileSync(resolve(directory, 'package.json'), 'utf8'));
|
|
22
|
+
if (typeof pkg !== 'object' || pkg === null)
|
|
23
|
+
panic('package.json is not valid');
|
|
24
|
+
if (!('version' in pkg) || (typeof pkg.version !== 'string'))
|
|
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
|
+
const { scripts } = pkg;
|
|
29
|
+
for (const scriptName of ['lint', 'build', 'test', 'doc']) {
|
|
30
|
+
if (!(scriptName in scripts)) {
|
|
31
|
+
panic(`missing npm script "${scriptName}" in package.json`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// get last version
|
|
35
|
+
const tag = await check('get last github tag', getLastGitHubTag());
|
|
36
|
+
const shaLast = tag?.sha;
|
|
37
|
+
const versionLastGithub = tag?.version;
|
|
38
|
+
const versionLastPackage = String(pkg.version);
|
|
39
|
+
if (versionLastPackage !== versionLastGithub)
|
|
40
|
+
warn(`versions differ in package.json (${versionLastPackage}) and last GitHub tag (${versionLastGithub})`);
|
|
41
|
+
// get current sha
|
|
42
|
+
const { sha: shaCurrent } = await check('get current github commit', getCurrentGitHubCommit());
|
|
43
|
+
// handle version
|
|
44
|
+
const nextVersion = await editVersion(versionLastPackage);
|
|
45
|
+
// prepare release notes
|
|
46
|
+
const releaseNotes = await check('prepare release notes', getReleaseNotes(nextVersion, shaLast, shaCurrent));
|
|
47
|
+
// update version
|
|
48
|
+
await check('update version', setNextVersion(nextVersion));
|
|
49
|
+
// lint
|
|
50
|
+
await check('lint', shell.run('npm run lint'));
|
|
51
|
+
// build
|
|
52
|
+
await check('build', shell.run('npm run build'));
|
|
53
|
+
// test
|
|
54
|
+
await check('run tests', shell.run('npm run test'));
|
|
55
|
+
// test
|
|
56
|
+
await check('update doc', shell.run('npm run doc'));
|
|
57
|
+
// npm publish
|
|
58
|
+
await check('npm publish', shell.run('npm publish --access public'));
|
|
59
|
+
// git push
|
|
60
|
+
await check('git add', shell.run('git add .'));
|
|
61
|
+
await check('git commit', shell.run(`git commit -m "v${nextVersion}"`, false));
|
|
62
|
+
await check('git tag', shell.run(`git tag -f -a "v${nextVersion}" -m "new release: v${nextVersion}"`));
|
|
63
|
+
await check('git push', shell.run('git push --no-verify --follow-tags'));
|
|
64
|
+
// github release
|
|
65
|
+
const releaseNotesPipe = `echo -e '${releaseNotes.replace(/[^a-z0-9,.?!:_<> -]/gi, c => '\\x' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))}'`;
|
|
66
|
+
if (await check('check github release', shell.ok('gh release view v' + nextVersion))) {
|
|
67
|
+
await check('edit release', shell.run(`${releaseNotesPipe} | gh release edit "v${nextVersion}" -F -`));
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
await check('create release', shell.run(`${releaseNotesPipe} | gh release create "v${nextVersion}" --draft --prerelease -F -`));
|
|
71
|
+
}
|
|
72
|
+
info('Finished');
|
|
73
|
+
return;
|
|
74
|
+
async function checkThatNoUncommittedChanges() {
|
|
75
|
+
if ((await shell.stdout('git status --porcelain')).length < 3)
|
|
76
|
+
return;
|
|
77
|
+
throw Error('please commit all changes before releasing');
|
|
78
|
+
}
|
|
79
|
+
async function setNextVersion(version) {
|
|
80
|
+
// set new version in package.json
|
|
81
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
82
|
+
const packageJSON = JSON.parse(readFileSync(resolve(directory, 'package.json'), 'utf8'));
|
|
83
|
+
packageJSON.version = version;
|
|
84
|
+
writeFileSync(resolve(directory, 'package.json'), JSON.stringify(packageJSON, null, ' ') + '\n');
|
|
85
|
+
// rebuild package.json
|
|
86
|
+
await shell.run('npm i --package-lock-only');
|
|
87
|
+
}
|
|
88
|
+
async function getReleaseNotes(version, hashLast, hashCurrent) {
|
|
89
|
+
const commits = await getCommitsBetween(hashLast, hashCurrent);
|
|
90
|
+
let notes = commits.reverse()
|
|
91
|
+
.map(commit => '- ' + commit.message.replace(/\s+/g, ' '))
|
|
92
|
+
.join('\n');
|
|
93
|
+
notes = `# Release v${version}\n\nchanges: \n${notes}\n\n`;
|
|
94
|
+
return notes;
|
|
95
|
+
}
|
|
96
|
+
async function editVersion(versionPackage) {
|
|
97
|
+
// ask for new version
|
|
98
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
99
|
+
const versionNew = (await inquirer.prompt({
|
|
100
|
+
message: 'What should be the new version?',
|
|
101
|
+
name: 'versionNew',
|
|
102
|
+
type: 'list',
|
|
103
|
+
choices: [versionPackage, bump(2), bump(1), bump(0)],
|
|
104
|
+
default: 1,
|
|
105
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
106
|
+
})).versionNew;
|
|
107
|
+
if (!versionNew)
|
|
108
|
+
throw Error();
|
|
109
|
+
return versionNew;
|
|
110
|
+
function bump(index) {
|
|
111
|
+
const p = versionPackage.split('.').map(v => parseInt(v, 10));
|
|
112
|
+
if (p.length !== 3)
|
|
113
|
+
throw Error();
|
|
114
|
+
switch (index) {
|
|
115
|
+
case 0:
|
|
116
|
+
p[0]++;
|
|
117
|
+
p[1] = 0;
|
|
118
|
+
p[2] = 0;
|
|
119
|
+
break;
|
|
120
|
+
case 1:
|
|
121
|
+
p[1]++;
|
|
122
|
+
p[2] = 0;
|
|
123
|
+
break;
|
|
124
|
+
case 2:
|
|
125
|
+
p[2]++;
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
const name = p.map((n, i) => (i == index) ? `\x1b[1m${n}` : `${n}`).join('.') + '\x1b[22m';
|
|
129
|
+
const value = p.join('.');
|
|
130
|
+
return { name, value };
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=release.js.map
|
|
@@ -17,14 +17,53 @@ function* documentProject(project) {
|
|
|
17
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
|
-
|
|
21
|
-
|
|
20
|
+
yield '\n# ' + group.title;
|
|
21
|
+
for (const declaration of group.children) {
|
|
22
|
+
switch (declaration.kind) {
|
|
23
|
+
case ReflectionKind.Class:
|
|
24
|
+
yield* documentClass(declaration);
|
|
25
|
+
break;
|
|
26
|
+
case ReflectionKind.Function:
|
|
27
|
+
yield* documentMethod(declaration, 2);
|
|
28
|
+
break;
|
|
29
|
+
case ReflectionKind.Interface:
|
|
30
|
+
yield* documentInterface(declaration);
|
|
31
|
+
break;
|
|
32
|
+
case ReflectionKind.TypeAlias:
|
|
33
|
+
yield* documentType(declaration);
|
|
34
|
+
break;
|
|
35
|
+
case ReflectionKind.Variable:
|
|
36
|
+
yield* documentVariable(declaration);
|
|
37
|
+
break;
|
|
38
|
+
default:
|
|
39
|
+
throw new Error('implement ' + declaration.kind);
|
|
40
|
+
}
|
|
22
41
|
}
|
|
23
42
|
}
|
|
24
43
|
}
|
|
25
|
-
function*
|
|
26
|
-
|
|
27
|
-
yield
|
|
44
|
+
function* documentInterface(declaration) {
|
|
45
|
+
yield `\n## Interface: \`${declaration.name}\`<a id="${createAnchorId(declaration)}"></a>`;
|
|
46
|
+
yield '\n```typescript';
|
|
47
|
+
yield 'interface {';
|
|
48
|
+
for (const child of declaration.children ?? []) {
|
|
49
|
+
if (child.kind !== ReflectionKind.Property)
|
|
50
|
+
throw Error('should be a property inside an interface');
|
|
51
|
+
if (child.type == null)
|
|
52
|
+
throw Error('should have a type');
|
|
53
|
+
const name = child.name + (child.flags.isOptional ? '?' : '');
|
|
54
|
+
yield ` ${name}: ${formatTypeDeclaration(child.type)};`;
|
|
55
|
+
}
|
|
56
|
+
yield '}';
|
|
57
|
+
yield '```';
|
|
58
|
+
}
|
|
59
|
+
function* documentType(declaration) {
|
|
60
|
+
yield `\n## Type: \`${declaration.name}\`<a id="${createAnchorId(declaration)}"></a>`;
|
|
61
|
+
if (declaration.type) {
|
|
62
|
+
yield `\n**Type:** <code>${formatTypeDeclaration(declaration.type)}</code>`;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function* documentClass(declaration) {
|
|
66
|
+
yield `\n## Class: \`${declaration.name}\`<a id="${createAnchorId(declaration)}"></a>`;
|
|
28
67
|
yield* documentSummaryBlock(declaration);
|
|
29
68
|
for (const group of declaration.groups ?? []) {
|
|
30
69
|
const publicMembers = group.children.filter(member => !member.flags.isPrivate && !member.flags.isProtected);
|
|
@@ -36,30 +75,29 @@ function* documentDeclaration(declaration) {
|
|
|
36
75
|
case 'Constructors':
|
|
37
76
|
if (publicMembers.length !== 1)
|
|
38
77
|
throw Error('publicMembers.length !== 1');
|
|
39
|
-
yield* documentMethod(publicMembers[0], true);
|
|
78
|
+
yield* documentMethod(publicMembers[0], 3, true);
|
|
79
|
+
continue;
|
|
80
|
+
case 'Accessors':
|
|
81
|
+
yield '\n### Accessors';
|
|
82
|
+
for (const member of publicMembers)
|
|
83
|
+
yield documentAccessor(member);
|
|
40
84
|
continue;
|
|
41
85
|
case 'Properties':
|
|
42
|
-
|
|
43
|
-
//yield '**Properties**';
|
|
86
|
+
yield '\n### Properties';
|
|
44
87
|
for (const member of publicMembers)
|
|
45
88
|
yield documentProperty(member);
|
|
46
89
|
continue;
|
|
47
90
|
case 'Methods':
|
|
48
|
-
//yield '';
|
|
49
|
-
//yield '**Methods**';
|
|
50
91
|
for (const member of publicMembers)
|
|
51
|
-
yield* documentMethod(member);
|
|
92
|
+
yield* documentMethod(member, 3);
|
|
52
93
|
continue;
|
|
53
94
|
default:
|
|
54
95
|
console.log(group);
|
|
55
96
|
throw Error('Unknown group title');
|
|
56
97
|
}
|
|
57
98
|
}
|
|
58
|
-
if (declaration.type) {
|
|
59
|
-
yield `\n**Type:** <code>${formatTypeDeclaration(declaration.type)}</code>`;
|
|
60
|
-
}
|
|
61
99
|
}
|
|
62
|
-
function* documentMethod(method, isConstructor = false) {
|
|
100
|
+
function* documentMethod(method, depth, isConstructor = false) {
|
|
63
101
|
if (method.signatures?.length !== 1)
|
|
64
102
|
throw Error('should be 1');
|
|
65
103
|
const [signature] = method.signatures;
|
|
@@ -67,8 +105,7 @@ function* documentMethod(method, isConstructor = false) {
|
|
|
67
105
|
const parameters = formatMethodParameters(signature.parameters ?? []);
|
|
68
106
|
const returnType = signature.type;
|
|
69
107
|
const methodType = isConstructor ? 'Constructor' : 'Method';
|
|
70
|
-
yield
|
|
71
|
-
yield '';
|
|
108
|
+
yield `\n${'#'.repeat(depth)} ${methodType}: \`${methodName}(${parameters})\``;
|
|
72
109
|
yield* documentSummaryBlock(signature);
|
|
73
110
|
if (signature.parameters && signature.parameters.length > 0) {
|
|
74
111
|
yield '';
|
|
@@ -77,16 +114,15 @@ function* documentMethod(method, isConstructor = false) {
|
|
|
77
114
|
yield documentProperty(parameter);
|
|
78
115
|
}
|
|
79
116
|
}
|
|
80
|
-
if (returnType) {
|
|
81
|
-
yield
|
|
82
|
-
yield `**Returns:** <code>${formatTypeDeclaration(returnType)}</code>`;
|
|
117
|
+
if (returnType && !isConstructor) {
|
|
118
|
+
yield `\n**Returns:** <code>${formatTypeDeclaration(returnType)}</code>`;
|
|
83
119
|
}
|
|
84
120
|
}
|
|
85
121
|
function formatMethodParameters(parameters) {
|
|
86
122
|
return parameters.map(param => param.name).join(', ');
|
|
87
123
|
}
|
|
88
124
|
// Helper Functions
|
|
89
|
-
function
|
|
125
|
+
function getDeclarationKindName(kind) {
|
|
90
126
|
switch (kind) {
|
|
91
127
|
case ReflectionKind.Class: return 'Class';
|
|
92
128
|
case ReflectionKind.Function: return 'Function';
|
|
@@ -101,13 +137,27 @@ function documentProperty(ref) {
|
|
|
101
137
|
if (ref.flags.isOptional)
|
|
102
138
|
line += ' (optional)';
|
|
103
139
|
const summary = extractSummary(ref.comment);
|
|
104
|
-
if (summary)
|
|
140
|
+
if (summary != null)
|
|
141
|
+
line += ' \n ' + summary;
|
|
142
|
+
return line;
|
|
143
|
+
}
|
|
144
|
+
function* documentVariable(ref) {
|
|
145
|
+
const prefix = ref.flags.isConst ? 'const' : 'let';
|
|
146
|
+
yield `\n## \`${prefix} ${ref.name}\``;
|
|
147
|
+
const summary = extractSummary(ref.comment);
|
|
148
|
+
if (summary != null)
|
|
149
|
+
yield summary;
|
|
150
|
+
}
|
|
151
|
+
function documentAccessor(ref) {
|
|
152
|
+
let line = ` - <code>${ref.name}${resolveTypeDeclaration(ref.type)}</code>`;
|
|
153
|
+
const summary = extractSummary(ref.comment);
|
|
154
|
+
if (summary != null)
|
|
105
155
|
line += ' \n ' + summary;
|
|
106
156
|
return line;
|
|
107
157
|
}
|
|
108
158
|
function extractSummary(comment) {
|
|
109
159
|
if (!comment)
|
|
110
|
-
return
|
|
160
|
+
return null;
|
|
111
161
|
return comment.summary.map(line => line.text).join('');
|
|
112
162
|
}
|
|
113
163
|
function* documentSummaryBlock(ref) {
|
|
@@ -126,10 +176,16 @@ function* documentSummaryBlock(ref) {
|
|
|
126
176
|
return;
|
|
127
177
|
}
|
|
128
178
|
}
|
|
129
|
-
|
|
179
|
+
const sourceLink = createSourceLink(ref);
|
|
180
|
+
if (sourceLink != null)
|
|
181
|
+
yield sourceLink;
|
|
130
182
|
return;
|
|
131
183
|
function formatComment(comment) {
|
|
132
|
-
|
|
184
|
+
let summary = extractSummary(comment) ?? '';
|
|
185
|
+
const link = createSourceLink(ref);
|
|
186
|
+
if (link != null)
|
|
187
|
+
summary += ' ' + link;
|
|
188
|
+
return summary.replace(/\n/m, ' \n') + '\n';
|
|
133
189
|
}
|
|
134
190
|
}
|
|
135
191
|
function resolveTypeDeclaration(someType) {
|
|
@@ -150,42 +206,73 @@ function formatTypeDeclaration(someType) {
|
|
|
150
206
|
if (some.reflection)
|
|
151
207
|
result = `[${result}](#${createAnchorId(some.reflection)})`;
|
|
152
208
|
if (some.typeArguments?.length ?? 0)
|
|
153
|
-
result += '
|
|
209
|
+
result += '<'
|
|
154
210
|
+ (some.typeArguments ?? [])
|
|
155
211
|
.map(getTypeRec).join(',')
|
|
156
|
-
+ '
|
|
212
|
+
+ '>';
|
|
157
213
|
return result;
|
|
158
214
|
case 'reflection':
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
.map(p => {
|
|
167
|
-
return p.name + (p.type ? ': ' + getTypeRec(p.type) : '');
|
|
168
|
-
}).join(', ');
|
|
169
|
-
return `(${parameters}) => ${type}`;
|
|
215
|
+
switch (some.declaration.kind) {
|
|
216
|
+
case ReflectionKind.TypeLiteral: return decodeReflectionTypeLiteral(some.declaration);
|
|
217
|
+
default:
|
|
218
|
+
console.log('declarationKindName', getDeclarationKindName(some.declaration.kind));
|
|
219
|
+
console.dir(some, { depth: 4 });
|
|
220
|
+
throw Error();
|
|
221
|
+
}
|
|
170
222
|
case 'tuple':
|
|
171
223
|
return `[${some.elements.map(getTypeRec).join(', ')}]`;
|
|
172
224
|
case 'union':
|
|
173
225
|
return some.types.map(getTypeRec).join(' | ');
|
|
226
|
+
case 'array':
|
|
227
|
+
return getTypeRec(some.elementType) + '[]';
|
|
174
228
|
default:
|
|
175
229
|
console.log(some);
|
|
176
230
|
throw Error(some.type);
|
|
177
231
|
}
|
|
232
|
+
function decodeReflectionTypeLiteral(ref) {
|
|
233
|
+
try {
|
|
234
|
+
if (ref.variant !== 'declaration')
|
|
235
|
+
throw Error();
|
|
236
|
+
if (ref.groups && !ref.signatures) {
|
|
237
|
+
if (!Array.isArray(ref.groups))
|
|
238
|
+
throw Error();
|
|
239
|
+
if (ref.groups.length !== 1)
|
|
240
|
+
throw Error();
|
|
241
|
+
const [group] = ref.groups;
|
|
242
|
+
if (group.title !== 'Properties')
|
|
243
|
+
throw Error();
|
|
244
|
+
const properties = group.children.map(r => r.escapedName + ':?');
|
|
245
|
+
return `{${properties.join(', ')}}`;
|
|
246
|
+
}
|
|
247
|
+
if (!ref.groups && ref.signatures) {
|
|
248
|
+
if (ref.signatures.length !== 1)
|
|
249
|
+
throw Error('ref.signatures.length !== 1');
|
|
250
|
+
const [signature] = ref.signatures;
|
|
251
|
+
const returnType = signature.type ? getTypeRec(signature.type) : 'void';
|
|
252
|
+
const parameters = (signature.parameters ?? [])
|
|
253
|
+
.map(p => {
|
|
254
|
+
return p.name + (p.type ? ': ' + getTypeRec(p.type) : '');
|
|
255
|
+
}).join(', ');
|
|
256
|
+
return `(${parameters}) => ${returnType}`;
|
|
257
|
+
}
|
|
258
|
+
throw Error();
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
261
|
+
console.dir(ref, { depth: 3 });
|
|
262
|
+
throw error;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
178
265
|
}
|
|
179
266
|
}
|
|
180
267
|
function createSourceLink(reference) {
|
|
181
268
|
if (!reference.sources || reference.sources.length < 1)
|
|
182
|
-
return
|
|
269
|
+
return null;
|
|
183
270
|
if (reference.sources.length > 1)
|
|
184
271
|
throw Error('ref.sources.length > 1');
|
|
185
272
|
const [source] = reference.sources;
|
|
186
273
|
return `<sup><a href="${source.url}">[src]</a></sup>`;
|
|
187
274
|
}
|
|
188
275
|
function createAnchorId(reference) {
|
|
189
|
-
return `${
|
|
276
|
+
return `${getDeclarationKindName(reference.kind)}_${reference.name}`.toLowerCase();
|
|
190
277
|
}
|
|
191
278
|
//# sourceMappingURL=typedoc.js.map
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
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 { generateTsMarkdownDoc } from './
|
|
5
|
-
import { injectMarkdown, updateTOC } from './
|
|
4
|
+
import { generateTsMarkdownDoc } from './commands/typedoc.js';
|
|
5
|
+
import { injectMarkdown, updateTOC } from './commands/markdown.js';
|
|
6
6
|
import { Command, InvalidArgumentError } from 'commander';
|
|
7
7
|
import { cwd } from 'node:process';
|
|
8
|
-
import { generateCommandDocumentation } from './
|
|
9
|
-
|
|
8
|
+
import { generateCommandDocumentation } from './commands/command.js';
|
|
9
|
+
import { release } from './commands/release.js';
|
|
10
|
+
export const program = new Command();
|
|
10
11
|
program
|
|
11
12
|
.name('vrt')
|
|
12
13
|
.description('versatiles release and documentaion tool');
|
|
@@ -48,7 +49,15 @@ program.command('inserttoc')
|
|
|
48
49
|
mdFile = updateTOC(mdFile, heading);
|
|
49
50
|
writeFileSync(mdFilename, mdFile);
|
|
50
51
|
});
|
|
51
|
-
program.
|
|
52
|
+
program.command('release-npm')
|
|
53
|
+
.description('release a npm package')
|
|
54
|
+
.argument('[path]', 'root path of the Node.js project')
|
|
55
|
+
.action((path) => {
|
|
56
|
+
void release(resolve(path ?? ',', process.cwd()), 'main');
|
|
57
|
+
});
|
|
58
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
59
|
+
await program.parseAsync();
|
|
60
|
+
}
|
|
52
61
|
function checkFilename(filename) {
|
|
53
62
|
const fullname = resolve(cwd(), filename);
|
|
54
63
|
if (!existsSync(fullname)) {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface Commit {
|
|
2
|
+
sha: string;
|
|
3
|
+
message: string;
|
|
4
|
+
tag?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface Git {
|
|
7
|
+
getLastGitHubTag: () => Promise<{
|
|
8
|
+
sha: string;
|
|
9
|
+
version: string;
|
|
10
|
+
} | undefined>;
|
|
11
|
+
getCurrentGitHubCommit: () => Promise<Commit>;
|
|
12
|
+
getCommitsBetween: (shaLast?: string, shaCurrent?: string) => Promise<Commit[]>;
|
|
13
|
+
}
|
|
14
|
+
export declare function getGit(cwd: string): Git;
|
package/dist/lib/git.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { getShell } from './shell.js';
|
|
2
|
+
export function getGit(cwd) {
|
|
3
|
+
const shell = getShell(cwd);
|
|
4
|
+
return {
|
|
5
|
+
getLastGitHubTag,
|
|
6
|
+
getCurrentGitHubCommit,
|
|
7
|
+
getCommitsBetween,
|
|
8
|
+
};
|
|
9
|
+
async function getLastGitHubTag() {
|
|
10
|
+
const commits = await getAllCommits();
|
|
11
|
+
const result = commits
|
|
12
|
+
.map(commit => ({
|
|
13
|
+
sha: commit.sha,
|
|
14
|
+
version: commit.tag?.match(/^v(\d+\.\d+\.\d+)$/)?.[1],
|
|
15
|
+
}))
|
|
16
|
+
.find(r => r.version);
|
|
17
|
+
return result;
|
|
18
|
+
}
|
|
19
|
+
async function getAllCommits() {
|
|
20
|
+
const result = await shell.stdout('git log --pretty=format:\'⍃%H⍄%s⍄%D⍄\'');
|
|
21
|
+
return result
|
|
22
|
+
.split('⍃')
|
|
23
|
+
.filter(line => line.length > 2)
|
|
24
|
+
.map(line => {
|
|
25
|
+
const obj = line.split('⍄');
|
|
26
|
+
return {
|
|
27
|
+
sha: obj[0],
|
|
28
|
+
message: obj[1],
|
|
29
|
+
tag: /tag: ([a-z0-9.]+)/.exec(obj[2])?.[1],
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
async function getCurrentGitHubCommit() {
|
|
34
|
+
return (await getAllCommits())[0];
|
|
35
|
+
}
|
|
36
|
+
async function getCommitsBetween(shaLast, shaCurrent) {
|
|
37
|
+
let commits = await getAllCommits();
|
|
38
|
+
const start = commits.findIndex(commit => commit.sha === shaCurrent);
|
|
39
|
+
if (start >= 0)
|
|
40
|
+
commits = commits.slice(start);
|
|
41
|
+
const end = commits.findIndex(commit => commit.sha === shaLast);
|
|
42
|
+
if (end >= 0)
|
|
43
|
+
commits = commits.slice(0, end);
|
|
44
|
+
return commits;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=git.js.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function panic(text: string): never;
|
|
2
|
+
export declare function warn(text: string): void;
|
|
3
|
+
export declare function info(text: string): void;
|
|
4
|
+
export declare function abort(): never;
|
|
5
|
+
export declare function check<T>(message: string, promise: Promise<T>): Promise<T>;
|
package/dist/lib/log.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export function panic(text) {
|
|
2
|
+
process.stderr.write(`\x1b[1;31m! ERROR: ${text}\x1b[0m\n`);
|
|
3
|
+
abort();
|
|
4
|
+
}
|
|
5
|
+
export function warn(text) {
|
|
6
|
+
process.stderr.write(`\x1b[1;33m! warning: ${text}\x1b[0m\n`);
|
|
7
|
+
}
|
|
8
|
+
export function info(text) {
|
|
9
|
+
process.stderr.write(`\x1b[0mi ${text}\n`);
|
|
10
|
+
}
|
|
11
|
+
export function abort() {
|
|
12
|
+
info('abort');
|
|
13
|
+
process.exit();
|
|
14
|
+
}
|
|
15
|
+
export async function check(message, promise) {
|
|
16
|
+
process.stderr.write(`\x1b[0;90m\u2B95 ${message}\x1b[0m`);
|
|
17
|
+
try {
|
|
18
|
+
const result = await promise;
|
|
19
|
+
process.stderr.write(`\r\x1b[0;92m\u2714 ${message}\x1b[0m\n`);
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
process.stderr.write(`\r\x1b[0;91m\u2718 ${message}\x1b[0m\n`);
|
|
24
|
+
panic(error.message);
|
|
25
|
+
throw Error();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=log.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface Shell {
|
|
2
|
+
run: (command: string, errorOnCodeNonZero?: boolean) => Promise<{
|
|
3
|
+
code: number | null;
|
|
4
|
+
signal: string | null;
|
|
5
|
+
stdout: string;
|
|
6
|
+
stderr: string;
|
|
7
|
+
}>;
|
|
8
|
+
stderr: (command: string, errorOnCodeZero?: boolean) => Promise<string>;
|
|
9
|
+
stdout: (command: string, errorOnCodeZero?: boolean) => Promise<string>;
|
|
10
|
+
ok: (command: string) => Promise<boolean>;
|
|
11
|
+
}
|
|
12
|
+
export declare function getShell(cwd: string): Shell;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
export function getShell(cwd) {
|
|
3
|
+
async function run(command, errorOnCodeNonZero) {
|
|
4
|
+
try {
|
|
5
|
+
return await new Promise((resolve, reject) => {
|
|
6
|
+
const stdout = [];
|
|
7
|
+
const stderr = [];
|
|
8
|
+
const cp = spawn('bash', ['-c', command], { cwd })
|
|
9
|
+
.on('error', error => {
|
|
10
|
+
reject(error);
|
|
11
|
+
})
|
|
12
|
+
.on('close', (code, signal) => {
|
|
13
|
+
const result = {
|
|
14
|
+
code,
|
|
15
|
+
signal,
|
|
16
|
+
stdout: Buffer.concat(stdout).toString(),
|
|
17
|
+
stderr: Buffer.concat(stderr).toString(),
|
|
18
|
+
};
|
|
19
|
+
if ((errorOnCodeNonZero ?? true) && (code !== 0)) {
|
|
20
|
+
reject(result);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
resolve(result);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
cp.stdout.on('data', (chunk) => stdout.push(chunk));
|
|
27
|
+
cp.stderr.on('data', (chunk) => stderr.push(chunk));
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
console.error(error);
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
run,
|
|
37
|
+
stderr: async (command, errorOnCodeZero) => (await run(command, errorOnCodeZero)).stderr.trim(),
|
|
38
|
+
stdout: async (command, errorOnCodeZero) => (await run(command, errorOnCodeZero)).stdout.trim(),
|
|
39
|
+
ok: async (command) => (await run(command, false)).code === 0,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=shell.js.map
|
package/dist/lib/utils.d.ts
CHANGED
package/dist/lib/utils.js
CHANGED
|
@@ -2,9 +2,38 @@ export function getErrorMessage(error) {
|
|
|
2
2
|
if (error == null)
|
|
3
3
|
return 'unknown';
|
|
4
4
|
if (typeof error === 'object') {
|
|
5
|
-
if ('message' in error)
|
|
6
|
-
|
|
5
|
+
if ('message' in error) {
|
|
6
|
+
if (typeof error.message === 'string')
|
|
7
|
+
return error.message;
|
|
8
|
+
return JSON.stringify(error.message);
|
|
9
|
+
}
|
|
7
10
|
}
|
|
8
11
|
return 'unknown';
|
|
9
12
|
}
|
|
13
|
+
export function prettyStyleJSON(inputData) {
|
|
14
|
+
return recursive(inputData);
|
|
15
|
+
function recursive(data, prefix = '', path = '') {
|
|
16
|
+
if (path.endsWith('.bounds'))
|
|
17
|
+
return singleLine(data);
|
|
18
|
+
//if (path.includes('.vector_layers[].')) return singleLine(data);
|
|
19
|
+
if (path.startsWith('.layers[].filter'))
|
|
20
|
+
return singleLine(data);
|
|
21
|
+
if (path.startsWith('.layers[].paint.'))
|
|
22
|
+
return singleLine(data);
|
|
23
|
+
if (path.startsWith('.layers[].layout.'))
|
|
24
|
+
return singleLine(data);
|
|
25
|
+
if (typeof data === 'object') {
|
|
26
|
+
if (Array.isArray(data)) {
|
|
27
|
+
return '[\n\t' + prefix + data.map((value) => recursive(value, prefix + '\t', path + '[]')).join(',\n\t' + prefix) + '\n' + prefix + ']';
|
|
28
|
+
}
|
|
29
|
+
if (data) {
|
|
30
|
+
return '{\n\t' + prefix + Object.entries(data).map(([key, value]) => '"' + key + '": ' + recursive(value, prefix + '\t', path + '.' + key)).join(',\n\t' + prefix) + '\n' + prefix + '}';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return singleLine(data);
|
|
34
|
+
}
|
|
35
|
+
function singleLine(data) {
|
|
36
|
+
return JSON.stringify(data, null, '\t').replace(/[\t\n]+/g, ' ');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
10
39
|
//# sourceMappingURL=utils.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@versatiles/release-tool",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "VersaTiles release and documentation tools",
|
|
5
5
|
"bin": {
|
|
6
6
|
"vrt": "./dist/index.js"
|
|
@@ -11,11 +11,13 @@
|
|
|
11
11
|
],
|
|
12
12
|
"scripts": {
|
|
13
13
|
"build": "rm -rf dist && tsc -p tsconfig.build.json && chmod +x dist/index.js",
|
|
14
|
-
"check": "npm run lint && npm run
|
|
14
|
+
"check": "npm run lint && npm run build && npm run test",
|
|
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
|
-
"test": "
|
|
18
|
+
"test-coverage": "NODE_OPTIONS=--experimental-vm-modules jest --coverage",
|
|
19
|
+
"test": "NODE_OPTIONS=--experimental-vm-modules jest",
|
|
20
|
+
"upgrade": "npm-check-updates -u && rm -f package-lock.json; rm -rf node_modules; npm i && npm update"
|
|
19
21
|
},
|
|
20
22
|
"author": "yetzt <node@yetzt.me>, Michael Kreil <versatiles@michael-kreil.de>",
|
|
21
23
|
"license": "Unlicense",
|
|
@@ -26,14 +28,23 @@
|
|
|
26
28
|
},
|
|
27
29
|
"homepage": "https://github.com/versatiles-org/node-versatiles/blob/main/versatiles-release-tool/README.md",
|
|
28
30
|
"devDependencies": {
|
|
29
|
-
"@types/
|
|
30
|
-
"
|
|
31
|
-
"
|
|
31
|
+
"@types/inquirer": "^9.0.7",
|
|
32
|
+
"@types/jest": "^29.5.11",
|
|
33
|
+
"@types/node": "^20.11.10",
|
|
34
|
+
"@typescript-eslint/eslint-plugin": "^6.20.0",
|
|
35
|
+
"@typescript-eslint/parser": "^6.20.0",
|
|
36
|
+
"eslint": "^8.56.0",
|
|
37
|
+
"jest": "^29.7.0",
|
|
38
|
+
"ts-jest": "^29.1.2",
|
|
39
|
+
"ts-node": "^10.9.2",
|
|
40
|
+
"tsx": "^4.7.0",
|
|
41
|
+
"typescript": "^5.3.3"
|
|
32
42
|
},
|
|
33
43
|
"dependencies": {
|
|
34
44
|
"commander": "^11.1.0",
|
|
45
|
+
"inquirer": "^9.2.13",
|
|
35
46
|
"remark": "^15.0.1",
|
|
36
47
|
"remark-gfm": "^4.0.0",
|
|
37
|
-
"typedoc": "^0.25.
|
|
48
|
+
"typedoc": "^0.25.7"
|
|
38
49
|
}
|
|
39
50
|
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|