rman 2.24.1 → 2.25.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/builtins/platforms/node/node-manifest.provider.d.ts +18 -0
- package/builtins/platforms/node/node-manifest.provider.js +78 -0
- package/builtins/publish-targets/npm/npm-view.js +8 -1
- package/commands/version.command.d.ts +12 -0
- package/commands/version.command.js +17 -2
- package/constants.js +1 -1
- package/core/interfaces/manifest.d.ts +41 -0
- package/core/interfaces/manifest.js +11 -0
- package/package.json +1 -1
- package/services/version-plan.service.d.ts +20 -0
- package/services/version-plan.service.js +87 -1
- package/services/version.service.d.ts +7 -0
- package/services/version.service.js +30 -1
|
@@ -33,6 +33,15 @@ export declare class NodeManifestProvider implements ManifestProvider {
|
|
|
33
33
|
* Only an explicit version after `workspace:` needs bumping, like a plain range.
|
|
34
34
|
*/
|
|
35
35
|
updateDependencyVersions(manifest: Manifest, bumped: ReadonlyMap<Package, string>): void;
|
|
36
|
+
/**
|
|
37
|
+
* Every field naming `dependency`, with npm's answer to whether its range accepts `version` and
|
|
38
|
+
* the range with its floor raised to it - see `ManifestProvider.referencesTo`.
|
|
39
|
+
*
|
|
40
|
+
* A bare `"workspace:*"`/`"^"`/`"~"` is `dynamic`: it already follows the sibling, so there is
|
|
41
|
+
* nothing to keep or raise. An explicit `"workspace:<range>"` is judged by the range after it.
|
|
42
|
+
*/
|
|
43
|
+
referencesTo(manifest: Manifest, dependency: string, version: string): ManifestProvider.Reference[];
|
|
44
|
+
setReference(manifest: Manifest, field: string, dependency: string, range: string): void;
|
|
36
45
|
/**
|
|
37
46
|
* What the npm registry says this package's current version is - used *only* by
|
|
38
47
|
* `detectChangeHash`, to guess a tag name for a package that has no git tag yet. See
|
|
@@ -55,6 +64,15 @@ export declare class NodeManifestProvider implements ManifestProvider {
|
|
|
55
64
|
stampVersion(file: string, content: string, version: string, options?: {
|
|
56
65
|
constant?: string;
|
|
57
66
|
}): string | undefined;
|
|
67
|
+
/**
|
|
68
|
+
* `range` with its lower bound moved up to `version`, keeping its upper bound and its operator -
|
|
69
|
+
* `undefined` when the floor is already at or above it, `null` for a shape with no single floor
|
|
70
|
+
* to move.
|
|
71
|
+
*
|
|
72
|
+
* The shapes a hand-written range takes: `^a`, `~a`, `>=a`, `>=a <b` (or `<=b`) and `a - b`. An
|
|
73
|
+
* exact pin never needs moving - one that accepts `version` already is it.
|
|
74
|
+
*/
|
|
75
|
+
protected raiseFloor(range: string, version: string): string | null | undefined;
|
|
58
76
|
write(dir: string, manifest: Manifest): void;
|
|
59
77
|
/** The peers this manifest marks `"optional": true`, which `dependencies` leaves out of the
|
|
60
78
|
* graph. `optionalDependencies` is deliberately *not* read the same way: an optional peer says
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import semver from 'semver';
|
|
3
4
|
import { Manifest } from '../../../core/interfaces/manifest.js';
|
|
4
5
|
import { stampVersionConstant } from '../../../utils/version-stamp.js';
|
|
5
6
|
import { npmViewVersion } from '../../publish-targets/npm/npm-view.js';
|
|
@@ -91,6 +92,44 @@ export class NodeManifestProvider {
|
|
|
91
92
|
}
|
|
92
93
|
}
|
|
93
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* Every field naming `dependency`, with npm's answer to whether its range accepts `version` and
|
|
97
|
+
* the range with its floor raised to it - see `ManifestProvider.referencesTo`.
|
|
98
|
+
*
|
|
99
|
+
* A bare `"workspace:*"`/`"^"`/`"~"` is `dynamic`: it already follows the sibling, so there is
|
|
100
|
+
* nothing to keep or raise. An explicit `"workspace:<range>"` is judged by the range after it.
|
|
101
|
+
*/
|
|
102
|
+
referencesTo(manifest, dependency, version) {
|
|
103
|
+
const refs = [];
|
|
104
|
+
for (const field of DEPENDENCY_KEYS) {
|
|
105
|
+
const declared = manifest.raw[field]?.[dependency];
|
|
106
|
+
if (typeof declared !== 'string')
|
|
107
|
+
continue;
|
|
108
|
+
const workspace = parseWorkspaceRange(declared);
|
|
109
|
+
if (workspace && workspace.selector !== 'explicit') {
|
|
110
|
+
refs.push({ field, range: declared, dynamic: true, accepts: true });
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const range = workspace ? workspace.range : declared;
|
|
114
|
+
const raised = this.raiseFloor(range, version);
|
|
115
|
+
refs.push({
|
|
116
|
+
field,
|
|
117
|
+
range: declared,
|
|
118
|
+
accepts: semver.satisfies(version, range),
|
|
119
|
+
...(raised === null
|
|
120
|
+
? { unraisable: true }
|
|
121
|
+
: raised
|
|
122
|
+
? { raised: workspace ? `workspace:${raised}` : raised }
|
|
123
|
+
: {}),
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
return refs;
|
|
127
|
+
}
|
|
128
|
+
setReference(manifest, field, dependency, range) {
|
|
129
|
+
const deps = manifest.raw[field];
|
|
130
|
+
if (deps && typeof deps === 'object')
|
|
131
|
+
deps[dependency] = range;
|
|
132
|
+
}
|
|
94
133
|
/**
|
|
95
134
|
* What the npm registry says this package's current version is - used *only* by
|
|
96
135
|
* `detectChangeHash`, to guess a tag name for a package that has no git tag yet. See
|
|
@@ -115,6 +154,45 @@ export class NodeManifestProvider {
|
|
|
115
154
|
stampVersion(file, content, version, options) {
|
|
116
155
|
return stampVersionConstant(content, version, options?.constant);
|
|
117
156
|
}
|
|
157
|
+
/**
|
|
158
|
+
* `range` with its lower bound moved up to `version`, keeping its upper bound and its operator -
|
|
159
|
+
* `undefined` when the floor is already at or above it, `null` for a shape with no single floor
|
|
160
|
+
* to move.
|
|
161
|
+
*
|
|
162
|
+
* The shapes a hand-written range takes: `^a`, `~a`, `>=a`, `>=a <b` (or `<=b`) and `a - b`. An
|
|
163
|
+
* exact pin never needs moving - one that accepts `version` already is it.
|
|
164
|
+
*/
|
|
165
|
+
/* **`null` rather than a guess for anything else**, `||` above all: which alternative's floor is
|
|
166
|
+
* meant cannot be read off the range, and rewriting the wrong one changes what it accepts.
|
|
167
|
+
* `version` reports it instead, so the range can be raised by hand. */
|
|
168
|
+
raiseFloor(range, version) {
|
|
169
|
+
const r = range.trim();
|
|
170
|
+
let floor;
|
|
171
|
+
try {
|
|
172
|
+
floor = semver.minVersion(r);
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
if (!floor)
|
|
178
|
+
return null;
|
|
179
|
+
if (semver.lte(version, floor))
|
|
180
|
+
return undefined;
|
|
181
|
+
if (semver.valid(r))
|
|
182
|
+
return undefined;
|
|
183
|
+
const caret = /^([\^~])\s*v?\d[^\s|]*$/.exec(r);
|
|
184
|
+
if (caret)
|
|
185
|
+
return `${caret[1]}${version}`;
|
|
186
|
+
if (/^>=\s*v?\d\S*$/.test(r))
|
|
187
|
+
return `>=${version}`;
|
|
188
|
+
const bounded = /^>=\s*v?\d\S*\s+(<=?\s*v?\d\S*)$/.exec(r);
|
|
189
|
+
if (bounded)
|
|
190
|
+
return `>=${version} ${bounded[1]}`;
|
|
191
|
+
const hyphen = /^v?\d\S*\s+-\s+(v?\d\S*)$/.exec(r);
|
|
192
|
+
if (hyphen)
|
|
193
|
+
return `${version} - ${hyphen[1]}`;
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
118
196
|
write(dir, manifest) {
|
|
119
197
|
/**
|
|
120
198
|
* The version is written back into `raw` before serializing, because `raw` is the document and
|
|
@@ -93,7 +93,14 @@ export async function npmViewReleases(name, range, cwd, options = {}) {
|
|
|
93
93
|
const list = Array.isArray(releases) ? releases : releases ? [releases] : [];
|
|
94
94
|
const meta = (options.withTime ? tags : { 'dist-tags': tags });
|
|
95
95
|
return {
|
|
96
|
-
|
|
96
|
+
/** **A bare version string is a release too.** Asked for several fields, npm answers with one
|
|
97
|
+
* object per version - unless no version in the range has any of them set, and then it
|
|
98
|
+
* collapses to a plain list of version strings. Measured on `ts-gems@>=3.12.0`: six strings,
|
|
99
|
+
* so every release was dropped and `rman deps` reported 4.0.4 as nothing to offer - not even
|
|
100
|
+
* a skipped major. */
|
|
101
|
+
releases: list
|
|
102
|
+
.map((r) => (typeof r === 'string' ? { version: r } : r))
|
|
103
|
+
.filter((r) => typeof r?.version === 'string'),
|
|
97
104
|
latest: meta['dist-tags']?.latest,
|
|
98
105
|
time: meta.time,
|
|
99
106
|
};
|
|
@@ -86,6 +86,12 @@ declare const config: {
|
|
|
86
86
|
type: "string";
|
|
87
87
|
choices: readonly ["changed", "dependents", "group"];
|
|
88
88
|
};
|
|
89
|
+
dependentRanges: {
|
|
90
|
+
target: "config";
|
|
91
|
+
describe: string;
|
|
92
|
+
type: "string";
|
|
93
|
+
choices: readonly ["rewrite", "keep"];
|
|
94
|
+
};
|
|
89
95
|
releaseTagPattern: {
|
|
90
96
|
target: "config";
|
|
91
97
|
describe: string;
|
|
@@ -292,6 +298,12 @@ declare const versionCommand: (app: import("../index.js").RmanApplication) => {
|
|
|
292
298
|
type: "string";
|
|
293
299
|
choices: readonly ["changed", "dependents", "group"];
|
|
294
300
|
};
|
|
301
|
+
dependentRanges: {
|
|
302
|
+
target: "config";
|
|
303
|
+
describe: string;
|
|
304
|
+
type: "string";
|
|
305
|
+
choices: readonly ["rewrite", "keep"];
|
|
306
|
+
};
|
|
295
307
|
releaseTagPattern: {
|
|
296
308
|
target: "config";
|
|
297
309
|
describe: string;
|
|
@@ -5,6 +5,7 @@ import { registerCommand } from '../interfaces/rman-config.interface.js';
|
|
|
5
5
|
import { VersionPlanService } from '../services/version-plan.service.js';
|
|
6
6
|
import { assertAllowedBranch, branchGuardOptions, readBranchGuardOptions } from '../utils/branch-guard.js';
|
|
7
7
|
import { fitTable } from '../utils/fit-table.js';
|
|
8
|
+
import { UNCOMMITTED } from '../utils/git.js';
|
|
8
9
|
import { packageFilterOptions, readPackageFilterOptions } from '../utils/package-filter.js';
|
|
9
10
|
import { isNamedGroupKey, isSoloGroupKey, ROOT_GROUP_KEY } from '../utils/version-group.js';
|
|
10
11
|
/**
|
|
@@ -119,6 +120,17 @@ const config = {
|
|
|
119
120
|
type: 'string',
|
|
120
121
|
choices: ['changed', 'dependents', 'group'],
|
|
121
122
|
},
|
|
123
|
+
dependentRanges: {
|
|
124
|
+
target: 'config',
|
|
125
|
+
describe: "What happens to this package's range on an in-repo dependency that is getting a new " +
|
|
126
|
+
'version: "rewrite" (the default) sets it to the new version and releases this package for ' +
|
|
127
|
+
'it; "keep" leaves a range that still accepts the new version alone and does not release ' +
|
|
128
|
+
"this package for it, refuses the run when the range does not, and raises the range's " +
|
|
129
|
+
'lower bound when this package is released for changes of its own. A prerelease never ' +
|
|
130
|
+
"fails a range and never raises one. Per-package cascaded - the range is the package's own.",
|
|
131
|
+
type: 'string',
|
|
132
|
+
choices: ['rewrite', 'keep'],
|
|
133
|
+
},
|
|
122
134
|
releaseTagPattern: {
|
|
123
135
|
target: 'config',
|
|
124
136
|
describe: "Tag naming the repository's own release, as opposed to the per-package tags " +
|
|
@@ -235,8 +247,11 @@ const versionCommand = registerCommand(app => {
|
|
|
235
247
|
printPlan(plan);
|
|
236
248
|
const errors = plan.filter(e => e.status === 'error');
|
|
237
249
|
if (errors.length) {
|
|
238
|
-
const
|
|
239
|
-
|
|
250
|
+
const dirty = errors.filter(e => e.reason?.startsWith(UNCOMMITTED)).length;
|
|
251
|
+
const message = dirty === errors.length
|
|
252
|
+
? `${errors.length} package(s) have uncommitted local changes ` +
|
|
253
|
+
'(pass --ignore-dirty to exclude them instead of aborting)'
|
|
254
|
+
: `${errors.length} package(s) cannot be versioned - see the Reason column`;
|
|
240
255
|
console.log(colors.red(message));
|
|
241
256
|
const err = new Error(message);
|
|
242
257
|
err.logged = true;
|
package/constants.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '2.
|
|
1
|
+
export const version = '2.25.1';
|
|
@@ -159,6 +159,42 @@ export interface ManifestProvider {
|
|
|
159
159
|
* by path.
|
|
160
160
|
*/
|
|
161
161
|
updateDependencyVersions?(manifest: Manifest, bumped: ReadonlyMap<Package, string>): void;
|
|
162
|
+
/**
|
|
163
|
+
* Every reference this manifest makes to the in-repo package `dependency`, one per field naming
|
|
164
|
+
* it, each judged against `version` - whether the range written there accepts it, and the range
|
|
165
|
+
* with its lower bound raised to it.
|
|
166
|
+
*
|
|
167
|
+
* What `.rmanrc "version.dependentRanges": keep` asks instead of `updateDependencyVersions`: a
|
|
168
|
+
* hand-written range is left alone while it accepts the new version, refused when it does not,
|
|
169
|
+
* and has its floor raised when the package holding it is released for reasons of its own.
|
|
170
|
+
*
|
|
171
|
+
* Omit it and a package has no ranges to keep, so `keep` behaves like `rewrite` for it.
|
|
172
|
+
*/
|
|
173
|
+
referencesTo?(manifest: Manifest, dependency: string, version: string): ManifestProvider.Reference[];
|
|
174
|
+
/**
|
|
175
|
+
* Writes `range` as this manifest's reference to `dependency` in `field` - one of the fields
|
|
176
|
+
* `referencesTo` reported. Mutates `manifest.raw`, like `updateDependencyVersions`.
|
|
177
|
+
*/
|
|
178
|
+
setReference?(manifest: Manifest, field: string, dependency: string, range: string): void;
|
|
179
|
+
}
|
|
180
|
+
export declare namespace ManifestProvider {
|
|
181
|
+
/** One field's reference to an in-repo package - see `ManifestProvider.referencesTo`. */
|
|
182
|
+
interface Reference {
|
|
183
|
+
/** Where it is written - npm's `dependencies`, `peerDependencies`, ... */
|
|
184
|
+
field: string;
|
|
185
|
+
/** The range as written. */
|
|
186
|
+
range: string;
|
|
187
|
+
/** It follows the dependency by itself (npm's bare `"workspace:^"`) - always accepts, never
|
|
188
|
+
* rewritten, and keeps `rewrite`'s handling under `keep` too. */
|
|
189
|
+
dynamic?: boolean;
|
|
190
|
+
/** Whether the range accepts the version asked about. */
|
|
191
|
+
accepts: boolean;
|
|
192
|
+
/** The range with its lower bound raised to the version asked about, when that moves it.
|
|
193
|
+
* Unset when the floor is already there, or when `unraisable`. */
|
|
194
|
+
raised?: string;
|
|
195
|
+
/** The range's shape has no single lower bound this provider knows how to move. */
|
|
196
|
+
unraisable?: boolean;
|
|
197
|
+
}
|
|
162
198
|
}
|
|
163
199
|
/**
|
|
164
200
|
* The registry, merged onto the `Manifest` interface so one name carries both the shape and the
|
|
@@ -218,6 +254,11 @@ export declare namespace Manifest {
|
|
|
218
254
|
* reference each other by path has nothing here to go stale.
|
|
219
255
|
*/
|
|
220
256
|
function updateDependencyVersions(pkg: Package, bumped: ReadonlyMap<Package, string>): void;
|
|
257
|
+
/** `pkg`'s references to `dependency`, judged against `version` - see
|
|
258
|
+
* `ManifestProvider.referencesTo`. Empty where the provider has no opinion. */
|
|
259
|
+
function referencesTo(pkg: Package, dependency: string, version: string): ManifestProvider.Reference[];
|
|
260
|
+
/** Writes one of `pkg`'s references - see `ManifestProvider.setReference`. */
|
|
261
|
+
function setReference(pkg: Package, field: string, dependency: string, range: string): void;
|
|
221
262
|
/**
|
|
222
263
|
* Rewrites a hard-coded version in `content` through `pkg`'s own ecosystem - see
|
|
223
264
|
* `Plugin.stampVersion`.
|
|
@@ -88,6 +88,17 @@ export var Manifest;
|
|
|
88
88
|
platformOf(pkg)?.manifestProvider.updateDependencyVersions?.(pkg.manifest, bumped);
|
|
89
89
|
}
|
|
90
90
|
Manifest.updateDependencyVersions = updateDependencyVersions;
|
|
91
|
+
/** `pkg`'s references to `dependency`, judged against `version` - see
|
|
92
|
+
* `ManifestProvider.referencesTo`. Empty where the provider has no opinion. */
|
|
93
|
+
function referencesTo(pkg, dependency, version) {
|
|
94
|
+
return platformOf(pkg)?.manifestProvider.referencesTo?.(pkg.manifest, dependency, version) ?? [];
|
|
95
|
+
}
|
|
96
|
+
Manifest.referencesTo = referencesTo;
|
|
97
|
+
/** Writes one of `pkg`'s references - see `ManifestProvider.setReference`. */
|
|
98
|
+
function setReference(pkg, field, dependency, range) {
|
|
99
|
+
platformOf(pkg)?.manifestProvider.setReference?.(pkg.manifest, field, dependency, range);
|
|
100
|
+
}
|
|
101
|
+
Manifest.setReference = setReference;
|
|
91
102
|
/**
|
|
92
103
|
* Rewrites a hard-coded version in `content` through `pkg`'s own ecosystem - see
|
|
93
104
|
* `Plugin.stampVersion`.
|
package/package.json
CHANGED
|
@@ -142,6 +142,26 @@ export declare abstract class VersionPlanService {
|
|
|
142
142
|
*/
|
|
143
143
|
protected declaredPreid(members: Package[], label: string): string | undefined;
|
|
144
144
|
protected declaredCascade(members: Package[]): VersionPlanService.Cascade | undefined;
|
|
145
|
+
/**
|
|
146
|
+
* Whether `dependent` keeps its range on `dependency` (`.rmanrc "version.dependentRanges": keep`)
|
|
147
|
+
* and that range still accepts `version` - so the new version reaches it without a release of
|
|
148
|
+
* its own. `undefined` means "release it as `rewrite` would": the key says `rewrite`, the range
|
|
149
|
+
* no longer accepts the version (refused later, by `checkKeptRanges`), or there is no range to
|
|
150
|
+
* judge - a reference that follows the dependency by itself, or an edge `.rmanrc
|
|
151
|
+
* "dependencies"` declared with no range at all.
|
|
152
|
+
*
|
|
153
|
+
* A prerelease is accepted whatever the range says: a range written for stable releases is not
|
|
154
|
+
* meant to take a beta, and the dependent keeps resolving the stable one it was built against.
|
|
155
|
+
*/
|
|
156
|
+
protected rangeVerdict(dependent: Package, dependency: Package, version: string): 'accepts' | undefined;
|
|
157
|
+
/**
|
|
158
|
+
* The plan's last word on kept ranges: every package that keeps its ranges is checked against
|
|
159
|
+
* the versions its in-repo dependencies are getting. A range that refuses one makes the package
|
|
160
|
+
* an `'error'` naming the field, the range and the version, so `version` writes nothing and the
|
|
161
|
+
* range can be widened by hand. One left out of the release because its range still accepts says
|
|
162
|
+
* so in its reason, and one being released notes a range whose floor cannot be raised.
|
|
163
|
+
*/
|
|
164
|
+
protected checkKeptRanges(packages: Package[], entries: Map<string, VersionPlanService.Entry>): void;
|
|
145
165
|
/**
|
|
146
166
|
* The largest bump `commits` ask for, in `scheme`'s own names.
|
|
147
167
|
*
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { RmanApplication } from '../core/application.js';
|
|
3
3
|
import { assertOneScheme, semverScheme, VersionScheme } from '../core/classes/version-scheme.js';
|
|
4
|
+
import { Manifest } from '../core/interfaces/manifest.js';
|
|
4
5
|
import { skipReasonFor, targetsOf } from '../core/interfaces/publish-target.js';
|
|
5
6
|
import { dirtyReason, filesUnder, GitHelper } from '../utils/git.js';
|
|
6
7
|
import { filterPackages } from '../utils/package-filter.js';
|
|
@@ -185,6 +186,7 @@ export class VersionPlanService {
|
|
|
185
186
|
this.computeGroupPlan(key, members, changeByPackage, explicitVersion, preid, entries);
|
|
186
187
|
}
|
|
187
188
|
this.rippleCrossGroup(packages, entries, options.preid);
|
|
189
|
+
this.checkKeptRanges(packages, entries);
|
|
188
190
|
const result = packages.map(pkg => entries.get(pkg.name));
|
|
189
191
|
if (repository.monorepo) {
|
|
190
192
|
result.push(this.buildRootEntry(repository, result, {
|
|
@@ -364,6 +366,86 @@ export class VersionPlanService {
|
|
|
364
366
|
return widest;
|
|
365
367
|
return undefined;
|
|
366
368
|
}
|
|
369
|
+
/**
|
|
370
|
+
* Whether `dependent` keeps its range on `dependency` (`.rmanrc "version.dependentRanges": keep`)
|
|
371
|
+
* and that range still accepts `version` - so the new version reaches it without a release of
|
|
372
|
+
* its own. `undefined` means "release it as `rewrite` would": the key says `rewrite`, the range
|
|
373
|
+
* no longer accepts the version (refused later, by `checkKeptRanges`), or there is no range to
|
|
374
|
+
* judge - a reference that follows the dependency by itself, or an edge `.rmanrc
|
|
375
|
+
* "dependencies"` declared with no range at all.
|
|
376
|
+
*
|
|
377
|
+
* A prerelease is accepted whatever the range says: a range written for stable releases is not
|
|
378
|
+
* meant to take a beta, and the dependent keeps resolving the stable one it was built against.
|
|
379
|
+
*/
|
|
380
|
+
/* **Why `keep` exists**, asked for by `panates/syncbridge`: nine extensions declare
|
|
381
|
+
* `"@syncbridge/common": ">=1.1.0 <2"` so that a release of `common` does not have to release
|
|
382
|
+
* all nine. `rewrite` turned the range back into `^1.2.0` and released every one of them anyway.
|
|
383
|
+
* That narrows the cascade npm's own table asks for, and the cost is stated rather than hidden: a
|
|
384
|
+
* dependent that bundles its dependency keeps shipping the older code until it is released
|
|
385
|
+
* itself. Choosing `keep` is the repository accepting that; it is not the default. */
|
|
386
|
+
rangeVerdict(dependent, dependency, version) {
|
|
387
|
+
if (dependent.config?.version?.dependentRanges !== 'keep')
|
|
388
|
+
return undefined;
|
|
389
|
+
const refs = Manifest.referencesTo(dependent, dependency.name, version).filter(r => !r.dynamic);
|
|
390
|
+
if (!refs.length)
|
|
391
|
+
return undefined;
|
|
392
|
+
if (dependency.versionScheme.isPrerelease(version))
|
|
393
|
+
return 'accepts';
|
|
394
|
+
return refs.every(r => r.accepts) ? 'accepts' : undefined;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* The plan's last word on kept ranges: every package that keeps its ranges is checked against
|
|
398
|
+
* the versions its in-repo dependencies are getting. A range that refuses one makes the package
|
|
399
|
+
* an `'error'` naming the field, the range and the version, so `version` writes nothing and the
|
|
400
|
+
* range can be widened by hand. One left out of the release because its range still accepts says
|
|
401
|
+
* so in its reason, and one being released notes a range whose floor cannot be raised.
|
|
402
|
+
*/
|
|
403
|
+
/* **Every kept package, released or not - a `publish.skip` one included.** A range that no longer
|
|
404
|
+
* accepts its sibling breaks more than a publish: npm workspaces link a sibling only while the
|
|
405
|
+
* range accepts its version, and otherwise install it from the registry, silently. */
|
|
406
|
+
checkKeptRanges(packages, entries) {
|
|
407
|
+
for (const pkg of packages) {
|
|
408
|
+
if (pkg.config?.version?.dependentRanges !== 'keep')
|
|
409
|
+
continue;
|
|
410
|
+
const entry = entries.get(pkg.name);
|
|
411
|
+
if (!entry)
|
|
412
|
+
continue;
|
|
413
|
+
const refused = [];
|
|
414
|
+
const kept = [];
|
|
415
|
+
const unraisable = [];
|
|
416
|
+
for (const dep of pkg.dependencies) {
|
|
417
|
+
const depEntry = entries.get(dep.name);
|
|
418
|
+
const version = depEntry?.status === 'bump' ? depEntry.to : dep.version;
|
|
419
|
+
const refs = Manifest.referencesTo(pkg, dep.name, version).filter(r => !r.dynamic);
|
|
420
|
+
if (!refs.length || dep.versionScheme.isPrerelease(version))
|
|
421
|
+
continue;
|
|
422
|
+
for (const ref of refs) {
|
|
423
|
+
if (!ref.accepts) {
|
|
424
|
+
/** Only a version this run gives: a range already refusing the version on disk is not
|
|
425
|
+
* this run's doing, and npm reports it at install time. */
|
|
426
|
+
if (depEntry?.status === 'bump') {
|
|
427
|
+
refused.push(`${ref.field} "${dep.name}": "${ref.range}" does not accept ${version}`);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
else if (entry.status === 'bump' && ref.unraisable) {
|
|
431
|
+
unraisable.push(`${dep.name} "${ref.range}"`);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
if (depEntry?.status === 'bump' && refs.every(r => r.accepts))
|
|
435
|
+
kept.push(`${dep.name} "${refs[0].range}"`);
|
|
436
|
+
}
|
|
437
|
+
if (refused.length) {
|
|
438
|
+
entry.status = 'error';
|
|
439
|
+
entry.reason = `range does not accept the new version - ${refused.join('; ')}`;
|
|
440
|
+
}
|
|
441
|
+
else if (entry.status === 'no-change' && kept.length) {
|
|
442
|
+
entry.reason = `range ${kept.join(', ')} still satisfied`;
|
|
443
|
+
}
|
|
444
|
+
else if (unraisable.length) {
|
|
445
|
+
entry.reason = `${entry.reason ? `${entry.reason}; ` : ''}lower bound not raised: ${unraisable.join(', ')}`;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
367
449
|
/**
|
|
368
450
|
* The largest bump `commits` ask for, in `scheme`'s own names.
|
|
369
451
|
*
|
|
@@ -478,7 +560,7 @@ export class VersionPlanService {
|
|
|
478
560
|
for (const m of members) {
|
|
479
561
|
if (bumping.has(m))
|
|
480
562
|
continue;
|
|
481
|
-
if (m.dependencies.includes(cur)) {
|
|
563
|
+
if (m.dependencies.includes(cur) && !this.rangeVerdict(m, cur, to)) {
|
|
482
564
|
bumping.add(m);
|
|
483
565
|
worklist.push(m);
|
|
484
566
|
}
|
|
@@ -536,6 +618,10 @@ export class VersionPlanService {
|
|
|
536
618
|
continue;
|
|
537
619
|
if (!pkg.dependencies.includes(source.package))
|
|
538
620
|
continue;
|
|
621
|
+
/** Its range still takes the new version, so nothing it publishes is out of date; one that
|
|
622
|
+
* does not is refused by `checkKeptRanges`, after every bump is known. */
|
|
623
|
+
if (this.rangeVerdict(pkg, source.package, source.to))
|
|
624
|
+
continue;
|
|
539
625
|
/** The group always contains `pkg` itself, so there is always at least one version here -
|
|
540
626
|
* the fallback is for the type, not for a case that can happen. */
|
|
541
627
|
const groupCeiling = pkg.versionScheme.highestVersion(packages
|
|
@@ -104,6 +104,13 @@ export declare namespace VersionService {
|
|
|
104
104
|
/** `topo` is refused by default here, which is the one way this differs from `RunService`'s: a
|
|
105
105
|
* version hook has no package ordering to join. */
|
|
106
106
|
function normalizeScriptValue(value: unknown, at: string): RunStepValue[];
|
|
107
|
+
/**
|
|
108
|
+
* Raises the lower bound of each of `pkg`'s ranges on an in-repo dependency to the version that
|
|
109
|
+
* dependency now has - `bumped`'s for one this run moves, its own otherwise. Upper bounds stay.
|
|
110
|
+
* What `.rmanrc "version.dependentRanges": keep` does for a package released for changes of its
|
|
111
|
+
* own, in place of `Manifest.updateDependencyVersions`.
|
|
112
|
+
*/
|
|
113
|
+
function raiseKeptFloors(pkg: Package, bumped: ReadonlyMap<Package, string>): void;
|
|
107
114
|
/**
|
|
108
115
|
* Keeps a package's Dockerfile `org.opencontainers.image.version` label in step with the version
|
|
109
116
|
* just written, returning the absolute path when it actually changed (so the caller can fold it
|
|
@@ -98,7 +98,10 @@ export class VersionService extends Service {
|
|
|
98
98
|
/** Which fields hold a sibling reference, and what a reference even looks like, is the
|
|
99
99
|
* ecosystem's - npm's four fields and its `"workspace:"` protocol used to be spelled out
|
|
100
100
|
* here. See `Plugin.updateDependencyVersions`. */
|
|
101
|
-
|
|
101
|
+
if (pkg.config?.version?.dependentRanges === 'keep')
|
|
102
|
+
VersionService.raiseKeptFloors(pkg, bumpedVersions);
|
|
103
|
+
else
|
|
104
|
+
Manifest.updateDependencyVersions(pkg, bumpedVersions);
|
|
102
105
|
await hook('exec');
|
|
103
106
|
pkg.writeManifest();
|
|
104
107
|
// Before the `after` hook, so a script reacting to the bump sees the whole new state.
|
|
@@ -308,6 +311,32 @@ export class VersionService extends Service {
|
|
|
308
311
|
return RunService.normalizeScriptValue(value, at, { topo: false });
|
|
309
312
|
}
|
|
310
313
|
VersionService.normalizeScriptValue = normalizeScriptValue;
|
|
314
|
+
/**
|
|
315
|
+
* Raises the lower bound of each of `pkg`'s ranges on an in-repo dependency to the version that
|
|
316
|
+
* dependency now has - `bumped`'s for one this run moves, its own otherwise. Upper bounds stay.
|
|
317
|
+
* What `.rmanrc "version.dependentRanges": keep` does for a package released for changes of its
|
|
318
|
+
* own, in place of `Manifest.updateDependencyVersions`.
|
|
319
|
+
*/
|
|
320
|
+
/* **Only when the package itself is released**, because that is the only time it can have
|
|
321
|
+
* started using something new: code relying on a newer dependency is a change in the dependent,
|
|
322
|
+
* and a change is what releases it. Left unreleased, the range it published is still true. The
|
|
323
|
+
* floor may end up higher than strictly needed - that errs toward what was built and tested, and
|
|
324
|
+
* the upper bound keeps every consumer installable.
|
|
325
|
+
*
|
|
326
|
+
* A prerelease never raises one: a stable range floored on a beta would no longer install for
|
|
327
|
+
* anyone on stable releases. */
|
|
328
|
+
function raiseKeptFloors(pkg, bumped) {
|
|
329
|
+
for (const dep of pkg.dependencies) {
|
|
330
|
+
const version = bumped.get(dep) ?? dep.version;
|
|
331
|
+
if (dep.versionScheme.isPrerelease(version))
|
|
332
|
+
continue;
|
|
333
|
+
for (const ref of Manifest.referencesTo(pkg, dep.name, version)) {
|
|
334
|
+
if (ref.raised && ref.accepts)
|
|
335
|
+
Manifest.setReference(pkg, ref.field, dep.name, ref.raised);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
VersionService.raiseKeptFloors = raiseKeptFloors;
|
|
311
340
|
/**
|
|
312
341
|
* Keeps a package's Dockerfile `org.opencontainers.image.version` label in step with the version
|
|
313
342
|
* just written, returning the absolute path when it actually changed (so the caller can fold it
|