@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 CHANGED
@@ -1,3 +1,6 @@
1
+ [![Code Coverage](https://codecov.io/gh/versatiles-org/node-release-tool/branch/main/graph/badge.svg?token=IDHAI13M0K)](https://codecov.io/gh/versatiles-org/node-release-tool)
2
+ [![GitHub Workflow Status)](https://img.shields.io/github/actions/workflow/status/versatiles-org/node-release-tool/ci.yml)](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 './utils.js';
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 './utils.js';
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
- console.error(`Error while searching for segment "${heading}": ${getErrorMessage(error)}`);
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 === 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,2 @@
1
+ #!/usr/bin/env npx tsx
2
+ export declare function release(directory: string, branch?: string): Promise<void>;
@@ -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
- for (const item of group.children) {
21
- yield* documentDeclaration(item);
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* documentDeclaration(declaration) {
26
- const declarationType = getDeclarationTypeName(declaration.kind);
27
- yield `# ${declarationType}: \`${declaration.name}\`<a id="${createAnchorId(declaration)}"></a>`;
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
- //yield '';
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 `## ${methodType}: \`${methodName}(${parameters})\``;
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 getDeclarationTypeName(kind) {
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
- yield createSourceLink(ref) + '\n';
179
+ const sourceLink = createSourceLink(ref);
180
+ if (sourceLink != null)
181
+ yield sourceLink;
130
182
  return;
131
183
  function formatComment(comment) {
132
- return (extractSummary(comment) + ' ' + createSourceLink(ref)).replace(/\n/m, ' \n') + '\n';
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 += '&lt;'
154
210
  + (some.typeArguments ?? [])
155
211
  .map(getTypeRec).join(',')
156
- + '>';
212
+ + '&gt;';
157
213
  return result;
158
214
  case 'reflection':
159
- if (!some.declaration.signatures)
160
- throw Error('!some.declaration.signatures');
161
- if (some.declaration.signatures.length !== 1)
162
- throw Error('some.declaration.signatures.length !== 1');
163
- const [signature] = some.declaration.signatures;
164
- const type = signature.type ? getTypeRec(signature.type) : 'void';
165
- const parameters = (signature.parameters ?? [])
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 `${getDeclarationTypeName(reference.kind)}_${reference.name}`.toLowerCase();
276
+ return `${getDeclarationKindName(reference.kind)}_${reference.name}`.toLowerCase();
190
277
  }
191
278
  //# sourceMappingURL=typedoc.js.map
package/dist/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  #!/usr/bin/env -S node --enable-source-maps
2
- export {};
2
+ import { Command } from 'commander';
3
+ export declare const program: Command;
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 './lib/typedoc.js';
5
- import { injectMarkdown, updateTOC } from './lib/markdown.js';
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 './lib/command.js';
9
- const program = new Command();
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.parse();
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;
@@ -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>;
@@ -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
@@ -1 +1,2 @@
1
1
  export declare function getErrorMessage(error: unknown): string;
2
+ export declare function prettyStyleJSON(inputData: unknown): string;
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
- return String(error.message);
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.2",
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 test && npm run build",
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": "cd ..; NODE_OPTIONS=--experimental-vm-modules jest --testPathPattern versatiles-release-tool"
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/node": "^20.10.0",
30
- "tsx": "^4.4.0",
31
- "typescript": "^5.2.2"
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.3"
48
+ "typedoc": "^0.25.7"
38
49
  }
39
50
  }
File without changes
File without changes
File without changes