rman 2.12.0 → 2.14.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-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/publish.service.d.ts +4 -0
- package/builtins/platforms/node/services/publish.service.js +22 -0
- package/builtins/publish-targets/npm/npm-publish-target.d.ts +2 -0
- package/builtins/publish-targets/npm/npm-publish-target.js +4 -0
- package/builtins/publish-targets/npm/npm-view.d.ts +31 -0
- package/builtins/publish-targets/npm/npm-view.js +47 -0
- package/commands/deps.command.d.ts +175 -0
- package/commands/deps.command.js +252 -0
- package/commands/list.command.js +53 -4
- 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/plugin.d.ts +4 -0
- package/core/interfaces/publish-target.d.ts +5 -0
- package/index.d.ts +2 -0
- package/index.js +2 -0
- package/package.json +1 -1
- package/services/changelog.service.d.ts +15 -0
- package/services/changelog.service.js +66 -6
- package/services/list.service.d.ts +13 -0
- package/services/list.service.js +23 -1
- package/utils/git.d.ts +17 -0
- package/utils/git.js +84 -0
|
@@ -79,6 +79,10 @@ export class NpmPublishTarget {
|
|
|
79
79
|
skipReason(pkg) {
|
|
80
80
|
return PublishService.skipReason(pkg);
|
|
81
81
|
}
|
|
82
|
+
/** The registry's host where `publishConfig.registry` names one other than npm's own. */
|
|
83
|
+
labelFor(pkg) {
|
|
84
|
+
return PublishService.registryLabel(pkg);
|
|
85
|
+
}
|
|
82
86
|
getPlan(ctx) {
|
|
83
87
|
return PublishService.getPlan(ctx.repository, this.planOptions(ctx));
|
|
84
88
|
}
|
|
@@ -46,3 +46,34 @@ export declare function npmViewVersion(name: string, cwd: string, options?: {
|
|
|
46
46
|
registry?: string;
|
|
47
47
|
userconfig?: string;
|
|
48
48
|
}): Promise<string | undefined>;
|
|
49
|
+
/** One published version, as `rman deps` needs it. */
|
|
50
|
+
export interface NpmRelease {
|
|
51
|
+
version: string;
|
|
52
|
+
/** The deprecation message, when the version carries one. */
|
|
53
|
+
deprecated?: string;
|
|
54
|
+
peerDependencies?: Record<string, string>;
|
|
55
|
+
peerDependenciesMeta?: Record<string, {
|
|
56
|
+
optional?: boolean;
|
|
57
|
+
}>;
|
|
58
|
+
engines?: Record<string, string>;
|
|
59
|
+
}
|
|
60
|
+
/** What `rman deps` asks of the registry about one package name. */
|
|
61
|
+
export interface NpmReleases {
|
|
62
|
+
/** Every version satisfying the range that was asked about, oldest first. */
|
|
63
|
+
releases: NpmRelease[];
|
|
64
|
+
/** The `latest` dist-tag. */
|
|
65
|
+
latest?: string;
|
|
66
|
+
/** When each version was published, when it was asked for. */
|
|
67
|
+
time?: Record<string, string>;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* `npm view '<name>@<range>' version deprecated peerDependencies peerDependenciesMeta engines` plus
|
|
71
|
+
* the package's `dist-tags` (and `time`, when `withTime`) - two calls, run at once. `undefined` when
|
|
72
|
+
* the registry gave no answer at all.
|
|
73
|
+
*
|
|
74
|
+
* **Not catch-everything, unlike the two above.** A name the registry does not know is an answer
|
|
75
|
+
* `rman deps` has to report, not a state it can treat as "nothing newer".
|
|
76
|
+
*/
|
|
77
|
+
export declare function npmViewReleases(name: string, range: string, cwd: string, options?: {
|
|
78
|
+
withTime?: boolean;
|
|
79
|
+
}): Promise<NpmReleases | undefined>;
|
|
@@ -63,9 +63,56 @@ export async function npmViewVersion(name, cwd, options = {}) {
|
|
|
63
63
|
return undefined;
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* `npm view '<name>@<range>' version deprecated peerDependencies peerDependenciesMeta engines` plus
|
|
68
|
+
* the package's `dist-tags` (and `time`, when `withTime`) - two calls, run at once. `undefined` when
|
|
69
|
+
* the registry gave no answer at all.
|
|
70
|
+
*
|
|
71
|
+
* **Not catch-everything, unlike the two above.** A name the registry does not know is an answer
|
|
72
|
+
* `rman deps` has to report, not a state it can treat as "nothing newer".
|
|
73
|
+
*/
|
|
74
|
+
/* **One call returns every version in the range.** npm answers a range with an array holding one
|
|
75
|
+
* object per matching version - measured at ~0.45s for a range covering dozens - so the cost is
|
|
76
|
+
* per name and not per version. A range matching exactly one version comes back as a bare object
|
|
77
|
+
* instead, which is why the result is normalized.
|
|
78
|
+
*
|
|
79
|
+
* `time` is a separate call because it is the whole package's history: thousands of entries for a
|
|
80
|
+
* package publishing nightlies, so it is fetched only when a minimum age asks for it. */
|
|
81
|
+
export async function npmViewReleases(name, range, cwd, options = {}) {
|
|
82
|
+
const fields = ['version', 'deprecated', 'peerDependencies', 'peerDependenciesMeta', 'engines'];
|
|
83
|
+
const [releases, tags] = await Promise.all([
|
|
84
|
+
npmViewJson(['view', `${name}@${range}`, ...fields, '--json'], cwd),
|
|
85
|
+
npmViewJson(['view', name, 'dist-tags', ...(options.withTime ? ['time'] : []), '--json'], cwd),
|
|
86
|
+
]);
|
|
87
|
+
if (tags === undefined)
|
|
88
|
+
return undefined;
|
|
89
|
+
const list = Array.isArray(releases) ? releases : releases ? [releases] : [];
|
|
90
|
+
const meta = (options.withTime ? tags : { 'dist-tags': tags });
|
|
91
|
+
return {
|
|
92
|
+
releases: list.filter((r) => typeof r?.version === 'string'),
|
|
93
|
+
latest: meta['dist-tags']?.latest,
|
|
94
|
+
time: meta.time,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
66
97
|
function toVersionList(versions) {
|
|
67
98
|
if (Array.isArray(versions))
|
|
68
99
|
return versions;
|
|
69
100
|
return versions ? [versions] : [];
|
|
70
101
|
}
|
|
71
102
|
const execFileAsync = promisify(execFile);
|
|
103
|
+
/** `npm <argv>` parsed as JSON - `undefined` for a failed call, `null` for an empty answer (a range
|
|
104
|
+
* no version satisfies). */
|
|
105
|
+
async function npmViewJson(argv, cwd) {
|
|
106
|
+
try {
|
|
107
|
+
const { stdout } = await execFileAsync('npm', argv, { cwd, maxBuffer: 64 * 1024 * 1024 });
|
|
108
|
+
const text = stdout.trim();
|
|
109
|
+
return text ? JSON.parse(text) : null;
|
|
110
|
+
}
|
|
111
|
+
catch (e) {
|
|
112
|
+
/** A range nothing satisfies is answered with `E404 No match found` rather than an empty list. */
|
|
113
|
+
if (argv[1]?.includes('@', 1) && /No match found/.test(String(e?.stdout ?? '') + String(e?.stderr ?? ''))) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -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/list.command.js
CHANGED
|
@@ -2,6 +2,7 @@ import colors from 'ansi-colors';
|
|
|
2
2
|
import EasyTable from 'easy-table';
|
|
3
3
|
import { registerCommand } from '../interfaces/rman-config.interface.js';
|
|
4
4
|
import { packageFilterOptions, readPackageFilterOptions } from '../utils/package-filter.js';
|
|
5
|
+
import { isNamedGroupKey, isSoloGroupKey, ROOT_GROUP_KEY } from '../utils/version-group.js';
|
|
5
6
|
/** `[options...]` is yargs-meaningless - its variadic marker is two dots, and this command declares
|
|
6
7
|
* no positional for it. Left as it is rather than fixed in passing: dropping it makes
|
|
7
8
|
* `rman list foo` start failing, which is a behaviour change and belongs in its own commit. */
|
|
@@ -82,8 +83,9 @@ const listCommand = registerCommand(app => {
|
|
|
82
83
|
* so counting rows that are not the root reported `0 Package(s) found` there (measured). The
|
|
83
84
|
* condition is the same one the service applies, and it is one line.
|
|
84
85
|
*/
|
|
86
|
+
/** Grouped, except where the order *is* the answer: `--toposort` is dependency order. */
|
|
85
87
|
else
|
|
86
|
-
printTable(items, table && app.repository.monorepo);
|
|
88
|
+
printTable(args.toposort ? items : byGroup(items), table && app.repository.monorepo);
|
|
87
89
|
},
|
|
88
90
|
};
|
|
89
91
|
});
|
|
@@ -100,10 +102,54 @@ function statusLabel(status) {
|
|
|
100
102
|
return '';
|
|
101
103
|
}
|
|
102
104
|
}
|
|
103
|
-
/** Where a package ships, one target
|
|
104
|
-
*
|
|
105
|
+
/** Where a package ships, one target each - its label where it has one (`Item.targetLabels`, npm's
|
|
106
|
+
* registry host). A target `publish` would skip it for (`Item.skippedTargets`) is left out, and a
|
|
107
|
+
* package left with none reads `-`, so the column never reads as a promise the release will not
|
|
108
|
+
* keep. Blank where no target claims the package at all - a monorepo's root, for one. */
|
|
105
109
|
function publishLabel(it) {
|
|
106
|
-
|
|
110
|
+
if (!it.publishTargets.length)
|
|
111
|
+
return '';
|
|
112
|
+
const shipping = it.publishTargets.filter(name => !it.skippedTargets[name]);
|
|
113
|
+
if (!shipping.length)
|
|
114
|
+
return colors.gray('-');
|
|
115
|
+
return shipping.map(name => colors.green(it.targetLabels[name] ?? name)).join(', ');
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* The rows with each release group's members together - the root first, then the groups in the
|
|
119
|
+
* order their first member appears, then the packages in no group (`group: false`) - the order
|
|
120
|
+
* `version`'s plan prints them in. Stable within a group, so the lexical order holds there.
|
|
121
|
+
*/
|
|
122
|
+
/* The cost, stated: a package nested inside another can land away from it, when the two are in
|
|
123
|
+
* different groups - its indentation still says how deep it sits, not under which row. */
|
|
124
|
+
function byGroup(items) {
|
|
125
|
+
const root = items.filter(it => it.groupKey === ROOT_GROUP_KEY);
|
|
126
|
+
const groups = new Map();
|
|
127
|
+
const solo = [];
|
|
128
|
+
for (const it of items) {
|
|
129
|
+
if (it.groupKey === ROOT_GROUP_KEY)
|
|
130
|
+
continue;
|
|
131
|
+
if (isSoloGroupKey(it.groupKey))
|
|
132
|
+
solo.push(it);
|
|
133
|
+
else
|
|
134
|
+
groups.set(it.groupKey, [...(groups.get(it.groupKey) ?? []), it]);
|
|
135
|
+
}
|
|
136
|
+
return [...root, ...[...groups.values()].flat(), ...solo];
|
|
137
|
+
}
|
|
138
|
+
/** Whether the Group column says anything: a group the repository named, or the default group
|
|
139
|
+
* beside packages in none. One line for everything, or no grouping at all, would put the same
|
|
140
|
+
* answer - or nothing - on every row. */
|
|
141
|
+
function showsGroups(items) {
|
|
142
|
+
const keys = items.map(it => it.groupKey).filter(key => key !== ROOT_GROUP_KEY);
|
|
143
|
+
return keys.some(isNamedGroupKey) || (keys.includes('default') && keys.some(isSoloGroupKey));
|
|
144
|
+
}
|
|
145
|
+
/** A named group by its name, the default one as a grey `(default)` - `version`'s plan table reads
|
|
146
|
+
* the same way. Blank for the root and for a package in no group. */
|
|
147
|
+
function groupCell(it) {
|
|
148
|
+
if (isNamedGroupKey(it.groupKey))
|
|
149
|
+
return colors.cyan(it.group);
|
|
150
|
+
if (it.groupKey === 'default')
|
|
151
|
+
return colors.gray('(default)');
|
|
152
|
+
return '';
|
|
107
153
|
}
|
|
108
154
|
/**
|
|
109
155
|
* **The tree, as a table**: the root first, then each package indented by how far below it sits.
|
|
@@ -125,10 +171,13 @@ function publishLabel(it) {
|
|
|
125
171
|
*/
|
|
126
172
|
function printTable(items, withRoot) {
|
|
127
173
|
const table = new EasyTable();
|
|
174
|
+
const withGroup = showsGroups(items);
|
|
128
175
|
for (const it of items) {
|
|
129
176
|
const indent = ' '.repeat(it.depth);
|
|
130
177
|
table.cell('Package', indent + (it.isRoot ? colors.whiteBright(it.selector) : colors.yellowBright(it.selector)));
|
|
131
178
|
table.cell('Version', colors.yellow(it.version));
|
|
179
|
+
if (withGroup)
|
|
180
|
+
table.cell('Group', groupCell(it));
|
|
132
181
|
table.cell('Platform', it.platform ? colors.cyan(it.platform) : '');
|
|
133
182
|
table.cell('Private', it.private ? colors.magentaBright('yes') : '');
|
|
134
183
|
table.cell('Publish', publishLabel(it));
|
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.14.0';
|