@eclipse-docks/cli 0.7.114 → 0.7.118

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
@@ -9,6 +9,7 @@ npx @eclipse-docks/cli release # bump patch, prompt for re
9
9
  npx @eclipse-docks/cli release minor # bump minor
10
10
  npx @eclipse-docks/cli release major -m "notes" # bump major with inline release notes
11
11
  npx @eclipse-docks/cli release v1.2.3 # explicit version
12
+ npx @eclipse-docks/cli release --since v1.2.0 # notes from that version (bump still from latest)
12
13
  npx @eclipse-docks/cli release --dry-run # print what would be committed
13
14
  npx @eclipse-docks/cli release --no-push # commit locally without pushing
14
15
  ```
@@ -20,7 +21,10 @@ Pair this with a CI workflow that tags/releases on that commit subject (see
20
21
  [`.github/workflows/app-release.yml`](../../.github/workflows/app-release.yml) for a
21
22
  reusable one) — the commit body becomes the release notes.
22
23
 
24
+ The last version is taken from the most recent commit whose subject is exactly `vX.Y.Z`
25
+ or the legacy form `Release: vX.Y.Z`. That drives the default bump.
26
+
23
27
  If no release notes are given via `-m`, an end-user-facing summary is generated from the
24
- commits since the last version, grouped under **Features**, **Fixes**, **Docs**, and
25
- **Chore/Refactor**, and offered as the default (press Enter on an empty first line to
26
- accept it).
28
+ commits since that version (or since `--since vX.Y.Z` when set), grouped under
29
+ **Features**, **Fixes**, **Docs**, and **Chore/Refactor**, and offered as the default
30
+ (press Enter on an empty first line to accept it).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eclipse-docks/cli",
3
- "version": "0.7.114",
3
+ "version": "0.7.118",
4
4
  "description": "Developer CLI for Eclipse Docks apps (release automation and more)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,7 +1,10 @@
1
1
  import { spawnSync } from 'node:child_process';
2
2
  import { createInterface } from 'node:readline/promises';
3
3
 
4
+ /** CLI arg / new release commit subject: exact `vX.Y.Z`. */
4
5
  const VERSION_RE = /^v\d+\.\d+\.\d+$/;
6
+ /** Discover last release from exact `vX.Y.Z` or legacy `Release: vX.Y.Z`. */
7
+ const VERSION_IN_SUBJECT_RE = /^(?:Release:\s*)?(v\d+\.\d+\.\d+)$/i;
5
8
 
6
9
  function git(args, cwd) {
7
10
  const result = spawnSync('git', args, { cwd, encoding: 'utf8' });
@@ -16,8 +19,18 @@ function gitOrNull(args, cwd) {
16
19
  return result.status === 0 ? result.stdout.trim() : null;
17
20
  }
18
21
 
22
+ function parseVersionSubject(subject) {
23
+ const m = subject.trim().match(VERSION_IN_SUBJECT_RE);
24
+ if (!m) return null;
25
+ return `v${m[1].slice(1)}`;
26
+ }
27
+
28
+ function usage() {
29
+ return 'Usage: docks release [major|minor|patch|vX.Y.Z] [-m "release notes"] [--since vX.Y.Z] [--dry-run] [--no-push]';
30
+ }
31
+
19
32
  function parseArgs(argv) {
20
- const opts = { bump: 'patch', version: '', notes: '', dryRun: false, push: true };
33
+ const opts = { bump: 'patch', version: '', since: '', notes: '', dryRun: false, push: true };
21
34
 
22
35
  for (let i = 0; i < argv.length; i++) {
23
36
  const arg = argv[i];
@@ -25,6 +38,12 @@ function parseArgs(argv) {
25
38
  opts.bump = arg;
26
39
  } else if (VERSION_RE.test(arg)) {
27
40
  opts.version = arg;
41
+ } else if (arg === '--since') {
42
+ const value = argv[++i] ?? '';
43
+ if (!VERSION_RE.test(value)) {
44
+ throw new Error(`--since requires a version like v1.2.3\n${usage()}`);
45
+ }
46
+ opts.since = value;
28
47
  } else if (arg === '-m' || arg === '--message') {
29
48
  opts.notes = argv[++i] ?? '';
30
49
  } else if (arg === '-n' || arg === '--dry-run') {
@@ -34,9 +53,7 @@ function parseArgs(argv) {
34
53
  } else if (arg === '--no-push') {
35
54
  opts.push = false;
36
55
  } else {
37
- throw new Error(
38
- `Unknown argument: ${arg}\nUsage: docks release [major|minor|patch|vX.Y.Z] [-m "release notes"] [--dry-run] [--no-push]`,
39
- );
56
+ throw new Error(`Unknown argument: ${arg}\n${usage()}`);
40
57
  }
41
58
  }
42
59
 
@@ -47,11 +64,20 @@ function findLastVersion(cwd) {
47
64
  const log = gitOrNull(['log', '--pretty=%s'], cwd);
48
65
  if (!log) return null;
49
66
  for (const subject of log.split('\n')) {
50
- if (VERSION_RE.test(subject)) return subject;
67
+ const version = parseVersionSubject(subject);
68
+ if (version) return version;
51
69
  }
52
70
  return null;
53
71
  }
54
72
 
73
+ function findVersionCommit(cwd, version) {
74
+ const escaped = version.replace(/\./g, '\\.');
75
+ return gitOrNull(
76
+ ['log', '-E', '-i', '--pretty=%H', '-1', `--grep=^(Release:[[:space:]]*)?${escaped}$`],
77
+ cwd,
78
+ );
79
+ }
80
+
55
81
  function bumpVersion(last, bump) {
56
82
  const [major, minor, patch] = last.slice(1).split('.').map(Number);
57
83
  switch (bump) {
@@ -64,16 +90,18 @@ function bumpVersion(last, bump) {
64
90
  }
65
91
  }
66
92
 
67
- function generateSummary(cwd, last) {
68
- // find the commit whose subject *is* the last version, not a tag (tags may not exist locally)
69
- const lastCommit = last ? gitOrNull(['log', '-E', '--pretty=%H', '-1', `--grep=^${last}$`], cwd) : null;
70
- const range = lastCommit ? `${lastCommit}..HEAD` : 'HEAD';
93
+ function generateSummary(cwd, since) {
94
+ const sinceCommit = since ? findVersionCommit(cwd, since) : null;
95
+ if (since && !sinceCommit) {
96
+ throw new Error(`No commit found for --since ${since} (expected subject "${since}" or "Release: ${since}").`);
97
+ }
98
+ const range = sinceCommit ? `${sinceCommit}..HEAD` : 'HEAD';
71
99
 
72
100
  const log = gitOrNull(['log', range, '--no-merges', '--pretty=%s'], cwd) ?? '';
73
101
  const buckets = { features: [], fixes: [], docs: [], chores: [] };
74
102
 
75
103
  for (const subject of log.split('\n')) {
76
- if (!subject || VERSION_RE.test(subject)) continue;
104
+ if (!subject || parseVersionSubject(subject)) continue;
77
105
 
78
106
  const stripped = subject.includes(': ') ? subject.slice(subject.indexOf(': ') + 2) : subject;
79
107
  const text = stripped.charAt(0).toUpperCase() + stripped.slice(1);
@@ -135,7 +163,9 @@ export async function release(argv, cwd = process.cwd()) {
135
163
 
136
164
  let notes = opts.notes;
137
165
  if (!notes) {
138
- const summary = generateSummary(cwd, last);
166
+ const since = opts.since || last;
167
+ if (opts.since) console.log(`Summarizing commits since ${opts.since}`);
168
+ const summary = generateSummary(cwd, since);
139
169
  notes = await promptNotes(version, summary);
140
170
  }
141
171