jvm-upgrade 0.2.0 → 0.3.1
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 +13 -7
- package/dist/format.js +102 -19
- package/dist/main.js +23 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -40,12 +40,14 @@ Or run it once with `npx jvm-upgrade`. Needs Node.js 22.13 or newer.
|
|
|
40
40
|
## Usage
|
|
41
41
|
|
|
42
42
|
```sh
|
|
43
|
-
jvm-upgrade
|
|
44
|
-
jvm-upgrade -u
|
|
45
|
-
jvm-upgrade -u -t minor
|
|
46
|
-
jvm-upgrade -i
|
|
47
|
-
jvm-upgrade -l
|
|
48
|
-
jvm-upgrade -
|
|
43
|
+
jvm-upgrade # list available upgrades (nothing is changed)
|
|
44
|
+
jvm-upgrade -u # apply the latest major versions
|
|
45
|
+
jvm-upgrade -u -t minor # apply the latest minor versions only
|
|
46
|
+
jvm-upgrade -i # pick versions and read changelogs interactively
|
|
47
|
+
jvm-upgrade -l # print a changelog link for each upgrade
|
|
48
|
+
jvm-upgrade --changelog-latest # print the release notes of each new version
|
|
49
|
+
jvm-upgrade --changelog-diff # print the release notes of every version in between
|
|
50
|
+
jvm-upgrade -c 7 # ignore versions published in the last 7 days
|
|
49
51
|
jvm-upgrade path/to/project
|
|
50
52
|
```
|
|
51
53
|
|
|
@@ -70,7 +72,9 @@ jvm-upgrade path/to/project
|
|
|
70
72
|
| `[directory]` | `.` | Project root to scan |
|
|
71
73
|
| `-u, --upgrade` | | Write the upgrades to the build files |
|
|
72
74
|
| `-i, --interactive` | | Pick upgrades in a terminal UI |
|
|
73
|
-
| `-l, --changelog` | | Print
|
|
75
|
+
| `-l, --changelog` | | Print a changelog link for each selected upgrade |
|
|
76
|
+
| `--changelog-latest` | | Print the release notes of the new version only |
|
|
77
|
+
| `--changelog-diff` | | Print the release notes of every version between the old and the new one |
|
|
74
78
|
| `-t, --target <level>` | `major` | Highest jump allowed: `major`, `minor` or `patch` |
|
|
75
79
|
| `-c, --cooldown <days>` | `0` | Skip versions published less than N days ago |
|
|
76
80
|
| `--allow-downgrade` | | With `--cooldown`, roll back versions that are too new |
|
|
@@ -132,6 +136,8 @@ For each upgrade, jvm-upgrade finds the source repository:
|
|
|
132
136
|
|
|
133
137
|
It then reads the GitHub releases between your version and the new one. Prereleases are skipped unless you upgrade to one. If there are no releases, it reads `CHANGELOG.md` (and similar files) and cuts out the right sections. Tags without the major version (protobuf-java `4.36.2` is tag `v36.2`) are matched too.
|
|
134
138
|
|
|
139
|
+
Notes are cleaned up for the terminal: headings, bullets and code are styled, links and HTML are removed, pull request and commit links become `#123` and short hashes, and Renovate/Dependabot bumps are hidden. Each version shows at most 40 lines, followed by a link to the rest. In the interactive picker (`c`), you see the full notes.
|
|
140
|
+
|
|
135
141
|
If it finds nothing, it gives you a link: the release notes page for androidx, Google Play services, Firebase and Gradle, or the project's releases page.
|
|
136
142
|
|
|
137
143
|
Set `GITHUB_TOKEN` (or `GH_TOKEN`) to avoid GitHub rate limits.
|
package/dist/format.js
CHANGED
|
@@ -51,37 +51,120 @@ export function toJson(rows, root) {
|
|
|
51
51
|
};
|
|
52
52
|
});
|
|
53
53
|
}
|
|
54
|
-
export function formatChangelog(log, width) {
|
|
54
|
+
export function formatChangelog(log, width, opts = {}) {
|
|
55
55
|
if (!log.entries.length) {
|
|
56
|
-
return log.source ? `No release notes found here. Read them at:\n${log.source}` : 'No release notes found.';
|
|
56
|
+
return log.source ? ` No release notes found here. Read them at:\n ${styleText('cyan', log.source)}` : ' No release notes found.';
|
|
57
57
|
}
|
|
58
58
|
const out = [];
|
|
59
59
|
for (const entry of log.entries) {
|
|
60
|
-
const
|
|
61
|
-
|
|
60
|
+
const title = entry.title && !isJustVersion(entry.title, entry.version) ? styleText('gray', ` ${entry.title}`) : '';
|
|
61
|
+
const date = entry.date ? styleText('gray', ` · ${entry.date.slice(0, 10)}`) : '';
|
|
62
|
+
out.push(` ${styleText(['bold', 'green'], `● ${entry.version}`)}${title}${date}`);
|
|
62
63
|
if (entry.url)
|
|
63
|
-
out.push(styleText('gray', entry.url));
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
out.push(` ${styleText('gray', entry.url)}`);
|
|
65
|
+
const body = renderMarkdown(entry.body, width - 4);
|
|
66
|
+
const shown = opts.maxLines && body.length > opts.maxLines ? body.slice(0, opts.maxLines) : body;
|
|
67
|
+
if (shown.length)
|
|
68
|
+
out.push('', ...shown.map((line) => (line ? ` ${line}` : '')));
|
|
69
|
+
if (shown.length < body.length) {
|
|
70
|
+
out.push(styleText('gray', ` … ${body.length - shown.length} more lines${entry.url ? ` at ${entry.url}` : ''}`));
|
|
71
|
+
}
|
|
67
72
|
out.push('');
|
|
68
73
|
}
|
|
69
|
-
if (log.source)
|
|
70
|
-
out.push(styleText('gray', `Source: ${log.source}`));
|
|
71
|
-
return out.join('\n');
|
|
74
|
+
if (log.source && log.entries.some((e) => !e.url))
|
|
75
|
+
out.push(styleText('gray', ` Source: ${log.source}`));
|
|
76
|
+
return out.join('\n').trimEnd();
|
|
77
|
+
}
|
|
78
|
+
export function formatChangelogHeader(name, from, to, width) {
|
|
79
|
+
const title = ` ${name} ${from} → ${to.version} `;
|
|
80
|
+
const rule = '━'.repeat(Math.max(3, Math.min(width, 100) - title.length - 3));
|
|
81
|
+
return `${styleText('gray', '━━')}${styleText('bold', ` ${name} `)}${from} → ${styleText(['bold', levelColor(to.level)], to.version)} ${styleText('gray', rule)}`;
|
|
82
|
+
}
|
|
83
|
+
export function formatChangelogLinks(links) {
|
|
84
|
+
const nameWidth = Math.max(...links.map((l) => l.name.length));
|
|
85
|
+
const jumpWidth = Math.max(...links.map((l) => `${l.from} → ${l.to.version}`.length));
|
|
86
|
+
const lines = links.map((l) => {
|
|
87
|
+
const jump = `${l.from} → ${l.to.version}`;
|
|
88
|
+
const colored = `${l.from} → ${styleText(levelColor(l.to.level), l.to.version)}${' '.repeat(jumpWidth - jump.length)}`;
|
|
89
|
+
return `${l.name.padEnd(nameWidth)} ${colored} ${l.url ? styleText('cyan', l.url) : styleText('gray', 'no release notes found')}`;
|
|
90
|
+
});
|
|
91
|
+
return [styleText('bold', 'Changelogs'), ...lines].join('\n');
|
|
92
|
+
}
|
|
93
|
+
/** Link to the notes of the target version when found, else to the general notes page. */
|
|
94
|
+
export function changelogUrl(log) {
|
|
95
|
+
return log.entries[0]?.url ?? log.source;
|
|
96
|
+
}
|
|
97
|
+
function isJustVersion(title, version) {
|
|
98
|
+
return title.replace(/^v/i, '') === version || !title.replace(new RegExp(version.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), '').replace(/[\sv]/gi, '');
|
|
99
|
+
}
|
|
100
|
+
/** Turns release-note markdown into short, styled terminal lines. */
|
|
101
|
+
export function renderMarkdown(markdown, width) {
|
|
102
|
+
const text = markdown
|
|
103
|
+
.replace(/\r/g, '')
|
|
104
|
+
.replace(/<!--[\s\S]*?-->/g, '')
|
|
105
|
+
.replace(/<\/?[a-z][^>]*>/gi, '')
|
|
106
|
+
.replace(/!\[[^\]]*\]\([^)]*\)/g, '')
|
|
107
|
+
.replace(/\[([^\]]+)\]\((?:https?:[^)\s]+)\)/g, '$1')
|
|
108
|
+
.replace(/https:\/\/github\.com\/[^/\s]+\/[^/\s]+\/(?:pull|issues)\/(\d+)/g, '#$1')
|
|
109
|
+
.replace(/\(?https:\/\/github\.com\/[^/\s]+\/[^/\s]+\/commit\/([0-9a-f]{8})[0-9a-f]*\)?/g, '($1)')
|
|
110
|
+
.replace(/^\s*(?:\*\*|__)([^*_\n]+)(?:\*\*|__):?\s*$/gm, '### $1')
|
|
111
|
+
.replace(/^\s*[-*+]\s.*@(?:renovate|dependabot)\[bot\].*$\n?/gim, '')
|
|
112
|
+
.replace(/\*\*|__/g, '');
|
|
113
|
+
const out = [];
|
|
114
|
+
let fence = false;
|
|
115
|
+
let afterHeading = false;
|
|
116
|
+
for (const raw of text.split('\n')) {
|
|
117
|
+
const line = raw.trimEnd();
|
|
118
|
+
const wasHeading = afterHeading;
|
|
119
|
+
afterHeading = false;
|
|
120
|
+
if (/^\s*```/.test(line)) {
|
|
121
|
+
fence = !fence;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (fence) {
|
|
125
|
+
out.push(styleText('cyan', line));
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (/^\s*\[[^\]]+\]:\s*\S+$/.test(line) || /^\s*([-*_])(\s*\1){2,}\s*$/.test(line))
|
|
129
|
+
continue;
|
|
130
|
+
const heading = /^#{1,6}\s+(.*?)\s*#*$/.exec(line);
|
|
131
|
+
if (heading) {
|
|
132
|
+
if (out.length && out.at(-1) !== '')
|
|
133
|
+
out.push('');
|
|
134
|
+
out.push(styleText(['bold', 'yellow'], heading[1]));
|
|
135
|
+
afterHeading = true;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (!line.trim()) {
|
|
139
|
+
if (out.length && out.at(-1) !== '' && !wasHeading)
|
|
140
|
+
out.push('');
|
|
141
|
+
afterHeading = wasHeading;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
const bullet = /^(\s*)[-*+]\s+(.*)$/.exec(line);
|
|
145
|
+
if (bullet) {
|
|
146
|
+
const indent = ' '.repeat(Math.min(Math.floor(bullet[1].length / 2), 3));
|
|
147
|
+
out.push(...wrap(bullet[2], width - indent.length - 2).map((l, i) => `${indent}${i ? ' ' : '• '}${code(l)}`));
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
out.push(...wrap(line.trim(), width).map(code));
|
|
151
|
+
}
|
|
152
|
+
while (out.at(-1) === '')
|
|
153
|
+
out.pop();
|
|
154
|
+
return out;
|
|
155
|
+
}
|
|
156
|
+
function code(line) {
|
|
157
|
+
return line.replace(/`([^`]+)`/g, (_, c) => styleText('cyan', c));
|
|
72
158
|
}
|
|
73
|
-
function wrap(
|
|
74
|
-
if (
|
|
75
|
-
return [
|
|
76
|
-
const indent = /^\s*([-*+]\s+|\d+\.\s+)?/.exec(line)[0].length;
|
|
77
|
-
const pad = ' '.repeat(Math.min(indent, 8));
|
|
78
|
-
const words = line.split(' ');
|
|
159
|
+
function wrap(text, width) {
|
|
160
|
+
if (text.length <= width || width < 20)
|
|
161
|
+
return [text];
|
|
79
162
|
const lines = [];
|
|
80
163
|
let current = '';
|
|
81
|
-
for (const word of
|
|
164
|
+
for (const word of text.split(' ')) {
|
|
82
165
|
if (current && current.length + word.length + 1 > width) {
|
|
83
166
|
lines.push(current);
|
|
84
|
-
current =
|
|
167
|
+
current = word;
|
|
85
168
|
}
|
|
86
169
|
else {
|
|
87
170
|
current = current ? `${current} ${word}` : word;
|
package/dist/main.js
CHANGED
|
@@ -5,7 +5,7 @@ import { parseArgs, styleText } from 'node:util';
|
|
|
5
5
|
import { applyEdits, EditConflictError } from './apply.js';
|
|
6
6
|
import { getChangelog } from './changelog.js';
|
|
7
7
|
import { ConfigError, loadCredentials, loadUserConfig, projectConfigResolver, splitPatterns } from './config.js';
|
|
8
|
-
import { formatChangelog, renderTable, toJson } from './format.js';
|
|
8
|
+
import { changelogUrl, formatChangelog, formatChangelogHeader, formatChangelogLinks, renderTable, toJson } from './format.js';
|
|
9
9
|
import { clearCache, createHttp, defaultCacheDir } from './http.js';
|
|
10
10
|
import { createProgress } from './progress.js';
|
|
11
11
|
import { buildRows } from './rows.js';
|
|
@@ -20,7 +20,9 @@ Find outdated libraries in Gradle and Maven projects and upgrade them.
|
|
|
20
20
|
Actions
|
|
21
21
|
-u, --upgrade Write the selected upgrades to the build files
|
|
22
22
|
-i, --interactive Pick upgrades (and read changelogs) in a terminal UI
|
|
23
|
-
-l, --changelog Print
|
|
23
|
+
-l, --changelog Print a changelog link for every selected upgrade
|
|
24
|
+
--changelog-latest Print the release notes of the new version only
|
|
25
|
+
--changelog-diff Print the release notes of every version between old and new
|
|
24
26
|
|
|
25
27
|
Policy
|
|
26
28
|
-t, --target <level> Highest jump allowed: major, minor or patch (default: major)
|
|
@@ -59,6 +61,8 @@ function parseCli(argv) {
|
|
|
59
61
|
upgrade: { type: 'boolean', short: 'u' },
|
|
60
62
|
interactive: { type: 'boolean', short: 'i' },
|
|
61
63
|
changelog: { type: 'boolean', short: 'l' },
|
|
64
|
+
'changelog-latest': { type: 'boolean' },
|
|
65
|
+
'changelog-diff': { type: 'boolean' },
|
|
62
66
|
target: { type: 'string', short: 't' },
|
|
63
67
|
cooldown: { type: 'string', short: 'c' },
|
|
64
68
|
'allow-downgrade': { type: 'boolean' },
|
|
@@ -229,8 +233,9 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
229
233
|
chosen.set(row, choice);
|
|
230
234
|
}
|
|
231
235
|
}
|
|
232
|
-
|
|
233
|
-
|
|
236
|
+
const changelogMode = flags['changelog-diff'] ? 'diff' : flags['changelog-latest'] ? 'latest' : flags.changelog ? 'links' : undefined;
|
|
237
|
+
if (changelogMode && !json)
|
|
238
|
+
await printChangelogs(http, chosen, changelogMode, showProgress);
|
|
234
239
|
const writable = [...chosen].filter(([row]) => row.location);
|
|
235
240
|
if (flags.upgrade || flags.interactive) {
|
|
236
241
|
if (!writable.length) {
|
|
@@ -265,17 +270,27 @@ function loadChangelog(http, row, choice) {
|
|
|
265
270
|
const dep = row.deps[0];
|
|
266
271
|
return getChangelog(http, dep, row.current, choice.version, row.repoOf.get(choice.version), compareVersions);
|
|
267
272
|
}
|
|
268
|
-
|
|
273
|
+
const MAX_NOTE_LINES = 40;
|
|
274
|
+
async function printChangelogs(http, chosen, mode, showProgress) {
|
|
269
275
|
const entries = [...chosen];
|
|
276
|
+
if (!entries.length)
|
|
277
|
+
return;
|
|
270
278
|
const progress = createProgress('Loading changelogs', process.stderr, showProgress);
|
|
271
279
|
progress.total(entries.length);
|
|
272
280
|
const logs = await Promise.all(entries.map(([row, choice]) => loadChangelog(http, row, choice)
|
|
273
|
-
.catch((
|
|
281
|
+
.catch(() => ({ entries: [] }))
|
|
274
282
|
.finally(() => progress.tick())));
|
|
275
283
|
progress.done();
|
|
284
|
+
if (mode === 'links') {
|
|
285
|
+
const links = entries.map(([row, choice], i) => ({ name: row.name, from: row.current, to: choice, url: changelogUrl(logs[i]) }));
|
|
286
|
+
process.stdout.write(`\n${formatChangelogLinks(links)}\n`);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
276
289
|
const width = Math.min(process.stdout.columns ?? 100, 120) - 2;
|
|
277
290
|
entries.forEach(([row, choice], i) => {
|
|
278
|
-
|
|
279
|
-
|
|
291
|
+
const log = logs[i];
|
|
292
|
+
const shown = mode === 'latest' ? { ...log, entries: log.entries.slice(0, 1) } : log;
|
|
293
|
+
process.stdout.write(`\n${formatChangelogHeader(row.name, row.current, choice, width)}\n\n`);
|
|
294
|
+
process.stdout.write(`${formatChangelog(shown, width, { maxLines: MAX_NOTE_LINES })}\n`);
|
|
280
295
|
});
|
|
281
296
|
}
|