rman 2.13.0 → 2.15.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/README.md +52 -19
- package/builtins/platforms/node/node-dependency-updater.d.ts +116 -0
- package/builtins/platforms/node/node-dependency-updater.js +452 -0
- package/builtins/platforms/node/node.platform.d.ts +2 -0
- package/builtins/platforms/node/node.platform.js +2 -0
- package/builtins/platforms/node/services/version-plan.service.d.ts +1 -1
- package/builtins/platforms/node/services/version-plan.service.js +1 -1
- package/builtins/publish-targets/npm/npm-view.d.ts +31 -0
- package/builtins/publish-targets/npm/npm-view.js +47 -0
- package/commands/changelog.command.js +4 -3
- package/commands/deps.command.d.ts +175 -0
- package/commands/deps.command.js +252 -0
- package/commands/info.command.js +11 -5
- package/commands/version.command.js +2 -0
- package/commands.d.ts +2 -0
- package/commands.js +1 -0
- package/constants.js +1 -1
- package/core/interfaces/dependency-updater.d.ts +114 -0
- package/core/interfaces/dependency-updater.js +61 -0
- package/core/interfaces/manifest.js +1 -1
- package/core/interfaces/plugin.d.ts +5 -1
- package/core/interfaces/plugin.js +1 -1
- package/index.d.ts +2 -0
- package/index.js +2 -0
- package/package.json +1 -1
- package/services/change-hash.service.d.ts +2 -2
- package/services/change-hash.service.js +2 -2
- package/services/system-info.d.ts +3 -0
- package/services/system-info.js +1 -0
- package/services/version-plan.service.d.ts +2 -1
- package/services/version-plan.service.js +7 -5
- package/services/version.service.js +4 -4
- package/utils/package-filter.d.ts +1 -1
- package/utils/package-filter.js +1 -1
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import type { RmanApplication } from '../core/application.js';
|
|
2
|
+
import { type RmanConfig } from '../interfaces/rman-config.interface.js';
|
|
3
|
+
/** Hoisted for `ArgsOf` - see `version.command.ts` and `RmanConfig.ArgsOf` for why. */
|
|
4
|
+
declare const COMMAND: "deps [names..]";
|
|
5
|
+
declare const config: {
|
|
6
|
+
scope: {
|
|
7
|
+
target: "cli";
|
|
8
|
+
describe: string;
|
|
9
|
+
type: "string";
|
|
10
|
+
};
|
|
11
|
+
ignore: {
|
|
12
|
+
target: "cli";
|
|
13
|
+
describe: string;
|
|
14
|
+
type: "string";
|
|
15
|
+
};
|
|
16
|
+
platform: {
|
|
17
|
+
target: "cli";
|
|
18
|
+
describe: string;
|
|
19
|
+
type: "string";
|
|
20
|
+
};
|
|
21
|
+
upgrade: {
|
|
22
|
+
target: "cli";
|
|
23
|
+
alias: string;
|
|
24
|
+
describe: string;
|
|
25
|
+
type: "boolean";
|
|
26
|
+
};
|
|
27
|
+
/** Its `choices` and its default are the version schemes' own sizes, so they are filled in where
|
|
28
|
+
* the command is built - see `targetOption`. */
|
|
29
|
+
target: {
|
|
30
|
+
target: "both";
|
|
31
|
+
describe: string;
|
|
32
|
+
type: "string";
|
|
33
|
+
};
|
|
34
|
+
reject: {
|
|
35
|
+
target: "both";
|
|
36
|
+
describe: string;
|
|
37
|
+
type: "string";
|
|
38
|
+
array: true;
|
|
39
|
+
};
|
|
40
|
+
minAge: {
|
|
41
|
+
target: "both";
|
|
42
|
+
cliName: string;
|
|
43
|
+
describe: string;
|
|
44
|
+
type: "number";
|
|
45
|
+
};
|
|
46
|
+
types: {
|
|
47
|
+
target: "config";
|
|
48
|
+
describe: string;
|
|
49
|
+
type: "string";
|
|
50
|
+
array: true;
|
|
51
|
+
};
|
|
52
|
+
concurrency: {
|
|
53
|
+
target: "cli";
|
|
54
|
+
describe: string;
|
|
55
|
+
type: "number";
|
|
56
|
+
};
|
|
57
|
+
verify: {
|
|
58
|
+
target: "cli";
|
|
59
|
+
describe: string;
|
|
60
|
+
type: "boolean";
|
|
61
|
+
};
|
|
62
|
+
json: {
|
|
63
|
+
target: "cli";
|
|
64
|
+
alias: string;
|
|
65
|
+
describe: string;
|
|
66
|
+
type: "boolean";
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
type Args = RmanConfig.ArgsOf<typeof config, typeof COMMAND>;
|
|
70
|
+
/**
|
|
71
|
+
* `rman deps` - which dependencies have a newer version, and with `--upgrade`, moving them there.
|
|
72
|
+
*
|
|
73
|
+
* A version is offered only when every rule that can be seen allows it: the package's own
|
|
74
|
+
* `deps.target`/`reject`/`minAge`, the peer ranges of the other dependencies it names, the runtime
|
|
75
|
+
* the package supports, and what its sibling packages ask of a shared dependency. Each version held
|
|
76
|
+
* back is printed with the rule that held it.
|
|
77
|
+
*/
|
|
78
|
+
declare const depsCommand: (app: RmanApplication) => {
|
|
79
|
+
command: "deps [names..]";
|
|
80
|
+
describe: string;
|
|
81
|
+
config: {
|
|
82
|
+
target: {
|
|
83
|
+
target: "both";
|
|
84
|
+
describe: string;
|
|
85
|
+
type: "string";
|
|
86
|
+
} & {
|
|
87
|
+
choices: string[];
|
|
88
|
+
};
|
|
89
|
+
scope: {
|
|
90
|
+
target: "cli";
|
|
91
|
+
describe: string;
|
|
92
|
+
type: "string";
|
|
93
|
+
};
|
|
94
|
+
ignore: {
|
|
95
|
+
target: "cli";
|
|
96
|
+
describe: string;
|
|
97
|
+
type: "string";
|
|
98
|
+
};
|
|
99
|
+
platform: {
|
|
100
|
+
target: "cli";
|
|
101
|
+
describe: string;
|
|
102
|
+
type: "string";
|
|
103
|
+
};
|
|
104
|
+
upgrade: {
|
|
105
|
+
target: "cli";
|
|
106
|
+
alias: string;
|
|
107
|
+
describe: string;
|
|
108
|
+
type: "boolean";
|
|
109
|
+
};
|
|
110
|
+
reject: {
|
|
111
|
+
target: "both";
|
|
112
|
+
describe: string;
|
|
113
|
+
type: "string";
|
|
114
|
+
array: true;
|
|
115
|
+
};
|
|
116
|
+
minAge: {
|
|
117
|
+
target: "both";
|
|
118
|
+
cliName: string;
|
|
119
|
+
describe: string;
|
|
120
|
+
type: "number";
|
|
121
|
+
};
|
|
122
|
+
types: {
|
|
123
|
+
target: "config";
|
|
124
|
+
describe: string;
|
|
125
|
+
type: "string";
|
|
126
|
+
array: true;
|
|
127
|
+
};
|
|
128
|
+
concurrency: {
|
|
129
|
+
target: "cli";
|
|
130
|
+
describe: string;
|
|
131
|
+
type: "number";
|
|
132
|
+
};
|
|
133
|
+
verify: {
|
|
134
|
+
target: "cli";
|
|
135
|
+
describe: string;
|
|
136
|
+
type: "boolean";
|
|
137
|
+
};
|
|
138
|
+
json: {
|
|
139
|
+
target: "cli";
|
|
140
|
+
alias: string;
|
|
141
|
+
describe: string;
|
|
142
|
+
type: "boolean";
|
|
143
|
+
};
|
|
144
|
+
};
|
|
145
|
+
positionals: {
|
|
146
|
+
names: {
|
|
147
|
+
describe: string;
|
|
148
|
+
type: "string";
|
|
149
|
+
};
|
|
150
|
+
};
|
|
151
|
+
examples: {
|
|
152
|
+
command: string;
|
|
153
|
+
description: string;
|
|
154
|
+
}[];
|
|
155
|
+
handler: (args: Args) => Promise<void>;
|
|
156
|
+
};
|
|
157
|
+
export default depsCommand;
|
|
158
|
+
/** The rest of `deps.*` - what an option cannot describe. */
|
|
159
|
+
export interface DepsExtraKeys {
|
|
160
|
+
/**
|
|
161
|
+
* The largest move for the dependencies a glob matches, ahead of `target` - a package that
|
|
162
|
+
* releases a major every few weeks, or one that has stayed on `0.x` for years, where a `0.x`
|
|
163
|
+
* minor counts as a major. The last glob matching a name wins.
|
|
164
|
+
*
|
|
165
|
+
* @example { "@types/node": "major", "esbuild": "major" }
|
|
166
|
+
*/
|
|
167
|
+
targets?: Record<string, string>;
|
|
168
|
+
}
|
|
169
|
+
/** `deps`'s own keys on `RmanConfig`: derived from the `config` block, plus `DepsExtraKeys`. */
|
|
170
|
+
declare module '../interfaces/rman-config.interface.js' {
|
|
171
|
+
namespace RmanConfig {
|
|
172
|
+
interface CommandConfigs extends RmanConfig.CommandContribution<ReturnType<typeof depsCommand>, DepsExtraKeys> {
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import colors from 'ansi-colors';
|
|
3
|
+
import { semverScheme } from '../core/classes/version-scheme.js';
|
|
4
|
+
import { DependencyUpdater } from '../core/interfaces/dependency-updater.js';
|
|
5
|
+
import { registerCommand } from '../interfaces/rman-config.interface.js';
|
|
6
|
+
import { filterPackages, packageFilterOptions, readPackageFilterOptions } from '../utils/package-filter.js';
|
|
7
|
+
/** Hoisted for `ArgsOf` - see `version.command.ts` and `RmanConfig.ArgsOf` for why. */
|
|
8
|
+
const COMMAND = 'deps [names..]';
|
|
9
|
+
const { scope, ignore, platform } = packageFilterOptions;
|
|
10
|
+
const config = {
|
|
11
|
+
/* `--deps`/`--dependents` are left out: on a command called `deps`, `rman deps --deps` would read
|
|
12
|
+
* as something other than "and the packages these depend on". */
|
|
13
|
+
scope,
|
|
14
|
+
ignore,
|
|
15
|
+
platform,
|
|
16
|
+
upgrade: {
|
|
17
|
+
target: 'cli',
|
|
18
|
+
alias: 'u',
|
|
19
|
+
describe: 'Write the new ranges to the manifests, then check that they still install',
|
|
20
|
+
type: 'boolean',
|
|
21
|
+
},
|
|
22
|
+
/** Its `choices` and its default are the version schemes' own sizes, so they are filled in where
|
|
23
|
+
* the command is built - see `targetOption`. */
|
|
24
|
+
target: {
|
|
25
|
+
target: 'both',
|
|
26
|
+
describe: 'The largest move a dependency may make',
|
|
27
|
+
type: 'string',
|
|
28
|
+
},
|
|
29
|
+
reject: {
|
|
30
|
+
target: 'both',
|
|
31
|
+
describe: 'Leave the dependencies matching these globs alone (repeatable)',
|
|
32
|
+
type: 'string',
|
|
33
|
+
array: true,
|
|
34
|
+
},
|
|
35
|
+
minAge: {
|
|
36
|
+
target: 'both',
|
|
37
|
+
cliName: 'min-age',
|
|
38
|
+
describe: 'Only move to a version published at least this many days ago',
|
|
39
|
+
type: 'number',
|
|
40
|
+
},
|
|
41
|
+
types: {
|
|
42
|
+
target: 'config',
|
|
43
|
+
describe: "The dependency kinds to look at, in the ecosystem's own words - default: every kind",
|
|
44
|
+
type: 'string',
|
|
45
|
+
array: true,
|
|
46
|
+
},
|
|
47
|
+
concurrency: {
|
|
48
|
+
target: 'cli',
|
|
49
|
+
describe: `How many registry lookups run at once (default: ${defaultConcurrency()})`,
|
|
50
|
+
type: 'number',
|
|
51
|
+
},
|
|
52
|
+
verify: {
|
|
53
|
+
target: 'cli',
|
|
54
|
+
describe: "After --upgrade, ask the ecosystem's own resolver whether the result installs (default: true)",
|
|
55
|
+
type: 'boolean',
|
|
56
|
+
},
|
|
57
|
+
json: {
|
|
58
|
+
target: 'cli',
|
|
59
|
+
alias: 'j',
|
|
60
|
+
describe: 'Print the plan as JSON - one entry per package and dependency',
|
|
61
|
+
type: 'boolean',
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* `rman deps` - which dependencies have a newer version, and with `--upgrade`, moving them there.
|
|
66
|
+
*
|
|
67
|
+
* A version is offered only when every rule that can be seen allows it: the package's own
|
|
68
|
+
* `deps.target`/`reject`/`minAge`, the peer ranges of the other dependencies it names, the runtime
|
|
69
|
+
* the package supports, and what its sibling packages ask of a shared dependency. Each version held
|
|
70
|
+
* back is printed with the rule that held it.
|
|
71
|
+
*/
|
|
72
|
+
/* **The command is the core's and the answer is the technology's** - see `DependencyUpdater`. A
|
|
73
|
+
* package whose platform has no updater is left alone, which in a polyglot repository is the right
|
|
74
|
+
* answer for the half rman cannot speak for. */
|
|
75
|
+
const depsCommand = registerCommand(app => {
|
|
76
|
+
const repository = app.repository;
|
|
77
|
+
return {
|
|
78
|
+
command: COMMAND,
|
|
79
|
+
describe: 'Lists the dependencies that have a newer version - pass -u to upgrade them',
|
|
80
|
+
config: { ...config, target: targetOption(app) },
|
|
81
|
+
positionals: {
|
|
82
|
+
names: { describe: 'Only these dependencies (globs)', type: 'string' },
|
|
83
|
+
},
|
|
84
|
+
examples: [
|
|
85
|
+
{ command: '$0 deps', description: '# What could move, and what holds it back' },
|
|
86
|
+
{ command: '$0 deps -u', description: '# Write the new ranges, then check that they install' },
|
|
87
|
+
{ command: '$0 deps --target major', description: '# Across a major too' },
|
|
88
|
+
{ command: '$0 deps "@types/*"', description: '# Only the dependencies matching this glob' },
|
|
89
|
+
],
|
|
90
|
+
handler: async (args) => {
|
|
91
|
+
const options = {
|
|
92
|
+
names: args.names?.length ? args.names.map(String) : undefined,
|
|
93
|
+
target: args.target,
|
|
94
|
+
reject: args.reject?.map(String),
|
|
95
|
+
minAge: args.minAge,
|
|
96
|
+
concurrency: Math.max(1, args.concurrency ?? defaultConcurrency()),
|
|
97
|
+
};
|
|
98
|
+
const ctx = { app, repository, options };
|
|
99
|
+
/** The root holds a monorepo's shared tooling, so it is asked too. */
|
|
100
|
+
const candidates = repository.monorepo ? [repository.rootPackage, ...repository.packages] : repository.packages;
|
|
101
|
+
const selected = filterPackages([...candidates], readPackageFilterOptions(args));
|
|
102
|
+
if (!selected.length)
|
|
103
|
+
throw logged('No package matched.');
|
|
104
|
+
const byUpdater = new Map();
|
|
105
|
+
for (const pkg of selected) {
|
|
106
|
+
const updater = pkg.platform.dependencyUpdater;
|
|
107
|
+
if (!updater)
|
|
108
|
+
continue;
|
|
109
|
+
byUpdater.set(updater, [...(byUpdater.get(updater) ?? []), pkg]);
|
|
110
|
+
}
|
|
111
|
+
if (!byUpdater.size) {
|
|
112
|
+
throw logged('None of the selected packages belongs to a technology that can update its dependencies.');
|
|
113
|
+
}
|
|
114
|
+
const plans = new Map();
|
|
115
|
+
for (const [updater, packages] of byUpdater)
|
|
116
|
+
plans.set(updater, await updater.getPlan(ctx, packages));
|
|
117
|
+
const all = [...plans.values()].flat();
|
|
118
|
+
if (args.json)
|
|
119
|
+
console.log(JSON.stringify(jsonPlan(all), undefined, 2));
|
|
120
|
+
else
|
|
121
|
+
printPlan(all, repository.dirname);
|
|
122
|
+
const errors = all.filter(e => e.status === 'error');
|
|
123
|
+
const updates = all.filter(e => e.status === 'update');
|
|
124
|
+
if (!args.upgrade) {
|
|
125
|
+
if (updates.length && !args.json)
|
|
126
|
+
console.log(colors.gray('\nRun "rman deps -u" to upgrade.'));
|
|
127
|
+
if (errors.length)
|
|
128
|
+
throw logged(`${errors.length} dependency lookup(s) failed - see above.`);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const applied = [];
|
|
132
|
+
for (const [updater, plan] of plans) {
|
|
133
|
+
if (plan.some(e => e.status === 'update'))
|
|
134
|
+
applied.push(await updater.applyPlan(ctx, plan));
|
|
135
|
+
}
|
|
136
|
+
const files = applied.flatMap(a => a.files);
|
|
137
|
+
if (files.length && args.verify !== false) {
|
|
138
|
+
for (const [updater, packages] of byUpdater) {
|
|
139
|
+
const refused = await updater.verify?.(ctx, packages);
|
|
140
|
+
if (!refused)
|
|
141
|
+
continue;
|
|
142
|
+
for (const a of applied)
|
|
143
|
+
a.restore();
|
|
144
|
+
throw logged(`The upgraded manifests do not install, so they were put back:\n${refused}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (!args.json) {
|
|
148
|
+
for (const file of files)
|
|
149
|
+
console.log(colors.green('updated'), path.relative(repository.dirname, file));
|
|
150
|
+
}
|
|
151
|
+
if (errors.length)
|
|
152
|
+
throw logged(`${errors.length} dependency lookup(s) failed - see above.`);
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
});
|
|
156
|
+
export default depsCommand;
|
|
157
|
+
/** Registry lookups are child processes waiting on the network, not CPU work, so the default is a
|
|
158
|
+
* fixed number rather than the core count. */
|
|
159
|
+
function defaultConcurrency() {
|
|
160
|
+
return 16;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* `--target`: one of the version schemes' sizes, and by default every size but the largest.
|
|
164
|
+
*
|
|
165
|
+
* Read off the technologies this repository loaded rather than written down, because the sizes are
|
|
166
|
+
* a scheme's - semver's three are the only ones today.
|
|
167
|
+
*/
|
|
168
|
+
function targetOption(app) {
|
|
169
|
+
const schemes = [...app.platforms].map(p => p.manifestProvider.versionScheme ?? semverScheme);
|
|
170
|
+
const sizes = [...new Set((schemes.length ? schemes : [semverScheme]).flatMap(s => s.bumpNames))];
|
|
171
|
+
const defaults = [...new Set(schemes.map(s => DependencyUpdater.defaultTarget(s.bumpNames)))];
|
|
172
|
+
return {
|
|
173
|
+
...config.target,
|
|
174
|
+
describe: `${config.target.describe} (default: ${defaults.join(' / ') || DependencyUpdater.defaultTarget(semverScheme.bumpNames)})`,
|
|
175
|
+
choices: sizes,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
function jsonPlan(entries) {
|
|
179
|
+
return entries.map(e => ({
|
|
180
|
+
package: e.package.name,
|
|
181
|
+
name: e.name,
|
|
182
|
+
types: e.types,
|
|
183
|
+
status: e.status,
|
|
184
|
+
current: e.current,
|
|
185
|
+
target: e.target,
|
|
186
|
+
latest: e.latest,
|
|
187
|
+
available: e.available,
|
|
188
|
+
bump: e.bump,
|
|
189
|
+
reason: e.reason,
|
|
190
|
+
}));
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* One block per package with something to say: what moves, grouped by size, smallest first - and
|
|
194
|
+
* then **what does not**, each with the reason it stays.
|
|
195
|
+
*/
|
|
196
|
+
/* **The second half is the one `npm-check-updates` taught people to read.** Under `--target minor`
|
|
197
|
+
* a major that was left behind has to be on the screen, or a run reporting three patches looks like
|
|
198
|
+
* a repository that is current. A dependency that moved but not as far as it could appears in both:
|
|
199
|
+
* once under its size, once under "not updated" with what held it. */
|
|
200
|
+
function printPlan(entries, root) {
|
|
201
|
+
const updated = entries.filter(e => e.status === 'update');
|
|
202
|
+
const behind = entries.filter(e => e.status === 'held' || e.status === 'skipped' || e.status === 'error' || (e.status === 'update' && e.reason));
|
|
203
|
+
if (!updated.length && !behind.length) {
|
|
204
|
+
console.log(colors.gray('Every dependency is up to date.'));
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
const shown = [...updated, ...behind];
|
|
208
|
+
const nameWidth = Math.max(...shown.map(e => e.name.length));
|
|
209
|
+
const currentWidth = Math.max(...shown.map(e => e.current.length));
|
|
210
|
+
const row = (e, rest) => ` ${e.name.padEnd(nameWidth)} ${e.current.padEnd(currentWidth)} ${rest}`;
|
|
211
|
+
for (const pkg of [...new Set(shown.map(e => e.package))]) {
|
|
212
|
+
const where = path.relative(root, pkg.dirname);
|
|
213
|
+
console.log(colors.cyan(pkg.name) + (where ? colors.gray(` (${where})`) : ''));
|
|
214
|
+
const sizes = pkg.versionScheme.bumpNames;
|
|
215
|
+
for (const size of [...sizes, undefined]) {
|
|
216
|
+
const group = updated.filter(e => e.package === pkg && (size ? e.bump === size : !sizes.includes(e.bump)));
|
|
217
|
+
if (!group.length)
|
|
218
|
+
continue;
|
|
219
|
+
const paint = sizeColor(size, sizes);
|
|
220
|
+
console.log(` ${paint(size ?? 'other')}`);
|
|
221
|
+
for (const e of group)
|
|
222
|
+
console.log(row(e, `→ ${paint(e.target ?? '')}`));
|
|
223
|
+
}
|
|
224
|
+
const left = behind.filter(e => e.package === pkg);
|
|
225
|
+
if (!left.length)
|
|
226
|
+
continue;
|
|
227
|
+
console.log(` ${colors.yellow('not updated')}`);
|
|
228
|
+
const newest = (e) => (e.status === 'error' ? 'error' : (e.available ?? e.latest ?? ''));
|
|
229
|
+
const width = Math.max(...left.map(e => newest(e).length));
|
|
230
|
+
for (const e of left) {
|
|
231
|
+
const paint = e.status === 'error' ? colors.red : (text) => text;
|
|
232
|
+
console.log(row(e, `${paint(newest(e).padEnd(width))} ${colors.gray(e.reason ?? '')}`));
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
/** The colours `npm-check-updates` uses - the largest size red, the next cyan, the rest green - read
|
|
237
|
+
* off the scheme's own order so a scheme with other names still gets them. */
|
|
238
|
+
function sizeColor(size, sizes) {
|
|
239
|
+
const rank = size ? sizes.indexOf(size) : -1;
|
|
240
|
+
if (rank === sizes.length - 1)
|
|
241
|
+
return colors.red;
|
|
242
|
+
if (rank === sizes.length - 2)
|
|
243
|
+
return colors.cyan;
|
|
244
|
+
return colors.green;
|
|
245
|
+
}
|
|
246
|
+
/** The `logged` convention: printed here, so `runCli`'s catch does not print it a second time. */
|
|
247
|
+
function logged(message) {
|
|
248
|
+
console.error(colors.red(message));
|
|
249
|
+
const err = new Error(message);
|
|
250
|
+
err.logged = true;
|
|
251
|
+
return err;
|
|
252
|
+
}
|
package/commands/info.command.js
CHANGED
|
@@ -60,13 +60,19 @@ function printSystemInfo(systemInfo) {
|
|
|
60
60
|
}
|
|
61
61
|
function printRepositoryInfo(info) {
|
|
62
62
|
console.log('', colors.whiteBright('Repository') + ':');
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
const rows = [
|
|
64
|
+
['Type', colors.yellowBright(info.type === 'monorepo' ? 'Monorepo' : 'Single package')],
|
|
65
|
+
['Name', colors.yellowBright(info.name || '(none)')],
|
|
66
|
+
['Version', colors.yellowBright(info.version || '(none)')],
|
|
67
|
+
['Root', colors.yellowBright(info.root)],
|
|
68
|
+
['Platforms', colors.yellowBright(info.platforms.join(', ') || '(none)')],
|
|
69
|
+
];
|
|
67
70
|
if (info.type === 'monorepo') {
|
|
68
|
-
|
|
71
|
+
rows.push(['Packages', colors.yellowBright(String(info.packageCount)), colors.gray('(run "list" to see them)')]);
|
|
69
72
|
}
|
|
73
|
+
const width = Math.max(...rows.map(([label]) => label.length));
|
|
74
|
+
for (const [label, ...values] of rows)
|
|
75
|
+
console.log(' ' + colors.reset(label.padEnd(width)) + ' :', ...values);
|
|
70
76
|
}
|
|
71
77
|
/**
|
|
72
78
|
* `rman info` - environment and repository, and back in the core because most of what it reports
|
|
@@ -210,6 +210,8 @@ const versionCommand = registerCommand(app => {
|
|
|
210
210
|
name: e.package.name,
|
|
211
211
|
selector: e.package.selector,
|
|
212
212
|
isRoot: e.package.isRoot,
|
|
213
|
+
/** `rman list --json`'s spelling, so a consumer joins the two on it. */
|
|
214
|
+
groupKey: e.groupKey,
|
|
213
215
|
group: e.group,
|
|
214
216
|
status: e.status,
|
|
215
217
|
from: e.from,
|
package/commands.d.ts
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import './commands/build.command.js';
|
|
16
16
|
import './commands/changelog.command.js';
|
|
17
17
|
import './commands/config.command.js';
|
|
18
|
+
import './commands/deps.command.js';
|
|
18
19
|
import './commands/diff.command.js';
|
|
19
20
|
import './commands/exec.command.js';
|
|
20
21
|
import './commands/github-release.command.js';
|
|
@@ -27,5 +28,6 @@ import './commands/test.command.js';
|
|
|
27
28
|
import './commands/version.command.js';
|
|
28
29
|
/** The config shapes a command declares by hand, where an option cannot describe them - see
|
|
29
30
|
* `RmanConfig.CommandContribution`'s `Extra`. Exported so a plugin can name one. */
|
|
31
|
+
export type { DepsExtraKeys } from './commands/deps.command.js';
|
|
30
32
|
export type { PublishExtraKeys, PublishTargetConfigs } from './commands/publish.command.js';
|
|
31
33
|
export type { VersionExtraKeys, VersionStampEntry } from './commands/version.command.js';
|
package/commands.js
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import './commands/build.command.js';
|
|
16
16
|
import './commands/changelog.command.js';
|
|
17
17
|
import './commands/config.command.js';
|
|
18
|
+
import './commands/deps.command.js';
|
|
18
19
|
import './commands/diff.command.js';
|
|
19
20
|
import './commands/exec.command.js';
|
|
20
21
|
import './commands/github-release.command.js';
|
package/constants.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '2.
|
|
1
|
+
export const version = '2.15.0';
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import type { RmanApplication } from '../application.js';
|
|
2
|
+
import type { Package } from '../classes/package.js';
|
|
3
|
+
import type { Repository } from '../classes/repository.js';
|
|
4
|
+
/**
|
|
5
|
+
* **How one technology answers "are this package's dependencies current, and what may they move
|
|
6
|
+
* to"** - the half of `rman deps` that is an ecosystem's.
|
|
7
|
+
*
|
|
8
|
+
* The command owns the repository's half: which packages are asked, printing the plan, `--json`,
|
|
9
|
+
* writing only under `--upgrade`, and undoing the writes when `verify` refuses them.
|
|
10
|
+
*/
|
|
11
|
+
export interface DependencyUpdater {
|
|
12
|
+
/** What each dependency of `packages` would move to - never writes. */
|
|
13
|
+
getPlan(ctx: DependencyUpdater.Context, packages: readonly Package[]): Promise<DependencyUpdater.Entry[]>;
|
|
14
|
+
/** Writes every `'update'` entry of `plan` to its manifest, and returns how to take that back. */
|
|
15
|
+
applyPlan(ctx: DependencyUpdater.Context, plan: readonly DependencyUpdater.Entry[]): Promise<DependencyUpdater.Applied>;
|
|
16
|
+
/**
|
|
17
|
+
* Asks the ecosystem's own resolver whether the manifests as they now stand can be installed,
|
|
18
|
+
* once `applyPlan` has written them. Returns why not, or `undefined` when they can.
|
|
19
|
+
*
|
|
20
|
+
* Omit it when the ecosystem has no resolver to ask; the plan's own checks then stand alone.
|
|
21
|
+
*/
|
|
22
|
+
verify?(ctx: DependencyUpdater.Context, packages: readonly Package[]): Promise<string | undefined>;
|
|
23
|
+
}
|
|
24
|
+
export declare namespace DependencyUpdater {
|
|
25
|
+
/** What one call is handed. */
|
|
26
|
+
interface Context {
|
|
27
|
+
readonly app: RmanApplication;
|
|
28
|
+
readonly repository: Repository;
|
|
29
|
+
readonly options: Options;
|
|
30
|
+
}
|
|
31
|
+
/** The run's own settings, read off the command line. A package's `.rmanrc "deps"` block supplies
|
|
32
|
+
* the rest - see `settingsFor`. */
|
|
33
|
+
interface Options {
|
|
34
|
+
/** Only the dependencies whose names match one of these globs. */
|
|
35
|
+
names?: string[];
|
|
36
|
+
/** Overrides every package's `deps.target`. */
|
|
37
|
+
target?: string;
|
|
38
|
+
/** Added to every package's `deps.reject`. */
|
|
39
|
+
reject?: string[];
|
|
40
|
+
/** Overrides every package's `deps.minAge`. */
|
|
41
|
+
minAge?: number;
|
|
42
|
+
/** How many registry lookups run at once. */
|
|
43
|
+
concurrency: number;
|
|
44
|
+
/** Reported as each dependency name's registry lookup finishes. */
|
|
45
|
+
onProgress?(done: number, total: number): void;
|
|
46
|
+
}
|
|
47
|
+
/** One package's settings, once the command line has been laid over its config. */
|
|
48
|
+
interface Settings {
|
|
49
|
+
/** The largest move allowed, as one of the package's version scheme's `bumpNames` - `minor`
|
|
50
|
+
* keeps every dependency inside its major. */
|
|
51
|
+
target: string;
|
|
52
|
+
/** `deps.targets`: a dependency-name glob, and the largest move for the names it matches -
|
|
53
|
+
* ahead of `target`. Empty when `--target` was given, which overrides both. */
|
|
54
|
+
targets: Record<string, string>;
|
|
55
|
+
reject: string[];
|
|
56
|
+
minAge: number;
|
|
57
|
+
/** The dependency kinds to look at, in the ecosystem's own words - `undefined` is every one. */
|
|
58
|
+
types?: string[];
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* One dependency of one package.
|
|
62
|
+
*
|
|
63
|
+
* - `'update'`: `target` is the new range; `reason` says why it is lower than `latest`, if it is.
|
|
64
|
+
* - `'held'`: a newer version exists and is allowed, but another dependency's rule refuses it -
|
|
65
|
+
* `reason` names that rule.
|
|
66
|
+
* - `'skipped'`: a newer version exists, and the package's own settings leave it out - a major
|
|
67
|
+
* under `target: minor`, or one younger than `minAge`. `reason` says which.
|
|
68
|
+
* - `'up-to-date'`: nothing newer exists.
|
|
69
|
+
* - `'error'`: the registry gave no answer.
|
|
70
|
+
*/
|
|
71
|
+
interface Entry {
|
|
72
|
+
package: Package;
|
|
73
|
+
/** The dependency's name. */
|
|
74
|
+
name: string;
|
|
75
|
+
/** Where the package declares it, in the ecosystem's own words - `devDependencies`. */
|
|
76
|
+
types: string[];
|
|
77
|
+
/** The range as the manifest declares it. */
|
|
78
|
+
current: string;
|
|
79
|
+
status: 'update' | 'held' | 'skipped' | 'up-to-date' | 'error';
|
|
80
|
+
/** The range it is rewritten to, on an `'update'`. */
|
|
81
|
+
target?: string;
|
|
82
|
+
/** The newest version the package's own settings allow, before any other dependency's rule. */
|
|
83
|
+
latest?: string;
|
|
84
|
+
/** The newest version there is, whatever the settings say - what a reader compares against to
|
|
85
|
+
* see what was left behind. */
|
|
86
|
+
available?: string;
|
|
87
|
+
/** How far `target` moves, in the version scheme's own words - `major`. */
|
|
88
|
+
bump?: string;
|
|
89
|
+
reason?: string;
|
|
90
|
+
}
|
|
91
|
+
/** What `applyPlan` wrote. */
|
|
92
|
+
interface Applied {
|
|
93
|
+
/** Every file it changed, absolute. */
|
|
94
|
+
files: string[];
|
|
95
|
+
/** Puts every one of them back as it was. */
|
|
96
|
+
restore(): void;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Every move but the largest - `minor` under semver. The largest is the one a scheme reserves for a
|
|
100
|
+
* break, and taking a break is a decision a person makes, not a default.
|
|
101
|
+
*/
|
|
102
|
+
function defaultTarget(bumps: readonly string[]): string;
|
|
103
|
+
/**
|
|
104
|
+
* The largest move `settings` allow for the dependency `name`, and the key that said so - the
|
|
105
|
+
* **last** `deps.targets` glob matching it, else `deps.target`.
|
|
106
|
+
*/
|
|
107
|
+
function targetFor(settings: Settings, name: string): {
|
|
108
|
+
size: string;
|
|
109
|
+
from: string;
|
|
110
|
+
};
|
|
111
|
+
/** The settings `pkg` is planned with: `options` from the command line over its `.rmanrc "deps"`,
|
|
112
|
+
* and the defaults under both. */
|
|
113
|
+
function settingsFor(pkg: Package, options: Options): Settings;
|
|
114
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import micromatch from 'micromatch';
|
|
2
|
+
export var DependencyUpdater;
|
|
3
|
+
(function (DependencyUpdater) {
|
|
4
|
+
/**
|
|
5
|
+
* Every move but the largest - `minor` under semver. The largest is the one a scheme reserves for a
|
|
6
|
+
* break, and taking a break is a decision a person makes, not a default.
|
|
7
|
+
*/
|
|
8
|
+
/* **It was `latest` - every move - and that was the wrong default**, measured the moment it ran:
|
|
9
|
+
* a `^5.3.0` came back as `^7.0.2`. The values are the scheme's own `bumpNames` rather than a list
|
|
10
|
+
* written here, for the reason `version` gives: `patch`/`minor`/`major` are semver's words, and a
|
|
11
|
+
* four-part scheme would offer four. */
|
|
12
|
+
function defaultTarget(bumps) {
|
|
13
|
+
return bumps[Math.max(0, bumps.length - 2)];
|
|
14
|
+
}
|
|
15
|
+
DependencyUpdater.defaultTarget = defaultTarget;
|
|
16
|
+
/**
|
|
17
|
+
* The largest move `settings` allow for the dependency `name`, and the key that said so - the
|
|
18
|
+
* **last** `deps.targets` glob matching it, else `deps.target`.
|
|
19
|
+
*/
|
|
20
|
+
/* Last rather than first, because that is how two `"[selector]"` blocks layer: what is written
|
|
21
|
+
* later wins. */
|
|
22
|
+
function targetFor(settings, name) {
|
|
23
|
+
let found;
|
|
24
|
+
for (const [glob, size] of Object.entries(settings.targets)) {
|
|
25
|
+
if (micromatch.isMatch(name, glob))
|
|
26
|
+
found = { size, from: `deps.targets["${glob}"]` };
|
|
27
|
+
}
|
|
28
|
+
return found ?? { size: settings.target, from: 'deps.target' };
|
|
29
|
+
}
|
|
30
|
+
DependencyUpdater.targetFor = targetFor;
|
|
31
|
+
/** The settings `pkg` is planned with: `options` from the command line over its `.rmanrc "deps"`,
|
|
32
|
+
* and the defaults under both. */
|
|
33
|
+
function settingsFor(pkg, options) {
|
|
34
|
+
const config = pkg.config.deps ?? {};
|
|
35
|
+
const bumps = pkg.versionScheme.bumpNames;
|
|
36
|
+
const target = options.target ?? config.target ?? defaultTarget(bumps);
|
|
37
|
+
if (!bumps.includes(target)) {
|
|
38
|
+
throw new Error(`Invalid "deps.target" for "${pkg.name}": "${target}" (expected one of: ${bumps.join(', ')}, ` +
|
|
39
|
+
`the "${pkg.versionScheme.name}" scheme's own sizes)`);
|
|
40
|
+
}
|
|
41
|
+
const targets = options.target ? {} : { ...(config.targets ?? {}) };
|
|
42
|
+
for (const [glob, size] of Object.entries(targets)) {
|
|
43
|
+
if (!bumps.includes(size)) {
|
|
44
|
+
throw new Error(`Invalid "deps.targets" entry "${glob}" for "${pkg.name}": "${size}" (expected one of: ${bumps.join(', ')})`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
target,
|
|
49
|
+
targets,
|
|
50
|
+
reject: [...toList(config.reject), ...(options.reject ?? [])],
|
|
51
|
+
minAge: options.minAge ?? config.minAge ?? 0,
|
|
52
|
+
types: config.types?.length ? [...config.types] : undefined,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
DependencyUpdater.settingsFor = settingsFor;
|
|
56
|
+
})(DependencyUpdater || (DependencyUpdater = {}));
|
|
57
|
+
function toList(value) {
|
|
58
|
+
if (value === undefined)
|
|
59
|
+
return [];
|
|
60
|
+
return Array.isArray(value) ? [...value] : [value];
|
|
61
|
+
}
|
|
@@ -53,7 +53,7 @@ export var Manifest;
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
throw new Error(`No manifest provider recognizes "${dir}", so there is nowhere to write its version.\n` +
|
|
56
|
-
` A repository's ".rmanrc" names its technologies - see "
|
|
56
|
+
` A repository's ".rmanrc" names its technologies - see "platforms", or "extends" (e.g. "rman:node").`);
|
|
57
57
|
}
|
|
58
58
|
Manifest.write = write;
|
|
59
59
|
/**
|