rman 2.25.1 → 2.26.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.
|
@@ -37,6 +37,12 @@ export declare class NodeDependencyUpdater implements DependencyUpdater {
|
|
|
37
37
|
/** Fills in what `d` may move to, from its own settings and the rules that do not depend on what
|
|
38
38
|
* else moves (steps 1-3). */
|
|
39
39
|
protected prepare(d: NodeDependencyUpdater.Decision, info: NpmReleases | undefined): void;
|
|
40
|
+
/**
|
|
41
|
+
* The first range of a dependency rman does not move - a pinned `11.7.5`, a `>=2 <3` - that the
|
|
42
|
+
* registry's newest offerable release falls outside, and that release. `undefined` when every
|
|
43
|
+
* such range still takes it, which is nothing worth a row.
|
|
44
|
+
*/
|
|
45
|
+
protected outgrown(d: NodeDependencyUpdater.Decision, releases: readonly NpmRelease[]): NodeDependencyUpdater.Decision['unmovable'];
|
|
40
46
|
/** Whether the registry offers `release` as a move at all: newer than what is declared, not
|
|
41
47
|
* deprecated, not above the `latest` tag, and not a prerelease unless the range is on one. */
|
|
42
48
|
protected offerable(d: NodeDependencyUpdater.Decision, release: NpmRelease): boolean;
|
|
@@ -84,6 +90,15 @@ export declare class NodeDependencyUpdater implements DependencyUpdater {
|
|
|
84
90
|
/** The in-repo packages `pkg` declares a dependency on. */
|
|
85
91
|
protected siblingsOf(pkg: Package, repoPackages: ReadonlyMap<string, Package>): Package[];
|
|
86
92
|
protected toEntry(d: NodeDependencyUpdater.Decision): DependencyUpdater.Entry;
|
|
93
|
+
/**
|
|
94
|
+
* The version of `name` that `pkg` resolves today: the first `node_modules/<name>/package.json`
|
|
95
|
+
* walking up from its directory, which is Node's own lookup - so a hoisted copy at a monorepo's
|
|
96
|
+
* root counts, and a nested one under the package wins over it.
|
|
97
|
+
*/
|
|
98
|
+
protected installedVersion(pkg: Package, name: string): string | undefined;
|
|
99
|
+
/** The newest release `range` takes, as npm's install would pick it: no prerelease unless the
|
|
100
|
+
* range asks for one, nothing above the `latest` tag, a deprecated one only when nothing else is. */
|
|
101
|
+
protected resolvedVersion(d: NodeDependencyUpdater.Decision, range: string): string | undefined;
|
|
87
102
|
protected manifestFile(pkg: Package): string;
|
|
88
103
|
/** `fn` over `items`, at most `limit` at a time. */
|
|
89
104
|
protected mapConcurrent<T>(items: readonly T[], limit: number, fn: (item: T) => Promise<void>): Promise<void>;
|
|
@@ -98,6 +113,16 @@ export declare namespace NodeDependencyUpdater {
|
|
|
98
113
|
fields: Map<string, string>;
|
|
99
114
|
/** The fields whose range is rewritten. */
|
|
100
115
|
movable: string[];
|
|
116
|
+
/** The fields holding a valid range rman does not rewrite - a pinned version, `>=a <b`. */
|
|
117
|
+
fixed: string[];
|
|
118
|
+
/** Not planned, but listed when a release falls outside a `fixed` range - see `outgrown`. */
|
|
119
|
+
listed?: boolean;
|
|
120
|
+
/** That release, and the range it falls outside. */
|
|
121
|
+
unmovable?: {
|
|
122
|
+
field: string;
|
|
123
|
+
range: string;
|
|
124
|
+
version: string;
|
|
125
|
+
};
|
|
101
126
|
/** Whether this name is planned at all - rather than only stating rules for the others. */
|
|
102
127
|
wanted: boolean;
|
|
103
128
|
/** What is declared now, as a version: the highest floor among the movable ranges. */
|
|
@@ -64,7 +64,15 @@ export class NodeDependencyUpdater {
|
|
|
64
64
|
for (const d of decisions)
|
|
65
65
|
this.prepare(d, registry.get(d.name));
|
|
66
66
|
this.resolve(decisions, ctx);
|
|
67
|
-
return decisions
|
|
67
|
+
return decisions
|
|
68
|
+
.filter(d => d.wanted || d.unmovable)
|
|
69
|
+
.map(d => {
|
|
70
|
+
/** Set on the entry itself, never a copy: `planned` is keyed by that object. */
|
|
71
|
+
const entry = this.toEntry(d);
|
|
72
|
+
entry.installed = this.installedVersion(d.pkg, d.name);
|
|
73
|
+
entry.resolved = this.resolvedVersion(d, entry.current);
|
|
74
|
+
return entry;
|
|
75
|
+
});
|
|
68
76
|
}
|
|
69
77
|
async applyPlan(ctx, plan) {
|
|
70
78
|
const originals = new Map();
|
|
@@ -142,6 +150,7 @@ export class NodeDependencyUpdater {
|
|
|
142
150
|
settings,
|
|
143
151
|
fields: new Map(),
|
|
144
152
|
movable: [],
|
|
153
|
+
fixed: [],
|
|
145
154
|
wanted: false,
|
|
146
155
|
allowed: [],
|
|
147
156
|
candidates: [],
|
|
@@ -150,14 +159,19 @@ export class NodeDependencyUpdater {
|
|
|
150
159
|
byName.set(name, d);
|
|
151
160
|
}
|
|
152
161
|
d.fields.set(field, range);
|
|
153
|
-
if (
|
|
162
|
+
if (kinds && !kinds.includes(field))
|
|
163
|
+
continue;
|
|
164
|
+
if (this.movablePattern.test(range))
|
|
154
165
|
d.movable.push(field);
|
|
166
|
+
else if (semver.validRange(range))
|
|
167
|
+
d.fixed.push(field);
|
|
155
168
|
}
|
|
156
169
|
}
|
|
157
170
|
for (const d of byName.values()) {
|
|
158
171
|
const picked = !options.names?.length || micromatch.isMatch(d.name, options.names);
|
|
159
172
|
const rejected = settings.reject.length > 0 && micromatch.isMatch(d.name, settings.reject);
|
|
160
173
|
d.wanted = d.movable.length > 0 && picked && !rejected;
|
|
174
|
+
d.listed = !d.wanted && d.fixed.length > 0 && picked && !rejected;
|
|
161
175
|
if (d.wanted) {
|
|
162
176
|
d.floor = d.movable.map(f => semver.minVersion(d.fields.get(f)).version).sort(semver.rcompare)[0];
|
|
163
177
|
}
|
|
@@ -196,6 +210,8 @@ export class NodeDependencyUpdater {
|
|
|
196
210
|
prepare(d, info) {
|
|
197
211
|
d.releases = new Map((info?.releases ?? []).map(r => [r.version, r]));
|
|
198
212
|
d.latestTag = info?.latest;
|
|
213
|
+
if (d.listed && info)
|
|
214
|
+
d.unmovable = this.outgrown(d, info.releases);
|
|
199
215
|
if (!d.wanted)
|
|
200
216
|
return;
|
|
201
217
|
if (!info) {
|
|
@@ -220,6 +236,30 @@ export class NodeDependencyUpdater {
|
|
|
220
236
|
}
|
|
221
237
|
d.index = d.candidates.length ? 0 : -1;
|
|
222
238
|
}
|
|
239
|
+
/**
|
|
240
|
+
* The first range of a dependency rman does not move - a pinned `11.7.5`, a `>=2 <3` - that the
|
|
241
|
+
* registry's newest offerable release falls outside, and that release. `undefined` when every
|
|
242
|
+
* such range still takes it, which is nothing worth a row.
|
|
243
|
+
*/
|
|
244
|
+
/* **Listed, never moved.** Only a `^` or `~` range on a plain version is rewritten, since anything
|
|
245
|
+
* else was written that way on purpose. But leaving such a dependency out of the table entirely
|
|
246
|
+
* hid it: measured on `panates/hl7v2`, `"mocha": "11.7.5"` with 12.x out appeared nowhere, and a
|
|
247
|
+
* person reading the table took that for "up to date". A `skipped` row says what is there and why
|
|
248
|
+
* it stays. */
|
|
249
|
+
outgrown(d, releases) {
|
|
250
|
+
for (const field of d.fixed) {
|
|
251
|
+
const range = d.fields.get(field);
|
|
252
|
+
const newest = releases
|
|
253
|
+
.map(r => semver.parse(r.version))
|
|
254
|
+
.filter((v) => !!v && !v.prerelease.length)
|
|
255
|
+
.filter(v => !releases.find(r => r.version === v.version)?.deprecated)
|
|
256
|
+
.filter(v => !d.latestTag || !semver.valid(d.latestTag) || semver.lte(v, d.latestTag))
|
|
257
|
+
.sort(semver.rcompare)[0];
|
|
258
|
+
if (newest && !semver.satisfies(newest, range))
|
|
259
|
+
return { field, range, version: newest.version };
|
|
260
|
+
}
|
|
261
|
+
return undefined;
|
|
262
|
+
}
|
|
223
263
|
/** Whether the registry offers `release` as a move at all: newer than what is declared, not
|
|
224
264
|
* deprecated, not above the `latest` tag, and not a prerelease unless the range is on one. */
|
|
225
265
|
offerable(d, release) {
|
|
@@ -431,6 +471,19 @@ export class NodeDependencyUpdater {
|
|
|
431
471
|
return [...result];
|
|
432
472
|
}
|
|
433
473
|
toEntry(d) {
|
|
474
|
+
if (!d.wanted && d.unmovable) {
|
|
475
|
+
const { field, range, version } = d.unmovable;
|
|
476
|
+
return {
|
|
477
|
+
package: d.pkg,
|
|
478
|
+
name: d.name,
|
|
479
|
+
types: [field],
|
|
480
|
+
current: range,
|
|
481
|
+
status: 'skipped',
|
|
482
|
+
latest: version,
|
|
483
|
+
available: version,
|
|
484
|
+
reason: 'not a ^ or ~ range - rman moves only those',
|
|
485
|
+
};
|
|
486
|
+
}
|
|
434
487
|
const entry = {
|
|
435
488
|
package: d.pkg,
|
|
436
489
|
name: d.name,
|
|
@@ -461,6 +514,41 @@ export class NodeDependencyUpdater {
|
|
|
461
514
|
this.planned.set(result, d);
|
|
462
515
|
return result;
|
|
463
516
|
}
|
|
517
|
+
/**
|
|
518
|
+
* The version of `name` that `pkg` resolves today: the first `node_modules/<name>/package.json`
|
|
519
|
+
* walking up from its directory, which is Node's own lookup - so a hoisted copy at a monorepo's
|
|
520
|
+
* root counts, and a nested one under the package wins over it.
|
|
521
|
+
*/
|
|
522
|
+
installedVersion(pkg, name) {
|
|
523
|
+
let dir = pkg.dirname;
|
|
524
|
+
for (;;) {
|
|
525
|
+
const file = path.join(dir, 'node_modules', name, 'package.json');
|
|
526
|
+
if (fs.existsSync(file)) {
|
|
527
|
+
try {
|
|
528
|
+
const version = JSON.parse(fs.readFileSync(file, 'utf-8'))?.version;
|
|
529
|
+
return typeof version === 'string' ? version : undefined;
|
|
530
|
+
}
|
|
531
|
+
catch {
|
|
532
|
+
return undefined;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
const parent = path.dirname(dir);
|
|
536
|
+
if (parent === dir)
|
|
537
|
+
return undefined;
|
|
538
|
+
dir = parent;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
/** The newest release `range` takes, as npm's install would pick it: no prerelease unless the
|
|
542
|
+
* range asks for one, nothing above the `latest` tag, a deprecated one only when nothing else is. */
|
|
543
|
+
resolvedVersion(d, range) {
|
|
544
|
+
if (!d.releases || !semver.validRange(range))
|
|
545
|
+
return undefined;
|
|
546
|
+
const taken = [...d.releases.values()]
|
|
547
|
+
.filter(r => semver.satisfies(r.version, range))
|
|
548
|
+
.filter(r => !d.latestTag || !semver.valid(d.latestTag) || semver.lte(r.version, d.latestTag))
|
|
549
|
+
.sort((a, b) => semver.rcompare(a.version, b.version));
|
|
550
|
+
return (taken.find(r => !r.deprecated) ?? taken[0])?.version;
|
|
551
|
+
}
|
|
464
552
|
manifestFile(pkg) {
|
|
465
553
|
return path.join(pkg.dirname, 'package.json');
|
|
466
554
|
}
|
package/commands/deps.command.js
CHANGED
|
@@ -182,6 +182,8 @@ function jsonPlan(entries) {
|
|
|
182
182
|
types: e.types,
|
|
183
183
|
status: e.status,
|
|
184
184
|
current: e.current,
|
|
185
|
+
installed: e.installed,
|
|
186
|
+
resolved: e.resolved,
|
|
185
187
|
target: e.target,
|
|
186
188
|
latest: e.latest,
|
|
187
189
|
available: e.available,
|
|
@@ -200,30 +202,40 @@ function jsonPlan(entries) {
|
|
|
200
202
|
* own settings leave the newer version out) or `error`. Moves first, smallest first, then the rest. */
|
|
201
203
|
function printPlan(entries, root) {
|
|
202
204
|
const updated = entries.filter(e => e.status === 'update');
|
|
203
|
-
const shown = entries.filter(e => e.status !== 'up-to-date');
|
|
205
|
+
const shown = entries.filter(e => e.status !== 'up-to-date' || reinstall(e));
|
|
204
206
|
/** Said first when nothing moves, so a run whose only rows are majors left behind does not read
|
|
205
207
|
* as a list of things to do. */
|
|
206
208
|
if (!updated.length) {
|
|
207
|
-
|
|
208
|
-
(shown.
|
|
209
|
+
const why = [
|
|
210
|
+
...(shown.some(e => e.status !== 'up-to-date')
|
|
211
|
+
? ['newer versions their settings leave out, or another rule holds back']
|
|
212
|
+
: []),
|
|
213
|
+
...(shown.some(reinstall) ? ['installed versions that differ from what the range takes'] : []),
|
|
214
|
+
];
|
|
215
|
+
console.log(colors.green('All dependencies are up to date') + (why.length ? colors.gray(` - ${why.join('; ')}:`) : '.'));
|
|
209
216
|
if (!shown.length)
|
|
210
217
|
return;
|
|
211
218
|
}
|
|
212
|
-
|
|
219
|
+
/** The upgrade last before the note, so the row reads from what is there to what it would become. */
|
|
220
|
+
const header = ['Dependency', 'Current', 'Installed', 'Latest', 'Change', 'Upgrade'];
|
|
213
221
|
const cells = (e) => [
|
|
214
222
|
e.name,
|
|
215
223
|
e.current,
|
|
216
|
-
e.
|
|
217
|
-
e.status === 'error' ? '-' : (e.available ?? e.latest ?? ''),
|
|
224
|
+
e.installed ?? '-',
|
|
225
|
+
e.status === 'error' ? '-' : (e.available ?? e.latest ?? e.resolved ?? ''),
|
|
218
226
|
changeOf(e),
|
|
227
|
+
e.status === 'update' ? (e.target ?? '') : '-',
|
|
219
228
|
];
|
|
229
|
+
const last = header.length - 1;
|
|
220
230
|
const widths = header.map((h, i) => Math.max(h.length, ...shown.map(e => cells(e)[i].length)));
|
|
221
231
|
/** Padded to the column, except a last value nothing follows - trailing spaces inside a colour
|
|
222
232
|
* code are past the reach of `trimEnd`. */
|
|
223
233
|
const line = (values, paint = text => text) => ' ' + values.map((v, i) => paint(i === values.length - 1 ? v : v.padEnd(widths[i]), i)).join(' ');
|
|
224
234
|
for (const pkg of [...new Set(shown.map(e => e.package))]) {
|
|
225
235
|
const sizes = pkg.versionScheme.bumpNames;
|
|
226
|
-
const rank = (e) => e.status === 'update'
|
|
236
|
+
const rank = (e) => e.status === 'update'
|
|
237
|
+
? sizes.indexOf(e.bump)
|
|
238
|
+
: sizes.length + ['held', 'skipped', 'error', 'up-to-date'].indexOf(e.status);
|
|
227
239
|
const rows = shown.filter(e => e.package === pkg).sort((x, y) => rank(x) - rank(y));
|
|
228
240
|
const where = path.relative(root, pkg.dirname);
|
|
229
241
|
console.log(colors.cyan(pkg.name) + (where ? colors.gray(` (${where})`) : ''));
|
|
@@ -231,16 +243,17 @@ function printPlan(entries, root) {
|
|
|
231
243
|
for (const e of rows) {
|
|
232
244
|
const paint = e.status === 'update' ? sizeColor(e.bump, sizes) : changeColor(e.status);
|
|
233
245
|
const values = cells(e);
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
246
|
+
const note = e.reason ?? (reinstall(e) ? `"${e.current}" takes ${e.resolved} - install again` : undefined);
|
|
247
|
+
if (note)
|
|
248
|
+
values[last] = values[last].padEnd(widths[last]);
|
|
249
|
+
const text = line(values, (v, column) => (column === 4 || column === 5 ? paint(v) : v));
|
|
250
|
+
if (!note) {
|
|
238
251
|
console.log(text);
|
|
239
252
|
continue;
|
|
240
253
|
}
|
|
241
254
|
const indent = 2 + widths.reduce((sum, w) => sum + w + 2, 0);
|
|
242
|
-
const
|
|
243
|
-
console.log(`${text} ${
|
|
255
|
+
const wrapped = wrapNote(note, process.stdout.isTTY ? process.stdout.columns - indent : 0);
|
|
256
|
+
console.log(`${text} ${wrapped.map(l => colors.gray(l)).join('\n' + ' '.repeat(indent))}`);
|
|
244
257
|
}
|
|
245
258
|
}
|
|
246
259
|
}
|
|
@@ -268,8 +281,18 @@ function wrapNote(note, width) {
|
|
|
268
281
|
}
|
|
269
282
|
/** What the `Change` column says: the size of the move, or why there is none. */
|
|
270
283
|
function changeOf(e) {
|
|
284
|
+
if (e.status === 'up-to-date')
|
|
285
|
+
return reinstall(e) ? 'install' : e.status;
|
|
271
286
|
return e.status === 'update' ? (e.bump ?? 'other') : e.status;
|
|
272
287
|
}
|
|
288
|
+
/**
|
|
289
|
+
* A range already at the newest version, with something else installed - a `node_modules` behind
|
|
290
|
+
* its manifest. Listed although nothing moves: hidden, it read as up to date while the code ran
|
|
291
|
+
* against the older version.
|
|
292
|
+
*/
|
|
293
|
+
function reinstall(e) {
|
|
294
|
+
return e.status === 'up-to-date' && !!e.installed && !!e.resolved && e.installed !== e.resolved;
|
|
295
|
+
}
|
|
273
296
|
/** `held` and `skipped` in yellow, an error in red - a row with no move to colour by size. */
|
|
274
297
|
function changeColor(status) {
|
|
275
298
|
return status === 'error' ? colors.red : colors.yellow;
|
package/constants.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '2.
|
|
1
|
+
export const version = '2.26.0';
|
|
@@ -76,6 +76,12 @@ export declare namespace DependencyUpdater {
|
|
|
76
76
|
types: string[];
|
|
77
77
|
/** The range as the manifest declares it. */
|
|
78
78
|
current: string;
|
|
79
|
+
/** The version installed now, as the package would resolve it - unset when it is not
|
|
80
|
+
* installed, or the ecosystem cannot say. */
|
|
81
|
+
installed?: string;
|
|
82
|
+
/** The newest version the declared range takes - what an install would put there. An
|
|
83
|
+
* `'up-to-date'` entry whose `installed` differs from it is still listed. */
|
|
84
|
+
resolved?: string;
|
|
79
85
|
status: 'update' | 'held' | 'skipped' | 'up-to-date' | 'error';
|
|
80
86
|
/** The range it is rewritten to, on an `'update'`. */
|
|
81
87
|
target?: string;
|