@eclipse-docks/cli 0.7.114 → 0.7.122
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 +17 -9
- package/package.json +1 -1
- package/src/commands/release.js +92 -25
package/README.md
CHANGED
|
@@ -9,18 +9,26 @@ 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 --
|
|
13
|
-
npx @eclipse-docks/cli release --
|
|
12
|
+
npx @eclipse-docks/cli release --since v1.2.0 # notes from commits since that tag
|
|
13
|
+
npx @eclipse-docks/cli release --dry-run # print what would be tagged
|
|
14
|
+
npx @eclipse-docks/cli release --no-push # create the tag locally without pushing
|
|
14
15
|
```
|
|
15
16
|
|
|
16
17
|
## `release`
|
|
17
18
|
|
|
18
|
-
Creates an
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
Creates an annotated tag whose name is the version (`vX.Y.Z`) and pushes it.
|
|
20
|
+
When pushing (the default), any unpushed commits on the current branch are pushed
|
|
21
|
+
to `origin` first. The tag message body becomes the GitHub Release notes.
|
|
22
|
+
|
|
23
|
+
Wire release in CI with [`.github/workflows/app-release.yml`](../../.github/workflows/app-release.yml):
|
|
24
|
+
downstream apps usually trigger it on `v*` tag push; this monorepo waits for a green
|
|
25
|
+
**CI** run on `main` first (see [`.github/workflows/release.yml`](../../.github/workflows/release.yml)).
|
|
26
|
+
|
|
27
|
+
The last version is the most recent `vX.Y.Z` tag reachable from `HEAD` (via
|
|
28
|
+
`git describe`), not the highest semver tag in the repository. That drives the
|
|
29
|
+
default bump and changelog range.
|
|
22
30
|
|
|
23
31
|
If no release notes are given via `-m`, an end-user-facing summary is generated from the
|
|
24
|
-
commits since
|
|
25
|
-
**Chore/Refactor**, and offered as the default (press Enter on
|
|
26
|
-
accept it).
|
|
32
|
+
commits since that tag (or since `--since vX.Y.Z` when set), grouped under **Features**,
|
|
33
|
+
**Fixes**, **Docs**, and **Chore/Refactor**, and offered as the default (press Enter on
|
|
34
|
+
an empty first line to accept it).
|
package/package.json
CHANGED
package/src/commands/release.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
2
|
import { createInterface } from 'node:readline/promises';
|
|
3
3
|
|
|
4
|
+
/** CLI arg / release tag name: exact `vX.Y.Z`. */
|
|
4
5
|
const VERSION_RE = /^v\d+\.\d+\.\d+$/;
|
|
5
6
|
|
|
6
7
|
function git(args, cwd) {
|
|
@@ -16,8 +17,12 @@ function gitOrNull(args, cwd) {
|
|
|
16
17
|
return result.status === 0 ? result.stdout.trim() : null;
|
|
17
18
|
}
|
|
18
19
|
|
|
20
|
+
function usage() {
|
|
21
|
+
return 'Usage: docks release [major|minor|patch|vX.Y.Z] [-m "release notes"] [--since vX.Y.Z] [--dry-run] [--no-push]';
|
|
22
|
+
}
|
|
23
|
+
|
|
19
24
|
function parseArgs(argv) {
|
|
20
|
-
const opts = { bump: 'patch', version: '', notes: '', dryRun: false, push: true };
|
|
25
|
+
const opts = { bump: 'patch', version: '', since: '', notes: '', dryRun: false, push: true };
|
|
21
26
|
|
|
22
27
|
for (let i = 0; i < argv.length; i++) {
|
|
23
28
|
const arg = argv[i];
|
|
@@ -25,6 +30,12 @@ function parseArgs(argv) {
|
|
|
25
30
|
opts.bump = arg;
|
|
26
31
|
} else if (VERSION_RE.test(arg)) {
|
|
27
32
|
opts.version = arg;
|
|
33
|
+
} else if (arg === '--since') {
|
|
34
|
+
const value = argv[++i] ?? '';
|
|
35
|
+
if (!VERSION_RE.test(value)) {
|
|
36
|
+
throw new Error(`--since requires a version like v1.2.3\n${usage()}`);
|
|
37
|
+
}
|
|
38
|
+
opts.since = value;
|
|
28
39
|
} else if (arg === '-m' || arg === '--message') {
|
|
29
40
|
opts.notes = argv[++i] ?? '';
|
|
30
41
|
} else if (arg === '-n' || arg === '--dry-run') {
|
|
@@ -34,9 +45,7 @@ function parseArgs(argv) {
|
|
|
34
45
|
} else if (arg === '--no-push') {
|
|
35
46
|
opts.push = false;
|
|
36
47
|
} 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
|
-
);
|
|
48
|
+
throw new Error(`Unknown argument: ${arg}\n${usage()}`);
|
|
40
49
|
}
|
|
41
50
|
}
|
|
42
51
|
|
|
@@ -44,12 +53,57 @@ function parseArgs(argv) {
|
|
|
44
53
|
}
|
|
45
54
|
|
|
46
55
|
function findLastVersion(cwd) {
|
|
47
|
-
const
|
|
48
|
-
if (!
|
|
49
|
-
|
|
50
|
-
|
|
56
|
+
const tag = gitOrNull(['describe', '--tags', '--abbrev=0', '--match', 'v*', 'HEAD'], cwd);
|
|
57
|
+
if (!tag) return null;
|
|
58
|
+
const trimmed = tag.trim();
|
|
59
|
+
return VERSION_RE.test(trimmed) ? trimmed : null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function findVersionCommit(cwd, version) {
|
|
63
|
+
return gitOrNull(['rev-parse', '--verify', `${version}^{commit}`], cwd);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function currentBranch(cwd) {
|
|
67
|
+
const branch = gitOrNull(['rev-parse', '--abbrev-ref', 'HEAD'], cwd);
|
|
68
|
+
if (!branch || branch === 'HEAD') return null;
|
|
69
|
+
return branch;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function upstreamStatus(cwd) {
|
|
73
|
+
const upstream = gitOrNull(['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}'], cwd);
|
|
74
|
+
if (!upstream) return null;
|
|
75
|
+
const counts = git(['rev-list', '--left-right', '--count', `${upstream}...HEAD`], cwd);
|
|
76
|
+
const [behind, ahead] = counts.split('\t').map(Number);
|
|
77
|
+
return { upstream, behind, ahead };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function pushBranchIfNeeded(cwd, { dryRun = false } = {}) {
|
|
81
|
+
const branch = currentBranch(cwd);
|
|
82
|
+
if (!branch) {
|
|
83
|
+
throw new Error('Detached HEAD — checkout a branch before releasing.');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const status = upstreamStatus(cwd);
|
|
87
|
+
if (!status) {
|
|
88
|
+
throw new Error(`Branch ${branch} has no upstream. Push it first:\n git push -u origin ${branch}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (status.behind > 0) {
|
|
92
|
+
throw new Error(
|
|
93
|
+
`Branch ${branch} is ${status.behind} commit(s) behind ${status.upstream}. Pull before releasing.`,
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (status.ahead === 0) return branch;
|
|
98
|
+
|
|
99
|
+
if (dryRun) {
|
|
100
|
+
console.log(`[dry run] would push ${status.ahead} commit(s) to origin ${branch}`);
|
|
101
|
+
return branch;
|
|
51
102
|
}
|
|
52
|
-
|
|
103
|
+
|
|
104
|
+
console.log(`Pushing ${status.ahead} commit(s) to origin ${branch}...`);
|
|
105
|
+
git(['push', 'origin', branch], cwd);
|
|
106
|
+
return branch;
|
|
53
107
|
}
|
|
54
108
|
|
|
55
109
|
function bumpVersion(last, bump) {
|
|
@@ -64,16 +118,18 @@ function bumpVersion(last, bump) {
|
|
|
64
118
|
}
|
|
65
119
|
}
|
|
66
120
|
|
|
67
|
-
function generateSummary(cwd,
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
121
|
+
function generateSummary(cwd, since) {
|
|
122
|
+
const sinceCommit = since ? findVersionCommit(cwd, since) : null;
|
|
123
|
+
if (since && !sinceCommit) {
|
|
124
|
+
throw new Error(`No tag found for --since ${since}.`);
|
|
125
|
+
}
|
|
126
|
+
const range = sinceCommit ? `${sinceCommit}..HEAD` : 'HEAD';
|
|
71
127
|
|
|
72
128
|
const log = gitOrNull(['log', range, '--no-merges', '--pretty=%s'], cwd) ?? '';
|
|
73
129
|
const buckets = { features: [], fixes: [], docs: [], chores: [] };
|
|
74
130
|
|
|
75
131
|
for (const subject of log.split('\n')) {
|
|
76
|
-
if (!subject || VERSION_RE.test(subject)) continue;
|
|
132
|
+
if (!subject || VERSION_RE.test(subject.trim())) continue;
|
|
77
133
|
|
|
78
134
|
const stripped = subject.includes(': ') ? subject.slice(subject.indexOf(': ') + 2) : subject;
|
|
79
135
|
const text = stripped.charAt(0).toUpperCase() + stripped.slice(1);
|
|
@@ -126,7 +182,7 @@ export async function release(argv, cwd = process.cwd()) {
|
|
|
126
182
|
let version = opts.version;
|
|
127
183
|
if (!version) {
|
|
128
184
|
version = bumpVersion(last ?? 'v0.0.0', opts.bump);
|
|
129
|
-
console.log(`Last
|
|
185
|
+
console.log(`Last release tag: ${last ?? 'v0.0.0'}`);
|
|
130
186
|
}
|
|
131
187
|
|
|
132
188
|
if (gitOrNull(['rev-parse', version], cwd)) {
|
|
@@ -135,28 +191,39 @@ export async function release(argv, cwd = process.cwd()) {
|
|
|
135
191
|
|
|
136
192
|
let notes = opts.notes;
|
|
137
193
|
if (!notes) {
|
|
138
|
-
const
|
|
194
|
+
const since = opts.since || last;
|
|
195
|
+
if (since) console.log(`Summarizing commits since ${since}`);
|
|
196
|
+
const summary = generateSummary(cwd, since);
|
|
139
197
|
notes = await promptNotes(version, summary);
|
|
140
198
|
}
|
|
141
199
|
|
|
142
200
|
if (opts.dryRun) {
|
|
143
|
-
|
|
201
|
+
if (opts.push) pushBranchIfNeeded(cwd, { dryRun: true });
|
|
202
|
+
console.log(`\n[dry run] would create annotated tag ${version} at HEAD`);
|
|
144
203
|
console.log(notes.replace(/^/gm, ' '));
|
|
145
204
|
if (opts.push) {
|
|
146
|
-
|
|
147
|
-
console.log(`[dry run] would push to origin ${branch}`);
|
|
205
|
+
console.log(`[dry run] would push tag to origin ${version}`);
|
|
148
206
|
}
|
|
149
207
|
return;
|
|
150
208
|
}
|
|
151
209
|
|
|
152
|
-
|
|
210
|
+
if (opts.push) {
|
|
211
|
+
pushBranchIfNeeded(cwd);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const tagArgs = ['tag', '-a', version, '-m', version];
|
|
215
|
+
if (notes.trim()) {
|
|
216
|
+
tagArgs.push('-m', notes.trimEnd());
|
|
217
|
+
}
|
|
218
|
+
git(tagArgs, cwd);
|
|
153
219
|
|
|
154
|
-
const branch = git(['rev-parse', '--abbrev-ref', 'HEAD'], cwd);
|
|
155
220
|
if (opts.push) {
|
|
156
|
-
git(['push', 'origin',
|
|
157
|
-
console.log(`\nPushed ${version}
|
|
221
|
+
git(['push', 'origin', version], cwd);
|
|
222
|
+
console.log(`\nPushed tag ${version}; the release workflow will take over.`);
|
|
158
223
|
} else {
|
|
159
|
-
console.log(`\nCreated
|
|
160
|
-
|
|
224
|
+
console.log(`\nCreated annotated tag ${version}. Push your branch and tag to trigger the release:`);
|
|
225
|
+
const branch = currentBranch(cwd);
|
|
226
|
+
if (branch) console.log(` git push origin ${branch}`);
|
|
227
|
+
console.log(` git push origin ${version}`);
|
|
161
228
|
}
|
|
162
229
|
}
|