rman 2.3.1 → 2.5.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/services/version-plan.service.d.ts +1 -3
- package/builtins/platforms/node/services/version-plan.service.js +26 -4
- package/commands/version.command.d.ts +12 -0
- package/commands/version.command.js +12 -0
- package/constants.js +1 -1
- package/index.d.ts +5 -0
- package/index.js +26 -0
- package/package.json +1 -1
- package/services/version-plan.service.d.ts +15 -2
- package/services/version-plan.service.js +71 -2
|
@@ -27,9 +27,7 @@ export declare class NodeVersionPlanService extends VersionPlanService {
|
|
|
27
27
|
* npm's dependency ranges, read as a release policy. What decides each case is whether a
|
|
28
28
|
* dependent's **range floor** has to move for a consumer to get a correct install:
|
|
29
29
|
*
|
|
30
|
-
* - **patch** -
|
|
31
|
-
* and a patch adds nothing for the dependent to require, so nothing downstream has to ship for
|
|
32
|
-
* consumers to receive it.
|
|
30
|
+
* - **patch** - the changed packages **and** every transitive in-group dependent.
|
|
33
31
|
* - **minor** - also every transitive in-group dependent. A minor adds API; a dependent that uses
|
|
34
32
|
* it is only correct once its own published range requires the new floor, and a range lives in
|
|
35
33
|
* a manifest, which only a release puts on the registry.
|
|
@@ -28,9 +28,7 @@ export class NodeVersionPlanService extends VersionPlanService {
|
|
|
28
28
|
* npm's dependency ranges, read as a release policy. What decides each case is whether a
|
|
29
29
|
* dependent's **range floor** has to move for a consumer to get a correct install:
|
|
30
30
|
*
|
|
31
|
-
* - **patch** -
|
|
32
|
-
* and a patch adds nothing for the dependent to require, so nothing downstream has to ship for
|
|
33
|
-
* consumers to receive it.
|
|
31
|
+
* - **patch** - the changed packages **and** every transitive in-group dependent.
|
|
34
32
|
* - **minor** - also every transitive in-group dependent. A minor adds API; a dependent that uses
|
|
35
33
|
* it is only correct once its own published range requires the new floor, and a range lives in
|
|
36
34
|
* a manifest, which only a release puts on the registry.
|
|
@@ -41,6 +39,30 @@ export class NodeVersionPlanService extends VersionPlanService {
|
|
|
41
39
|
* versions instead has to release every dependent for a patch as well, and one that resolves
|
|
42
40
|
* dependencies from source may not need a release for any of it.
|
|
43
41
|
*/
|
|
42
|
+
/* **`patch` answered `'changed'` until 2.4, on reasoning that is correct and too narrow.** The
|
|
43
|
+
* range argument holds: `^1.2.0` resolves to `1.2.1`, so a consumer receives the fix with no
|
|
44
|
+
* dependent republished, and the floor a dependent declares stays true. What it leaves out is
|
|
45
|
+
* that a dependent's *published artifact* was built against the old code - anything that bundles,
|
|
46
|
+
* vendors, or type-checks against its dependency ships the pre-fix version until it is released
|
|
47
|
+
* again, and under this default nothing ever released it.
|
|
48
|
+
*
|
|
49
|
+
* So the two answers differ in which failure they prefer, and the cheaper one is now the default:
|
|
50
|
+
* releasing a dependent that strictly did not need it is visible churn, while not releasing one
|
|
51
|
+
* that did is invisible - the same asymmetry `cascade`'s own doc states and `cascadeFor` already
|
|
52
|
+
* resolves by taking the widest.
|
|
53
|
+
*
|
|
54
|
+
* **`patch` and `minor` are now the same answer, and that is a real loss of resolution**, not a
|
|
55
|
+
* tidy-up: the table no longer distinguishes them, so a future reason to treat a patch
|
|
56
|
+
* differently has nowhere to live. It is kept as two entries rather than collapsed for exactly
|
|
57
|
+
* that reason.
|
|
58
|
+
*
|
|
59
|
+
* **It also makes `'changed'` unreachable under npm.** Nothing here returns it any more, and a
|
|
60
|
+
* repository writing `version.cascade: changed` gets no narrowing, because that key is a floor
|
|
61
|
+
* and never a ceiling. The value stays meaningful - it says "no floor of my own", which is what
|
|
62
|
+
* an ecosystem answering `'changed'` would still honour - but in a Node repository it is a no-op.
|
|
63
|
+
*
|
|
64
|
+
* A repository wanting the whole group on a patch writes `version.cascade: group`; one wanting
|
|
65
|
+
* the old narrow behaviour cannot ask for it, and has to say so if that turns out to matter. */
|
|
44
66
|
cascade(bump) {
|
|
45
67
|
/** Only semver's three can arrive - `getPlan` validates against `bumpNames` and
|
|
46
68
|
* `packageJsonManifest` leaves the scheme at the semver default. The fallback is for a scheme
|
|
@@ -52,7 +74,7 @@ export class NodeVersionPlanService extends VersionPlanService {
|
|
|
52
74
|
}
|
|
53
75
|
/** semver's bump names against how far each has to reach - see `NodeVersionPlanService.cascade`. */
|
|
54
76
|
const CASCADE_BY_BUMP = {
|
|
55
|
-
patch: '
|
|
77
|
+
patch: 'dependents',
|
|
56
78
|
minor: 'dependents',
|
|
57
79
|
major: 'group',
|
|
58
80
|
};
|
|
@@ -73,6 +73,12 @@ declare const config: {
|
|
|
73
73
|
describe: string;
|
|
74
74
|
type: "string";
|
|
75
75
|
};
|
|
76
|
+
cascade: {
|
|
77
|
+
target: "config";
|
|
78
|
+
describe: string;
|
|
79
|
+
type: "string";
|
|
80
|
+
choices: readonly ["changed", "dependents", "group"];
|
|
81
|
+
};
|
|
76
82
|
releaseTagPattern: {
|
|
77
83
|
target: "config";
|
|
78
84
|
describe: string;
|
|
@@ -266,6 +272,12 @@ declare const versionCommand: (app: import("../index.js").RmanApplication) => {
|
|
|
266
272
|
describe: string;
|
|
267
273
|
type: "string";
|
|
268
274
|
};
|
|
275
|
+
cascade: {
|
|
276
|
+
target: "config";
|
|
277
|
+
describe: string;
|
|
278
|
+
type: "string";
|
|
279
|
+
choices: readonly ["changed", "dependents", "group"];
|
|
280
|
+
};
|
|
269
281
|
releaseTagPattern: {
|
|
270
282
|
target: "config";
|
|
271
283
|
describe: string;
|
|
@@ -94,6 +94,18 @@ const config = {
|
|
|
94
94
|
'commit\'s own group shares one version. Default "chore(release): v{version}".',
|
|
95
95
|
type: 'string',
|
|
96
96
|
},
|
|
97
|
+
cascade: {
|
|
98
|
+
target: 'config',
|
|
99
|
+
describe: 'The narrowest this repository is willing to release a group: "changed" (only packages with ' +
|
|
100
|
+
'commits of their own, i.e. no floor of your own), "dependents" (those plus in-group ' +
|
|
101
|
+
'packages depending on them), or "group" (every member, so the group stays in lockstep). ' +
|
|
102
|
+
'A floor, not a ceiling: the technology still widens it where a narrower release would ' +
|
|
103
|
+
"leave a dependent's published artifact or range floor behind - under npm that is " +
|
|
104
|
+
'"dependents" for a patch or a minor and the whole group for a major, so "changed" is a ' +
|
|
105
|
+
'no-op there. Per-package cascaded; a group whose members disagree takes the widest.',
|
|
106
|
+
type: 'string',
|
|
107
|
+
choices: ['changed', 'dependents', 'group'],
|
|
108
|
+
},
|
|
97
109
|
releaseTagPattern: {
|
|
98
110
|
target: 'config',
|
|
99
111
|
describe: "Tag naming the repository's own release, as opposed to the per-package tags " +
|
package/constants.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '2.
|
|
1
|
+
export const version = '2.5.0';
|
package/index.d.ts
CHANGED
|
@@ -32,6 +32,11 @@
|
|
|
32
32
|
import './builtins/platforms/node/augmentation/rmanrc.augmentation.js';
|
|
33
33
|
import type { RmanConfig as CommandDeclaration } from './interfaces/rman-config.interface.js';
|
|
34
34
|
export { defineConfig } from './interfaces/rman-config.interface.js';
|
|
35
|
+
/**
|
|
36
|
+
* Merges `source` onto `target` the way rman layers an `extends` base, a directory level or a
|
|
37
|
+
* `"[selector]"` block - returning `target`, mutated.
|
|
38
|
+
*/
|
|
39
|
+
export { mergeConfig } from './core/config/merge-config.js';
|
|
35
40
|
/**
|
|
36
41
|
* **A config value written as a function**, and the scope it is handed. Exported because a config
|
|
37
42
|
* author could not name either: `interpolateConfig` calls a function wherever a `${{ }}` could
|
package/index.js
CHANGED
|
@@ -31,6 +31,32 @@
|
|
|
31
31
|
*/
|
|
32
32
|
import './builtins/platforms/node/augmentation/rmanrc.augmentation.js';
|
|
33
33
|
export { defineConfig } from './interfaces/rman-config.interface.js';
|
|
34
|
+
/**
|
|
35
|
+
* Merges `source` onto `target` the way rman layers an `extends` base, a directory level or a
|
|
36
|
+
* `"[selector]"` block - returning `target`, mutated.
|
|
37
|
+
*/
|
|
38
|
+
/* **Exported for a config assembled in JavaScript**, which `@panates/rman-preset` is: it composes
|
|
39
|
+
* one config from a platform-neutral part and a Node one, and a plain object spread gets that
|
|
40
|
+
* wrong in two ways a reader would not expect.
|
|
41
|
+
*
|
|
42
|
+
* A spread is shallow, so two objects both declaring `changelog` keep only the later one's -
|
|
43
|
+
* silently, and wholesale. A deep merge from a general-purpose library fixes that half and breaks
|
|
44
|
+
* the other: measured with `@jsopen/objects`' `merge({deep: true})`, `commands: ['a','b']` merged
|
|
45
|
+
* with `['c']` gives `['c']`, while rman appends those - `commands`, `plugins`, `platforms` and
|
|
46
|
+
* `publishTargets` are `ALWAYS_APPEND` keys precisely because naming one of your own never means
|
|
47
|
+
* "and drop the ones my base brought". A generic merge would drop a preset's commands the day
|
|
48
|
+
* anything else declared one.
|
|
49
|
+
*
|
|
50
|
+
* `origin` is the file `source` was read from, recorded per key so a failing expression can name
|
|
51
|
+
* it. A caller merging an object it built rather than read passes nothing, and the error then
|
|
52
|
+
* names whatever file the surrounding config came from.
|
|
53
|
+
*
|
|
54
|
+
* **`extends` remains the better answer where it fits**, and it is worth trying first: a config
|
|
55
|
+
* module can carry `extends: ['./other.js']` and let rman do the merging, which costs no API
|
|
56
|
+
* surface at all and keeps the origins exact. Measured to work for a JS module extending a JS
|
|
57
|
+
* module, nested inside a config that is itself an `extends` target. This export is for the case
|
|
58
|
+
* where one module has to hand back a single finished object. */
|
|
59
|
+
export { mergeConfig } from './core/config/merge-config.js';
|
|
34
60
|
export { defineCommand } from './core/interfaces/custom-command.js';
|
|
35
61
|
/** Both the shape and the registry: `const m: Manifest` and `Manifest.read(dir)` - merged onto one
|
|
36
62
|
* name so a plugin can augment it the way it augments `SystemInfo`. */
|
package/package.json
CHANGED
|
@@ -123,6 +123,12 @@ export declare abstract class VersionPlanService {
|
|
|
123
123
|
* a dependent that pins exact versions is a broken install.
|
|
124
124
|
*/
|
|
125
125
|
protected cascadeFor(members: Package[], bump: string): VersionPlanService.Cascade;
|
|
126
|
+
/**
|
|
127
|
+
* `.rmanrc "version.cascade"` for a group, or `undefined` where no member declares one - in which
|
|
128
|
+
* case the technology's own `cascade` answers. Members disagreeing take the widest, the same rule
|
|
129
|
+
* and the same direction `cascadeFor` applies to two technologies.
|
|
130
|
+
*/
|
|
131
|
+
protected declaredCascade(members: Package[]): VersionPlanService.Cascade | undefined;
|
|
126
132
|
/**
|
|
127
133
|
* The largest bump `commits` ask for, in `scheme`'s own names.
|
|
128
134
|
*
|
|
@@ -157,8 +163,15 @@ export declare abstract class VersionPlanService {
|
|
|
157
163
|
* bump (or `undefined` for one with no real commits since its last boundary) - `undefined`
|
|
158
164
|
* here always means "unchanged", never "explicit version" (that path is handled separately).
|
|
159
165
|
*
|
|
160
|
-
* How wide the bump goes is `cascade`'s answer, and only its answer
|
|
161
|
-
*
|
|
166
|
+
* How wide the bump goes is `cascade`'s answer, and only its answer.
|
|
167
|
+
*
|
|
168
|
+
* **An explicit `rman version <v>` reaches every member of the group, and the old wording here
|
|
169
|
+
* said the opposite.** It read "so it reaches the changed members alone", which is true of the
|
|
170
|
+
* code and false as a sentence: `getPlan` marks *every* eligible package changed with reason
|
|
171
|
+
* `explicit version <v>`, and a group's members are drawn from `eligible`, so "the changed
|
|
172
|
+
* members" is all of them. Measured while adding `version.cascade` - a spec asserting the
|
|
173
|
+
* narrow reading failed. A repository in lockstep therefore keeps it when the number is chosen
|
|
174
|
+
* by hand, with no cascade involved.
|
|
162
175
|
*/
|
|
163
176
|
protected computeGroupPlan(key: string, members: Package[], changeByPackage: Map<string, VersionPlanService.Change>, explicitVersion: string | undefined, preid: string | undefined, entries: Map<string, VersionPlanService.Entry>): void;
|
|
164
177
|
/**
|
|
@@ -218,12 +218,63 @@ export class VersionPlanService {
|
|
|
218
218
|
* a dependent that pins exact versions is a broken install.
|
|
219
219
|
*/
|
|
220
220
|
cascadeFor(members, bump) {
|
|
221
|
+
/** **A declared cascade is a floor, never a ceiling** - see `declaredCascade`. It joins the
|
|
222
|
+
* technologies' answers and the widest of the set wins, which is the rule already applied to
|
|
223
|
+
* two technologies disagreeing. */
|
|
221
224
|
const answers = new Set(members.map(m => this.plannerFor(m).cascade(bump)));
|
|
225
|
+
const declared = this.declaredCascade(members);
|
|
226
|
+
if (declared)
|
|
227
|
+
answers.add(declared);
|
|
222
228
|
for (const widest of CASCADE_WIDEST_FIRST)
|
|
223
229
|
if (answers.has(widest))
|
|
224
230
|
return widest;
|
|
225
231
|
return 'changed';
|
|
226
232
|
}
|
|
233
|
+
/**
|
|
234
|
+
* `.rmanrc "version.cascade"` for a group, or `undefined` where no member declares one - in which
|
|
235
|
+
* case the technology's own `cascade` answers. Members disagreeing take the widest, the same rule
|
|
236
|
+
* and the same direction `cascadeFor` applies to two technologies.
|
|
237
|
+
*/
|
|
238
|
+
/* **Why a repository gets to say this at all.** `cascade` is abstract because two of its inputs
|
|
239
|
+
* are an ecosystem's: whether a caret range already carries a patch to a dependent, and whether
|
|
240
|
+
* a floor left behind is a broken install. Those npm answers are right. What they cannot answer
|
|
241
|
+
* is whether a repository wants **one number across its whole product** - which is a release
|
|
242
|
+
* identity decision, not a fact about a package manager, and there was nowhere to state it.
|
|
243
|
+
*
|
|
244
|
+
* Measured on `panates/sqb`: 17 packages, `group: true`, every one published at 6.0.10 since it
|
|
245
|
+
* moved off rman 1.x's `version.unified: true` - and a `fix:` in two of them planned
|
|
246
|
+
* `6.0.10 -> 6.0.11` for those two and `no-change` for the other fifteen. Correct on ranges (all
|
|
247
|
+
* 39 internal edges are caret peers, so the fix reaches dependents with no republish) and still
|
|
248
|
+
* the wrong answer for that repository: the divergence is permanent, since a group's next
|
|
249
|
+
* baseline is `highestVersion(members)`, and every README and issue there is written in terms of
|
|
250
|
+
* one number.
|
|
251
|
+
*
|
|
252
|
+
* **The default is unchanged**, so this costs nothing to a repository that never writes the key.
|
|
253
|
+
*
|
|
254
|
+
* **It is not a safety valve, and must not be documented as one.** A change that can break a
|
|
255
|
+
* dependent is a `feat:` or a `feat!:`; republishing the dependent under a new number does not
|
|
256
|
+
* make a behavioural break safe, it only renumbers it. The lever for that is the bump size, which
|
|
257
|
+
* comes from the commit message.
|
|
258
|
+
*
|
|
259
|
+
* **A floor, never a ceiling, and that asymmetry is the whole safety of the key.** `cascadeFor`
|
|
260
|
+
* adds this answer to the technologies' and takes the widest, so a repository can ask for a
|
|
261
|
+
* *wider* release than npm requires and cannot ask for a narrower one. The direction matters
|
|
262
|
+
* because the two mistakes are not symmetric, which this class's own doc already records: too
|
|
263
|
+
* wide republishes a package that did not need it - visible and harmless - while too narrow
|
|
264
|
+
* leaves a dependent's published range floor wrong, which is a broken install. Concretely, a
|
|
265
|
+
* repository writing `cascade: changed` still gets the whole group on a **major**, because
|
|
266
|
+
* `^1.0.0` does not admit `2.0.0` and an in-group dependent left at the old number would declare
|
|
267
|
+
* a range its own group no longer satisfies. So `changed` means "no floor of my own", not "never
|
|
268
|
+
* more than the changed packages". */
|
|
269
|
+
declaredCascade(members) {
|
|
270
|
+
const declared = new Set(members.map(m => m.config?.version?.cascade).filter((c) => !!c));
|
|
271
|
+
if (!declared.size)
|
|
272
|
+
return undefined;
|
|
273
|
+
for (const widest of CASCADE_WIDEST_FIRST)
|
|
274
|
+
if (declared.has(widest))
|
|
275
|
+
return widest;
|
|
276
|
+
return undefined;
|
|
277
|
+
}
|
|
227
278
|
/**
|
|
228
279
|
* The largest bump `commits` ask for, in `scheme`'s own names.
|
|
229
280
|
*
|
|
@@ -284,8 +335,15 @@ export class VersionPlanService {
|
|
|
284
335
|
* bump (or `undefined` for one with no real commits since its last boundary) - `undefined`
|
|
285
336
|
* here always means "unchanged", never "explicit version" (that path is handled separately).
|
|
286
337
|
*
|
|
287
|
-
* How wide the bump goes is `cascade`'s answer, and only its answer
|
|
288
|
-
*
|
|
338
|
+
* How wide the bump goes is `cascade`'s answer, and only its answer.
|
|
339
|
+
*
|
|
340
|
+
* **An explicit `rman version <v>` reaches every member of the group, and the old wording here
|
|
341
|
+
* said the opposite.** It read "so it reaches the changed members alone", which is true of the
|
|
342
|
+
* code and false as a sentence: `getPlan` marks *every* eligible package changed with reason
|
|
343
|
+
* `explicit version <v>`, and a group's members are drawn from `eligible`, so "the changed
|
|
344
|
+
* members" is all of them. Measured while adding `version.cascade` - a spec asserting the
|
|
345
|
+
* narrow reading failed. A repository in lockstep therefore keeps it when the number is chosen
|
|
346
|
+
* by hand, with no cascade involved.
|
|
289
347
|
*/
|
|
290
348
|
computeGroupPlan(key, members, changeByPackage, explicitVersion, preid, entries) {
|
|
291
349
|
const label = this.groupLabel(key);
|
|
@@ -313,6 +371,12 @@ export class VersionPlanService {
|
|
|
313
371
|
to = scheme.next(current, bump, { preid });
|
|
314
372
|
}
|
|
315
373
|
const bumping = new Set(changed);
|
|
374
|
+
/** **`'changed'` with no bump is not a narrowing, because there is nothing left to narrow.** An
|
|
375
|
+
* explicit `rman version <v>` puts *every* eligible package into `changeByPackage` (see
|
|
376
|
+
* `getPlan`), and a group's members are drawn from `eligible` alone - so `changed` already
|
|
377
|
+
* holds all of them and no cascade could add one. Measured while wiring `version.cascade`: a
|
|
378
|
+
* declared cascade consulted here is dead code, and the spec written for it asserted a reason
|
|
379
|
+
* string that never renders. */
|
|
316
380
|
const cascade = bump ? this.cascadeFor(members, bump) : 'changed';
|
|
317
381
|
if (cascade === 'group') {
|
|
318
382
|
for (const m of members)
|
|
@@ -334,6 +398,11 @@ export class VersionPlanService {
|
|
|
334
398
|
}
|
|
335
399
|
/** Why a member with no commits of its own is being bumped. `'group'` reaches members that
|
|
336
400
|
* depend on nothing at all, so calling those a "dependent" was simply untrue. */
|
|
401
|
+
/* `bump` is always set where this string is *read*: a member reaches it only through
|
|
402
|
+
* `own?.reason ?? inherited`, and the one path with no bump - an explicit version - gives every
|
|
403
|
+
* member an `own.reason` of its own (`explicit version <v>`). So there is no
|
|
404
|
+
* `a undefined change` to guard against here. Checked rather than assumed, because the
|
|
405
|
+
* template reads as though there were. */
|
|
337
406
|
const inherited = cascade === 'group' ? `in-group member of a ${bump} change` : `in-group dependent of a ${bump} change`;
|
|
338
407
|
for (const m of members) {
|
|
339
408
|
if (bumping.has(m)) {
|