rman 2.15.1 → 2.16.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/builtins/platforms/node/node-config.interface.d.ts +11 -0
- package/builtins/platforms/node/services/publish.service.js +16 -1
- package/commands/deps.command.js +7 -3
- package/commands/version.command.d.ts +6 -2
- package/commands/version.command.js +5 -2
- package/constants.js +1 -1
- package/package.json +1 -1
- package/services/version-plan.service.d.ts +6 -0
- package/services/version-plan.service.js +31 -2
- package/utils/status-region.d.ts +14 -0
- package/utils/status-region.js +64 -6
|
@@ -61,6 +61,17 @@ export declare namespace RmanNodeConfig {
|
|
|
61
61
|
* own "Unknown command: stage" is a better message than a guess made somewhere else.
|
|
62
62
|
*/
|
|
63
63
|
staged?: ConfigValue<boolean>;
|
|
64
|
+
/**
|
|
65
|
+
* The prerelease identifiers that are **not previews** for this package - a version on one of
|
|
66
|
+
* them publishes to `latest` like a release, instead of under a dist-tag of its own.
|
|
67
|
+
*
|
|
68
|
+
* For a package whose every release is a prerelease by design: `["rev"]` publishes
|
|
69
|
+
* `4.13.3-rev.9` to `latest`, while a `4.14.0-beta.0` of the same package keeps its own `beta`
|
|
70
|
+
* tag. Pairs with `.rmanrc "version.preid"`.
|
|
71
|
+
*
|
|
72
|
+
* @example ["rev"]
|
|
73
|
+
*/
|
|
74
|
+
latestPrereleases?: ConfigValue<string[]>;
|
|
64
75
|
}
|
|
65
76
|
}
|
|
66
77
|
/**
|
|
@@ -501,7 +501,7 @@ function retiredDirectoryKey(pkg) {
|
|
|
501
501
|
* checks; they agree deliberately.
|
|
502
502
|
*/
|
|
503
503
|
function distTagFor(pkg, tag) {
|
|
504
|
-
const isPreview = !isCalendarVersion(pkg.version) && pkg.versionScheme.isPrerelease(pkg.version);
|
|
504
|
+
const isPreview = !isCalendarVersion(pkg.version) && pkg.versionScheme.isPrerelease(pkg.version) && !releasesAsLatest(pkg);
|
|
505
505
|
if (!isPreview)
|
|
506
506
|
return { tag };
|
|
507
507
|
if (tag) {
|
|
@@ -553,3 +553,18 @@ function resolveStaged(pkg, override) {
|
|
|
553
553
|
return override;
|
|
554
554
|
return !!pkg.config?.publish?.npm?.staged;
|
|
555
555
|
}
|
|
556
|
+
/**
|
|
557
|
+
* Whether `pkg`'s prerelease identifier is one `.rmanrc "publish.npm.latestPrereleases"` lists - a
|
|
558
|
+
* line the package releases on for good, which publishes to `latest` like any release.
|
|
559
|
+
*/
|
|
560
|
+
/* **An explicit list, not "whatever `version.preid` says".** A repository can put its previews on a
|
|
561
|
+
* declared line too (`preid: beta`), and reading the bump's setting as a statement about the
|
|
562
|
+
* registry would have every such beta land on `latest` - the one mistake `distTagFor` exists to
|
|
563
|
+
* stop. Naming the identifier also keeps the protection for every other one: a `-beta.0` of a
|
|
564
|
+
* package publishing `-rev.N` to `latest` still gets its own tag. Requested from `panates/browsery`,
|
|
565
|
+
* which repackages upstream modules as `<upstream version>-rev.N`. */
|
|
566
|
+
function releasesAsLatest(pkg) {
|
|
567
|
+
const listed = pkg.config?.publish?.npm?.latestPrereleases;
|
|
568
|
+
const id = pkg.versionScheme.prereleaseId(pkg.version);
|
|
569
|
+
return !!id && Array.isArray(listed) && listed.includes(id);
|
|
570
|
+
}
|
package/commands/deps.command.js
CHANGED
|
@@ -200,9 +200,13 @@ function jsonPlan(entries) {
|
|
|
200
200
|
function printPlan(entries, root) {
|
|
201
201
|
const updated = entries.filter(e => e.status === 'update');
|
|
202
202
|
const behind = entries.filter(e => e.status === 'held' || e.status === 'skipped' || e.status === 'error' || (e.status === 'update' && e.reason));
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
203
|
+
/** Said first when nothing moves, so a run whose only lines are majors left behind does not read
|
|
204
|
+
* as a list of things to do. */
|
|
205
|
+
if (!updated.length) {
|
|
206
|
+
console.log(colors.green('All dependencies are up to date') +
|
|
207
|
+
(behind.length ? colors.gray(' - newer versions their settings leave out, or another rule holds back:') : '.'));
|
|
208
|
+
if (!behind.length)
|
|
209
|
+
return;
|
|
206
210
|
}
|
|
207
211
|
const shown = [...updated, ...behind];
|
|
208
212
|
const nameWidth = Math.max(...shown.map(e => e.name.length));
|
|
@@ -58,8 +58,10 @@ declare const config: {
|
|
|
58
58
|
describe: string;
|
|
59
59
|
type: "boolean";
|
|
60
60
|
};
|
|
61
|
+
/** `target: 'both'`: a flag for one prerelease, and `.rmanrc "version.preid"` for a repository
|
|
62
|
+
* that releases on a prerelease line for good (`4.13.3-rev.N`). */
|
|
61
63
|
preid: {
|
|
62
|
-
target: "
|
|
64
|
+
target: "both";
|
|
63
65
|
describe: string;
|
|
64
66
|
type: "string";
|
|
65
67
|
};
|
|
@@ -257,8 +259,10 @@ declare const versionCommand: (app: import("../index.js").RmanApplication) => {
|
|
|
257
259
|
describe: string;
|
|
258
260
|
type: "boolean";
|
|
259
261
|
};
|
|
262
|
+
/** `target: 'both'`: a flag for one prerelease, and `.rmanrc "version.preid"` for a repository
|
|
263
|
+
* that releases on a prerelease line for good (`4.13.3-rev.N`). */
|
|
260
264
|
preid: {
|
|
261
|
-
target: "
|
|
265
|
+
target: "both";
|
|
262
266
|
describe: string;
|
|
263
267
|
type: "string";
|
|
264
268
|
};
|
|
@@ -77,11 +77,14 @@ const config = {
|
|
|
77
77
|
'"version.changelog", or false - --no-changelog forces it off even when that\'s true.',
|
|
78
78
|
type: 'boolean',
|
|
79
79
|
},
|
|
80
|
+
/** `target: 'both'`: a flag for one prerelease, and `.rmanrc "version.preid"` for a repository
|
|
81
|
+
* that releases on a prerelease line for good (`4.13.3-rev.N`). */
|
|
80
82
|
preid: {
|
|
81
|
-
target: '
|
|
83
|
+
target: 'both',
|
|
82
84
|
describe: 'Make the bump a prerelease with this identifier (e.g. "beta" -> 1.2.3-beta.0). ' +
|
|
83
85
|
'Running again with the same --preid increments it (-> 1.2.3-beta.1); a different ' +
|
|
84
|
-
'identifier starts a fresh prerelease line. Ignored when bump is an explicit version.'
|
|
86
|
+
'identifier starts a fresh prerelease line. Ignored when bump is an explicit version. ' +
|
|
87
|
+
'Default: .rmanrc "version.preid", which every member of a group has to agree on.',
|
|
85
88
|
type: 'string',
|
|
86
89
|
},
|
|
87
90
|
/**
|
package/constants.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '2.
|
|
1
|
+
export const version = '2.16.0';
|
package/package.json
CHANGED
|
@@ -129,6 +129,12 @@ export declare abstract class VersionPlanService {
|
|
|
129
129
|
* case the technology's own `cascade` answers. Members disagreeing take the widest, the same rule
|
|
130
130
|
* and the same direction `cascadeFor` applies to two technologies.
|
|
131
131
|
*/
|
|
132
|
+
/**
|
|
133
|
+
* The prerelease identifier a group's members declare in `.rmanrc "version.preid"`, or `undefined`
|
|
134
|
+
* when none does. Members declaring different ones are an error naming them: a group is one
|
|
135
|
+
* version line, and a line cannot be on two prerelease identifiers at once.
|
|
136
|
+
*/
|
|
137
|
+
protected declaredPreid(members: Package[], label: string): string | undefined;
|
|
132
138
|
protected declaredCascade(members: Package[]): VersionPlanService.Cascade | undefined;
|
|
133
139
|
/**
|
|
134
140
|
* The largest bump `commits` ask for, in `scheme`'s own names.
|
|
@@ -160,7 +160,8 @@ export class VersionPlanService {
|
|
|
160
160
|
groups.set(key, [pkg]);
|
|
161
161
|
}
|
|
162
162
|
for (const [key, members] of groups) {
|
|
163
|
-
|
|
163
|
+
const preid = options.preid ?? this.declaredPreid(members, this.groupLabel(key));
|
|
164
|
+
this.computeGroupPlan(key, members, changeByPackage, explicitVersion, preid, entries);
|
|
164
165
|
}
|
|
165
166
|
this.rippleCrossGroup(packages, entries, options.preid);
|
|
166
167
|
const result = packages.map(pkg => entries.get(pkg.name));
|
|
@@ -267,6 +268,31 @@ export class VersionPlanService {
|
|
|
267
268
|
* `^1.0.0` does not admit `2.0.0` and an in-group dependent left at the old number would declare
|
|
268
269
|
* a range its own group no longer satisfies. So `changed` means "no floor of my own", not "never
|
|
269
270
|
* more than the changed packages". */
|
|
271
|
+
/**
|
|
272
|
+
* The prerelease identifier a group's members declare in `.rmanrc "version.preid"`, or `undefined`
|
|
273
|
+
* when none does. Members declaring different ones are an error naming them: a group is one
|
|
274
|
+
* version line, and a line cannot be on two prerelease identifiers at once.
|
|
275
|
+
*/
|
|
276
|
+
/* **A member saying nothing agrees with whoever does.** The usual shape is one declaration above
|
|
277
|
+
* the group (`"[*]"`, or a shared preset), which reaches every member; a group where only some
|
|
278
|
+
* members say it is still one line, and refusing it would make adding a package to a group a way
|
|
279
|
+
* to break its release. Two different words are the only disagreement, and that one has no
|
|
280
|
+
* answer: `4.13.3-rev.9` and `4.13.3-beta.0` cannot both be the group's next version. */
|
|
281
|
+
declaredPreid(members, label) {
|
|
282
|
+
const declared = new Map();
|
|
283
|
+
for (const m of members) {
|
|
284
|
+
const preid = m.config?.version?.preid;
|
|
285
|
+
if (typeof preid !== 'string' || !preid)
|
|
286
|
+
continue;
|
|
287
|
+
declared.set(preid, [...(declared.get(preid) ?? []), m.name]);
|
|
288
|
+
}
|
|
289
|
+
if (declared.size > 1) {
|
|
290
|
+
const sides = [...declared].map(([preid, names]) => `"${preid}" (${names.join(', ')})`).join(' and ');
|
|
291
|
+
throw new Error(`Group ${label} is one version line, but its members declare different "version.preid": ${sides}. ` +
|
|
292
|
+
'Declare one identifier for the whole group.');
|
|
293
|
+
}
|
|
294
|
+
return declared.keys().next().value;
|
|
295
|
+
}
|
|
270
296
|
declaredCascade(members) {
|
|
271
297
|
const declared = new Set(members.map(m => m.config?.version?.cascade).filter((c) => !!c));
|
|
272
298
|
if (!declared.size)
|
|
@@ -454,7 +480,10 @@ export class VersionPlanService {
|
|
|
454
480
|
const next = {
|
|
455
481
|
...entry,
|
|
456
482
|
status: 'bump',
|
|
457
|
-
to: pkg.versionScheme.next(groupCeiling, pkg.versionScheme.smallestBump(), {
|
|
483
|
+
to: pkg.versionScheme.next(groupCeiling, pkg.versionScheme.smallestBump(), {
|
|
484
|
+
preid: preid ??
|
|
485
|
+
this.declaredPreid(packages.filter(p => entries.get(p.name).groupKey === entry.groupKey), entry.group ?? entry.groupKey),
|
|
486
|
+
}),
|
|
458
487
|
reason: `depends on ${source.package.name}@${source.to}`,
|
|
459
488
|
};
|
|
460
489
|
entries.set(pkg.name, next);
|
package/utils/status-region.d.ts
CHANGED
|
@@ -33,6 +33,12 @@ export declare class StatusRegion implements TerminalRegion {
|
|
|
33
33
|
* there, so `runBin` keeps asking one object (`app.statusRegion`) whatever is actually drawing.
|
|
34
34
|
*/
|
|
35
35
|
private takeover?;
|
|
36
|
+
/** Puts back the stream writes `guardWrites` replaced. */
|
|
37
|
+
private restoreWrites?;
|
|
38
|
+
/** Set while this region is writing its own frame, so the guard lets it through untouched. */
|
|
39
|
+
private drawing;
|
|
40
|
+
/** The last write from outside ended mid-line, so no frame is drawn until the line is finished. */
|
|
41
|
+
private partial;
|
|
36
42
|
constructor(label: string, detail?: string, enabled?: boolean);
|
|
37
43
|
/** Whether the spinner is actually drawn. `false` leaves `passThrough` a plain write and `stop`
|
|
38
44
|
* its single line, which is what a non-TTY wants. */
|
|
@@ -69,6 +75,14 @@ export declare class StatusRegion implements TerminalRegion {
|
|
|
69
75
|
passThrough(text: string): void;
|
|
70
76
|
/** Erases the spinner and leaves one line saying how it went. */
|
|
71
77
|
stop(outcome: 'ok' | 'fail'): void;
|
|
78
|
+
/**
|
|
79
|
+
* Moves every write to stdout and stderr above the spinner: erase it, write, draw it again below.
|
|
80
|
+
* Undone by `stop`.
|
|
81
|
+
*/
|
|
82
|
+
private guardWrites;
|
|
83
|
+
/** Runs `write` as one of this region's own writes, so the guard passes it through. */
|
|
84
|
+
private own;
|
|
85
|
+
private clear;
|
|
72
86
|
private draw;
|
|
73
87
|
/** `0.4s` while running and `950ms` / `4.1s` / `2m 03s` at the end - one formatter, because a
|
|
74
88
|
* duration that changes shape as it ticks reads as a glitch. */
|
package/utils/status-region.js
CHANGED
|
@@ -12,6 +12,12 @@ export class StatusRegion {
|
|
|
12
12
|
* there, so `runBin` keeps asking one object (`app.statusRegion`) whatever is actually drawing.
|
|
13
13
|
*/
|
|
14
14
|
takeover;
|
|
15
|
+
/** Puts back the stream writes `guardWrites` replaced. */
|
|
16
|
+
restoreWrites;
|
|
17
|
+
/** Set while this region is writing its own frame, so the guard lets it through untouched. */
|
|
18
|
+
drawing = false;
|
|
19
|
+
/** The last write from outside ended mid-line, so no frame is drawn until the line is finished. */
|
|
20
|
+
partial = false;
|
|
15
21
|
constructor(label, detail = '', enabled) {
|
|
16
22
|
this.label = label;
|
|
17
23
|
this.detail = detail;
|
|
@@ -26,6 +32,7 @@ export class StatusRegion {
|
|
|
26
32
|
start() {
|
|
27
33
|
if (!this.region.enabled)
|
|
28
34
|
return;
|
|
35
|
+
this.guardWrites();
|
|
29
36
|
this.draw();
|
|
30
37
|
/** **`unref`, so a spinner never holds the process open.** A command that finishes its work and
|
|
31
38
|
* leaves nothing else pending would otherwise wait out the interval before exiting. */
|
|
@@ -55,7 +62,7 @@ export class StatusRegion {
|
|
|
55
62
|
if (this.timer)
|
|
56
63
|
clearInterval(this.timer);
|
|
57
64
|
this.timer = undefined;
|
|
58
|
-
this.
|
|
65
|
+
this.clear();
|
|
59
66
|
}
|
|
60
67
|
/**
|
|
61
68
|
* Takes the terminal back - a no-op when it was never suspended.
|
|
@@ -88,8 +95,8 @@ export class StatusRegion {
|
|
|
88
95
|
process.stderr.write(text);
|
|
89
96
|
return;
|
|
90
97
|
}
|
|
91
|
-
this.
|
|
92
|
-
process.stderr.write(text);
|
|
98
|
+
this.clear();
|
|
99
|
+
this.own(() => process.stderr.write(text));
|
|
93
100
|
this.draw();
|
|
94
101
|
}
|
|
95
102
|
/** Erases the spinner and leaves one line saying how it went. */
|
|
@@ -98,11 +105,62 @@ export class StatusRegion {
|
|
|
98
105
|
if (this.timer)
|
|
99
106
|
clearInterval(this.timer);
|
|
100
107
|
this.timer = undefined;
|
|
101
|
-
this.
|
|
108
|
+
this.clear();
|
|
109
|
+
this.restoreWrites?.();
|
|
110
|
+
this.restoreWrites = undefined;
|
|
102
111
|
const mark = outcome === 'ok' ? colors.green('✔') : colors.red('✖');
|
|
103
|
-
|
|
112
|
+
const lead = this.partial ? '\n' : '';
|
|
113
|
+
process.stderr.write(`${lead}${mark} ${colors.bold(this.label)} ${colors.gray(this.elapsed())}\n`);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Moves every write to stdout and stderr above the spinner: erase it, write, draw it again below.
|
|
117
|
+
* Undone by `stop`.
|
|
118
|
+
*/
|
|
119
|
+
/* **Here, once, rather than in every command.** Measured with a pseudo-terminal: `rman deps`
|
|
120
|
+
* printed its plan with `console.log`, the spinner's next erase moved up one row from *below* the
|
|
121
|
+
* plan, and the plan's last line - the one dependency it was reporting - was gone from the screen,
|
|
122
|
+
* leaving a "not updated" heading with nothing under it. `version --show` lost its closing
|
|
123
|
+
* "Nothing to version." the same way. Asking each command to route its output through
|
|
124
|
+
* `passThrough` is a rule every new command would have to remember; a command's own `console.log`
|
|
125
|
+
* is the ordinary thing to write.
|
|
126
|
+
*
|
|
127
|
+
* **Left alone while suspended**: the region that took over draws to stdout itself, and it owns the
|
|
128
|
+
* terminal until `resume`. **A write ending mid-line holds the next frame back** (`partial`), or the
|
|
129
|
+
* frame's `\r` + erase would wipe the half-written line. */
|
|
130
|
+
guardWrites() {
|
|
131
|
+
const streams = [process.stdout, process.stderr];
|
|
132
|
+
const originals = streams.map(stream => stream.write);
|
|
133
|
+
streams.forEach((stream, i) => {
|
|
134
|
+
const original = originals[i];
|
|
135
|
+
stream.write = ((chunk, ...rest) => {
|
|
136
|
+
if (this.drawing || this.takeover)
|
|
137
|
+
return original.call(stream, chunk, ...rest);
|
|
138
|
+
this.clear();
|
|
139
|
+
const written = this.own(() => original.call(stream, chunk, ...rest));
|
|
140
|
+
this.partial = !String(chunk).endsWith('\n');
|
|
141
|
+
this.draw();
|
|
142
|
+
return written;
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
this.restoreWrites = () => streams.forEach((stream, i) => (stream.write = originals[i]));
|
|
146
|
+
}
|
|
147
|
+
/** Runs `write` as one of this region's own writes, so the guard passes it through. */
|
|
148
|
+
own(write) {
|
|
149
|
+
const was = this.drawing;
|
|
150
|
+
this.drawing = true;
|
|
151
|
+
try {
|
|
152
|
+
return write();
|
|
153
|
+
}
|
|
154
|
+
finally {
|
|
155
|
+
this.drawing = was;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
clear() {
|
|
159
|
+
this.own(() => this.region.clear());
|
|
104
160
|
}
|
|
105
161
|
draw() {
|
|
162
|
+
if (this.partial)
|
|
163
|
+
return;
|
|
106
164
|
const spinner = colors.cyan(FRAMES[this.frame % FRAMES.length]);
|
|
107
165
|
const parts = [
|
|
108
166
|
spinner,
|
|
@@ -110,7 +168,7 @@ export class StatusRegion {
|
|
|
110
168
|
this.detail && colors.gray(this.detail),
|
|
111
169
|
colors.gray(this.elapsed()),
|
|
112
170
|
];
|
|
113
|
-
this.region.render([parts.filter(Boolean).join(' ')]);
|
|
171
|
+
this.own(() => this.region.render([parts.filter(Boolean).join(' ')]));
|
|
114
172
|
}
|
|
115
173
|
/** `0.4s` while running and `950ms` / `4.1s` / `2m 03s` at the end - one formatter, because a
|
|
116
174
|
* duration that changes shape as it ticks reads as a glitch. */
|