@titannio/webtoolkit-cli 1.3.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/LICENSE +21 -0
- package/README.md +639 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +268 -0
- package/dist/bundle-audit.d.ts +7 -0
- package/dist/bundle-audit.js +111 -0
- package/dist/cleaner.d.ts +15 -0
- package/dist/cleaner.js +359 -0
- package/dist/config-reference.d.ts +8 -0
- package/dist/config-reference.js +805 -0
- package/dist/config.d.ts +306 -0
- package/dist/config.js +173 -0
- package/dist/dev-grid.d.ts +7 -0
- package/dist/dev-grid.js +181 -0
- package/dist/dev-watch.d.ts +13 -0
- package/dist/dev-watch.js +184 -0
- package/dist/environment.d.ts +10 -0
- package/dist/environment.js +172 -0
- package/dist/guard-registry.d.ts +1 -0
- package/dist/guard-registry.js +17 -0
- package/dist/guard-runner.d.ts +4 -0
- package/dist/guard-runner.js +36 -0
- package/dist/guards/any-guard.d.ts +16 -0
- package/dist/guards/any-guard.js +121 -0
- package/dist/guards/assert-no-tests-in-dist.d.ts +1 -0
- package/dist/guards/assert-no-tests-in-dist.js +56 -0
- package/dist/guards/check-mojibake.d.ts +52 -0
- package/dist/guards/check-mojibake.js +378 -0
- package/dist/guards/code-pattern-guard.d.ts +71 -0
- package/dist/guards/code-pattern-guard.js +654 -0
- package/dist/guards/dal-service-repository-check.d.ts +13 -0
- package/dist/guards/dal-service-repository-check.js +264 -0
- package/dist/guards/dependency-cruiser-guard.d.ts +14 -0
- package/dist/guards/dependency-cruiser-guard.js +69 -0
- package/dist/guards/documentation-guard.d.ts +3 -0
- package/dist/guards/documentation-guard.js +370 -0
- package/dist/guards/guard-config.d.ts +19 -0
- package/dist/guards/guard-config.js +87 -0
- package/dist/guards/internal-link-guard.d.ts +12 -0
- package/dist/guards/internal-link-guard.js +272 -0
- package/dist/guards/package-surface-guard.d.ts +37 -0
- package/dist/guards/package-surface-guard.js +234 -0
- package/dist/guards/pnpm-workspace-config.d.ts +2 -0
- package/dist/guards/pnpm-workspace-config.js +40 -0
- package/dist/guards/rebuild-preflight.d.ts +29 -0
- package/dist/guards/rebuild-preflight.js +137 -0
- package/dist/guards/repository-hygiene-guard.d.ts +12 -0
- package/dist/guards/repository-hygiene-guard.js +70 -0
- package/dist/guards/schema-guard.d.ts +10 -0
- package/dist/guards/schema-guard.js +160 -0
- package/dist/guards/singleton-deps-guard.d.ts +21 -0
- package/dist/guards/singleton-deps-guard.js +183 -0
- package/dist/guards/tsconfig-guard.d.ts +5 -0
- package/dist/guards/tsconfig-guard.js +105 -0
- package/dist/guards/workspace-manifest-guard.d.ts +21 -0
- package/dist/guards/workspace-manifest-guard.js +210 -0
- package/dist/jsdoc-report.d.ts +7 -0
- package/dist/jsdoc-report.js +456 -0
- package/dist/process.d.ts +20 -0
- package/dist/process.js +86 -0
- package/dist/ready-service.d.ts +7 -0
- package/dist/ready-service.js +135 -0
- package/dist/release-gate.d.ts +7 -0
- package/dist/release-gate.js +35 -0
- package/dist/repo-check.d.ts +7 -0
- package/dist/repo-check.js +121 -0
- package/dist/tasks.d.ts +17 -0
- package/dist/tasks.js +195 -0
- package/dist/upgrade.d.ts +10 -0
- package/dist/upgrade.js +674 -0
- package/dist/validate.d.ts +7 -0
- package/dist/validate.js +51 -0
- package/dist/workspace-tests.d.ts +33 -0
- package/dist/workspace-tests.js +529 -0
- package/package.json +57 -0
package/dist/upgrade.js
ADDED
|
@@ -0,0 +1,674 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { createInterface } from 'node:readline/promises';
|
|
5
|
+
import { stdin as input, stdout as output } from 'node:process';
|
|
6
|
+
import { prepareCorepackPnpm } from './environment.js';
|
|
7
|
+
import { buildFreshPackageManagerCommand, buildPackageManagerCommand, formatCommand, runCommandBuffered, runCommandInherited } from './process.js';
|
|
8
|
+
const colors = {
|
|
9
|
+
reset: '\x1b[0m',
|
|
10
|
+
bright: '\x1b[1m',
|
|
11
|
+
green: '\x1b[32m',
|
|
12
|
+
yellow: '\x1b[33m',
|
|
13
|
+
cyan: '\x1b[36m',
|
|
14
|
+
red: '\x1b[31m',
|
|
15
|
+
gray: '\x1b[90m',
|
|
16
|
+
};
|
|
17
|
+
const manifestVersionFields = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'];
|
|
18
|
+
const skippedReasonOrder = ['cooldown', 'major', 'protected-singleton'];
|
|
19
|
+
const skippedReasonLabels = {
|
|
20
|
+
cooldown: 'Cooldown',
|
|
21
|
+
major: 'Major',
|
|
22
|
+
'protected-singleton': 'Protected singleton',
|
|
23
|
+
};
|
|
24
|
+
const skippedReasonPriority = {
|
|
25
|
+
cooldown: 0,
|
|
26
|
+
major: 1,
|
|
27
|
+
'protected-singleton': 2,
|
|
28
|
+
};
|
|
29
|
+
function colorize(value, color) {
|
|
30
|
+
return `${color}${value}${colors.reset}`;
|
|
31
|
+
}
|
|
32
|
+
function uniqueSorted(values) {
|
|
33
|
+
return Array.from(new Set(values)).sort((left, right) => left.localeCompare(right));
|
|
34
|
+
}
|
|
35
|
+
function normalizeVersionSpec(version) {
|
|
36
|
+
return version.replace(/^[~^]/u, '');
|
|
37
|
+
}
|
|
38
|
+
function validatePinnedPnpmVersion(version) {
|
|
39
|
+
if (!version || version.includes(' ') || version === 'latest' || version === '11') {
|
|
40
|
+
throw new Error(`packageManager must pin an exact pnpm version, but found ${JSON.stringify(`pnpm@${version}`)}.`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function parsePnpmPackageManagerVersion(packageManager) {
|
|
44
|
+
if (typeof packageManager !== 'string' || !packageManager.startsWith('pnpm@'))
|
|
45
|
+
return null;
|
|
46
|
+
const version = packageManager.slice('pnpm@'.length).trim();
|
|
47
|
+
validatePinnedPnpmVersion(version);
|
|
48
|
+
return version;
|
|
49
|
+
}
|
|
50
|
+
function getVersionMajor(versionSpec) {
|
|
51
|
+
const matched = normalizeVersionSpec(versionSpec).match(/^(\d+)/u);
|
|
52
|
+
return matched ? Number.parseInt(matched[1], 10) : null;
|
|
53
|
+
}
|
|
54
|
+
function shouldIncludeOutdatedTarget(currentVersion, latestVersion, target) {
|
|
55
|
+
if (!currentVersion || !latestVersion || currentVersion === latestVersion)
|
|
56
|
+
return false;
|
|
57
|
+
if (target === 'latest')
|
|
58
|
+
return true;
|
|
59
|
+
const currentMajor = getVersionMajor(currentVersion);
|
|
60
|
+
const latestMajor = getVersionMajor(latestVersion);
|
|
61
|
+
return currentMajor !== null && latestMajor !== null && currentMajor === latestMajor;
|
|
62
|
+
}
|
|
63
|
+
function applyManifestVersionStyle(currentVersion, targetVersion) {
|
|
64
|
+
if (currentVersion?.startsWith('^'))
|
|
65
|
+
return `^${normalizeVersionSpec(targetVersion)}`;
|
|
66
|
+
if (currentVersion?.startsWith('~'))
|
|
67
|
+
return `~${normalizeVersionSpec(targetVersion)}`;
|
|
68
|
+
return normalizeVersionSpec(targetVersion);
|
|
69
|
+
}
|
|
70
|
+
function unquoteYamlScalar(value) {
|
|
71
|
+
const trimmed = value.trim();
|
|
72
|
+
if ((trimmed.startsWith("'") && trimmed.endsWith("'")) || (trimmed.startsWith('"') && trimmed.endsWith('"'))) {
|
|
73
|
+
return trimmed.slice(1, -1);
|
|
74
|
+
}
|
|
75
|
+
return trimmed;
|
|
76
|
+
}
|
|
77
|
+
function sortUpdatesByFile(updatesByFile) {
|
|
78
|
+
return Object.fromEntries(Object.entries(updatesByFile)
|
|
79
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
80
|
+
.map(([filePath, updates]) => [
|
|
81
|
+
filePath,
|
|
82
|
+
Object.fromEntries(Object.entries(updates).sort(([left], [right]) => left.localeCompare(right))),
|
|
83
|
+
]));
|
|
84
|
+
}
|
|
85
|
+
function mergeWorkspaceUpdates(...sources) {
|
|
86
|
+
const merged = {};
|
|
87
|
+
for (const source of sources) {
|
|
88
|
+
for (const [filePath, updates] of Object.entries(source)) {
|
|
89
|
+
merged[filePath] = { ...(merged[filePath] ?? {}), ...updates };
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return sortUpdatesByFile(merged);
|
|
93
|
+
}
|
|
94
|
+
function normalizeNcuJson(raw) {
|
|
95
|
+
const trimmed = raw.trim();
|
|
96
|
+
if (!trimmed)
|
|
97
|
+
return {};
|
|
98
|
+
const parsed = JSON.parse(trimmed);
|
|
99
|
+
const entries = Object.entries(parsed);
|
|
100
|
+
const looksLikeWorkspaceMap = entries.every(([, value]) => value !== null && typeof value === 'object' && !Array.isArray(value));
|
|
101
|
+
if (!looksLikeWorkspaceMap) {
|
|
102
|
+
return {
|
|
103
|
+
'package.json': Object.fromEntries(entries.filter(([, value]) => typeof value === 'string')),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
return sortUpdatesByFile(Object.fromEntries(entries.map(([filePath, value]) => [
|
|
107
|
+
filePath,
|
|
108
|
+
Object.fromEntries(Object.entries(value).filter(([, version]) => typeof version === 'string')),
|
|
109
|
+
])));
|
|
110
|
+
}
|
|
111
|
+
function parseJsonObjectFromCommandOutput(raw) {
|
|
112
|
+
const start = raw.indexOf('{');
|
|
113
|
+
if (start === -1)
|
|
114
|
+
throw new Error('No JSON object found.');
|
|
115
|
+
let depth = 0;
|
|
116
|
+
let inString = false;
|
|
117
|
+
let escaping = false;
|
|
118
|
+
for (let index = start; index < raw.length; index += 1) {
|
|
119
|
+
const character = raw[index];
|
|
120
|
+
if (escaping) {
|
|
121
|
+
escaping = false;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (character === '\\') {
|
|
125
|
+
escaping = inString;
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (character === '"') {
|
|
129
|
+
inString = !inString;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (inString)
|
|
133
|
+
continue;
|
|
134
|
+
if (character === '{')
|
|
135
|
+
depth += 1;
|
|
136
|
+
if (character === '}') {
|
|
137
|
+
depth -= 1;
|
|
138
|
+
if (depth === 0) {
|
|
139
|
+
return JSON.parse(raw.slice(start, index + 1));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
throw new Error('Unterminated JSON object.');
|
|
144
|
+
}
|
|
145
|
+
function toWorkspaceManifestPath(location, workspaceRoot) {
|
|
146
|
+
const relativeLocation = path.relative(workspaceRoot, location);
|
|
147
|
+
if (relativeLocation.startsWith('..') || path.isAbsolute(relativeLocation))
|
|
148
|
+
return null;
|
|
149
|
+
const normalizedLocation = relativeLocation.replace(/\\/gu, '/');
|
|
150
|
+
return normalizedLocation ? `${normalizedLocation}/package.json` : 'package.json';
|
|
151
|
+
}
|
|
152
|
+
function normalizePnpmOutdatedJson(raw, workspaceRoot, target) {
|
|
153
|
+
const trimmed = raw.trim();
|
|
154
|
+
if (!trimmed)
|
|
155
|
+
return {};
|
|
156
|
+
const parsed = JSON.parse(trimmed);
|
|
157
|
+
const updatesByFile = {};
|
|
158
|
+
for (const [packageName, packageInfo] of Object.entries(parsed)) {
|
|
159
|
+
const currentVersion = typeof packageInfo.current === 'string' ? packageInfo.current : null;
|
|
160
|
+
const latestVersion = typeof packageInfo.latest === 'string' ? packageInfo.latest : null;
|
|
161
|
+
if (!currentVersion || !latestVersion || !shouldIncludeOutdatedTarget(currentVersion, latestVersion, target))
|
|
162
|
+
continue;
|
|
163
|
+
const dependentPackages = Array.isArray(packageInfo.dependentPackages) ? packageInfo.dependentPackages : [];
|
|
164
|
+
for (const dependentPackage of dependentPackages) {
|
|
165
|
+
const location = typeof dependentPackage === 'object' && dependentPackage !== null && 'location' in dependentPackage
|
|
166
|
+
? dependentPackage.location
|
|
167
|
+
: null;
|
|
168
|
+
if (typeof location !== 'string')
|
|
169
|
+
continue;
|
|
170
|
+
const manifestPath = toWorkspaceManifestPath(location, workspaceRoot);
|
|
171
|
+
if (!manifestPath)
|
|
172
|
+
continue;
|
|
173
|
+
updatesByFile[manifestPath] = {
|
|
174
|
+
...(updatesByFile[manifestPath] ?? {}),
|
|
175
|
+
[packageName]: latestVersion,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return sortUpdatesByFile(updatesByFile);
|
|
180
|
+
}
|
|
181
|
+
async function readManifestVersions(filePath) {
|
|
182
|
+
const manifest = JSON.parse(await readFile(filePath, 'utf8'));
|
|
183
|
+
const versions = {};
|
|
184
|
+
for (const field of manifestVersionFields) {
|
|
185
|
+
const section = manifest[field];
|
|
186
|
+
if (!section || typeof section !== 'object' || Array.isArray(section))
|
|
187
|
+
continue;
|
|
188
|
+
for (const [packageName, version] of Object.entries(section)) {
|
|
189
|
+
if (typeof version === 'string')
|
|
190
|
+
versions[packageName] = version;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return versions;
|
|
194
|
+
}
|
|
195
|
+
async function readRootPnpmPackageManagerVersion(rootDir) {
|
|
196
|
+
const manifest = JSON.parse(await readFile(path.join(rootDir, 'package.json'), 'utf8'));
|
|
197
|
+
return parsePnpmPackageManagerVersion(manifest.packageManager);
|
|
198
|
+
}
|
|
199
|
+
async function getManifestVersionsByFile(updatesByFile, rootDir) {
|
|
200
|
+
const files = Object.keys(updatesByFile);
|
|
201
|
+
const entries = await Promise.all(files.map(async (filePath) => [filePath, await readManifestVersions(path.join(rootDir, filePath))]));
|
|
202
|
+
return Object.fromEntries(entries);
|
|
203
|
+
}
|
|
204
|
+
function buildUpgradeEntries(updatesByFile, resolveCurrentVersion) {
|
|
205
|
+
const entries = [];
|
|
206
|
+
for (const [filePath, updates] of Object.entries(sortUpdatesByFile(updatesByFile))) {
|
|
207
|
+
for (const [packageName, targetVersion] of Object.entries(updates)) {
|
|
208
|
+
entries.push({
|
|
209
|
+
filePath,
|
|
210
|
+
packageName,
|
|
211
|
+
currentVersion: resolveCurrentVersion(filePath, packageName, targetVersion),
|
|
212
|
+
targetVersion,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return entries;
|
|
217
|
+
}
|
|
218
|
+
function buildSkippedUpgradeEntries(updatesByFile, reason, resolveCurrentVersion) {
|
|
219
|
+
return buildUpgradeEntries(updatesByFile, resolveCurrentVersion).map((entry) => ({
|
|
220
|
+
...entry,
|
|
221
|
+
reason,
|
|
222
|
+
}));
|
|
223
|
+
}
|
|
224
|
+
function getUpgradeEntryKey(filePath, packageName) {
|
|
225
|
+
return `${filePath}::${packageName}`;
|
|
226
|
+
}
|
|
227
|
+
function filterWorkspaceUpdatesByPackageNames(updatesByFile, packageNames) {
|
|
228
|
+
return sortUpdatesByFile(Object.fromEntries(Object.entries(updatesByFile)
|
|
229
|
+
.map(([filePath, updates]) => [
|
|
230
|
+
filePath,
|
|
231
|
+
Object.fromEntries(Object.entries(updates).filter(([packageName]) => packageNames.has(packageName))),
|
|
232
|
+
])
|
|
233
|
+
.filter(([, updates]) => Object.keys(updates).length > 0)));
|
|
234
|
+
}
|
|
235
|
+
function subtractWorkspaceUpdates(minuend, subtrahend) {
|
|
236
|
+
return sortUpdatesByFile(Object.fromEntries(Object.entries(minuend)
|
|
237
|
+
.map(([filePath, updates]) => [
|
|
238
|
+
filePath,
|
|
239
|
+
Object.fromEntries(Object.entries(updates).filter(([packageName]) => !(packageName in (subtrahend[filePath] ?? {})))),
|
|
240
|
+
])
|
|
241
|
+
.filter(([, updates]) => Object.keys(updates).length > 0)));
|
|
242
|
+
}
|
|
243
|
+
function addSkippedEntries(target, entries) {
|
|
244
|
+
for (const entry of entries) {
|
|
245
|
+
const key = getUpgradeEntryKey(entry.filePath, entry.packageName);
|
|
246
|
+
const existing = target.get(key);
|
|
247
|
+
if (!existing || skippedReasonPriority[entry.reason] < skippedReasonPriority[existing.reason]) {
|
|
248
|
+
target.set(key, entry);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
function readProtectedOverrides(rootDir, relativeFilePath) {
|
|
253
|
+
const filePath = path.join(rootDir, relativeFilePath);
|
|
254
|
+
if (!existsSync(filePath))
|
|
255
|
+
return {};
|
|
256
|
+
const lines = readFileSync(filePath, 'utf8').split(/\r?\n/u);
|
|
257
|
+
const overrides = {};
|
|
258
|
+
let insideOverrides = false;
|
|
259
|
+
for (const line of lines) {
|
|
260
|
+
if (!insideOverrides) {
|
|
261
|
+
if (/^overrides:\s*$/u.test(line))
|
|
262
|
+
insideOverrides = true;
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
if (/^\S/u.test(line))
|
|
266
|
+
break;
|
|
267
|
+
const match = line.match(/^(\s{2,})([^:#][^:]*):\s*(.*?)\s*$/u);
|
|
268
|
+
if (!match)
|
|
269
|
+
continue;
|
|
270
|
+
overrides[unquoteYamlScalar(match[2])] = unquoteYamlScalar(match[3]);
|
|
271
|
+
}
|
|
272
|
+
return overrides;
|
|
273
|
+
}
|
|
274
|
+
async function updateProtectedOverrides(rootDir, relativeFilePath, overrideUpdates) {
|
|
275
|
+
if (Object.keys(overrideUpdates).length === 0)
|
|
276
|
+
return;
|
|
277
|
+
const filePath = path.join(rootDir, relativeFilePath);
|
|
278
|
+
const lines = (await readFile(filePath, 'utf8')).split(/\r?\n/u);
|
|
279
|
+
const remainingPackages = new Set(Object.keys(overrideUpdates));
|
|
280
|
+
let insideOverrides = false;
|
|
281
|
+
let modified = false;
|
|
282
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
283
|
+
const line = lines[index];
|
|
284
|
+
if (!insideOverrides) {
|
|
285
|
+
if (/^overrides:\s*$/u.test(line))
|
|
286
|
+
insideOverrides = true;
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
if (/^\S/u.test(line))
|
|
290
|
+
break;
|
|
291
|
+
const match = line.match(/^(\s{2,})([^:#][^:]*):\s*(.*?)\s*$/u);
|
|
292
|
+
if (!match)
|
|
293
|
+
continue;
|
|
294
|
+
const packageName = unquoteYamlScalar(match[2]);
|
|
295
|
+
const targetVersion = overrideUpdates[packageName];
|
|
296
|
+
if (!targetVersion)
|
|
297
|
+
continue;
|
|
298
|
+
lines[index] = `${match[1]}${match[2]}: ${targetVersion}`;
|
|
299
|
+
remainingPackages.delete(packageName);
|
|
300
|
+
modified = true;
|
|
301
|
+
}
|
|
302
|
+
if (remainingPackages.size > 0) {
|
|
303
|
+
throw new Error(`Unable to update protected override(s): ${Array.from(remainingPackages).join(', ')}`);
|
|
304
|
+
}
|
|
305
|
+
if (modified)
|
|
306
|
+
await writeFile(filePath, lines.join('\n'));
|
|
307
|
+
}
|
|
308
|
+
function getProtectedDependencyNames(runtime) {
|
|
309
|
+
return Object.keys(readProtectedOverrides(runtime.cwd, runtime.config.upgrade?.protectedOverridesFile ?? 'pnpm-workspace.yaml')).sort((left, right) => left.localeCompare(right));
|
|
310
|
+
}
|
|
311
|
+
export function mergeRejectLists(baseRejectList, protectedDependencyNames) {
|
|
312
|
+
return uniqueSorted([...baseRejectList, ...protectedDependencyNames]);
|
|
313
|
+
}
|
|
314
|
+
async function runBufferedPm(runtime, args, rejectOnNonZero = true) {
|
|
315
|
+
const command = buildPackageManagerCommand(runtime.config.packageManager, args);
|
|
316
|
+
const result = await runCommandBuffered(command, runtime.cwd);
|
|
317
|
+
if (rejectOnNonZero && result.code !== 0) {
|
|
318
|
+
throw new Error(`Command failed: ${formatCommand(command.command, command.args ?? [])}\n${result.output}`);
|
|
319
|
+
}
|
|
320
|
+
return result;
|
|
321
|
+
}
|
|
322
|
+
function runInheritedPm(runtime, args, buildCommand = buildPackageManagerCommand) {
|
|
323
|
+
const command = buildCommand(runtime.config.packageManager, args);
|
|
324
|
+
const code = runCommandInherited(command, runtime.cwd);
|
|
325
|
+
if (code !== 0) {
|
|
326
|
+
throw new Error(`Command failed: ${formatCommand(command.command, command.args ?? [])}`);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
async function collectNcuUpdates(runtime, target, rejectList, includeProtected) {
|
|
330
|
+
const protectedNames = getProtectedDependencyNames(runtime);
|
|
331
|
+
const mergedRejectList = includeProtected ? rejectList : mergeRejectLists(rejectList, protectedNames);
|
|
332
|
+
const args = ['exec', 'ncu', '--jsonUpgraded', '--workspaces', '--root', '--target', target];
|
|
333
|
+
if (mergedRejectList.length > 0)
|
|
334
|
+
args.push('--reject', mergedRejectList.join(','));
|
|
335
|
+
const result = await runBufferedPm(runtime, args);
|
|
336
|
+
return normalizeNcuJson(result.output);
|
|
337
|
+
}
|
|
338
|
+
async function collectOutdatedUpdates(runtime, target, rejectList, includeProtected) {
|
|
339
|
+
const protectedNames = getProtectedDependencyNames(runtime);
|
|
340
|
+
const mergedRejectList = new Set(includeProtected ? rejectList : mergeRejectLists(rejectList, protectedNames));
|
|
341
|
+
const result = await runBufferedPm(runtime, ['outdated', '--format', 'json', '--recursive'], false);
|
|
342
|
+
if (result.code !== 0 && result.code !== 1) {
|
|
343
|
+
throw new Error(`Command failed: pnpm outdated --format json --recursive\n${result.output}`);
|
|
344
|
+
}
|
|
345
|
+
const rawUpdates = normalizePnpmOutdatedJson(result.output, runtime.cwd, target);
|
|
346
|
+
const filteredUpdates = Object.fromEntries(Object.entries(rawUpdates)
|
|
347
|
+
.map(([filePath, updates]) => [
|
|
348
|
+
filePath,
|
|
349
|
+
Object.fromEntries(Object.entries(updates).filter(([packageName]) => !mergedRejectList.has(packageName))),
|
|
350
|
+
])
|
|
351
|
+
.filter(([, updates]) => Object.keys(updates).length > 0));
|
|
352
|
+
const versionsByFile = await getManifestVersionsByFile(filteredUpdates, runtime.cwd);
|
|
353
|
+
return sortUpdatesByFile(Object.fromEntries(Object.entries(filteredUpdates).map(([filePath, updates]) => [
|
|
354
|
+
filePath,
|
|
355
|
+
Object.fromEntries(Object.entries(updates).map(([packageName, targetVersion]) => [
|
|
356
|
+
packageName,
|
|
357
|
+
applyManifestVersionStyle(versionsByFile[filePath]?.[packageName], targetVersion),
|
|
358
|
+
])),
|
|
359
|
+
])));
|
|
360
|
+
}
|
|
361
|
+
async function collectUpgradeCandidates(runtime, target, rejectList, includeProtected = false) {
|
|
362
|
+
const [outdatedUpdates, ncuUpdates] = await Promise.all([
|
|
363
|
+
collectOutdatedUpdates(runtime, target, rejectList, includeProtected),
|
|
364
|
+
collectNcuUpdates(runtime, target, rejectList, includeProtected),
|
|
365
|
+
]);
|
|
366
|
+
return mergeWorkspaceUpdates(outdatedUpdates, ncuUpdates);
|
|
367
|
+
}
|
|
368
|
+
async function collectProtectedHoldUpdates(runtime, target, rejectList = []) {
|
|
369
|
+
const protectedNames = new Set(getProtectedDependencyNames(runtime));
|
|
370
|
+
const allCandidates = await collectUpgradeCandidates(runtime, target, rejectList, true);
|
|
371
|
+
return sortUpdatesByFile(Object.fromEntries(Object.entries(allCandidates)
|
|
372
|
+
.map(([filePath, updates]) => [
|
|
373
|
+
filePath,
|
|
374
|
+
Object.fromEntries(Object.entries(updates).filter(([packageName]) => protectedNames.has(packageName))),
|
|
375
|
+
])
|
|
376
|
+
.filter(([, updates]) => Object.keys(updates).length > 0)));
|
|
377
|
+
}
|
|
378
|
+
function deriveProtectedOverrideTargetVersion(currentOverride, normalizedTargetVersion) {
|
|
379
|
+
if (currentOverride?.startsWith('^'))
|
|
380
|
+
return `^${normalizedTargetVersion}`;
|
|
381
|
+
if (currentOverride?.startsWith('~'))
|
|
382
|
+
return `~${normalizedTargetVersion}`;
|
|
383
|
+
return normalizedTargetVersion;
|
|
384
|
+
}
|
|
385
|
+
function buildProtectedUpgradePlans(runtime, entries) {
|
|
386
|
+
const currentOverrides = readProtectedOverrides(runtime.cwd, runtime.config.upgrade?.protectedOverridesFile ?? 'pnpm-workspace.yaml');
|
|
387
|
+
const hints = runtime.config.upgrade?.protectedDependencyUpstreamHints ?? {};
|
|
388
|
+
const normalizedTargetsByPackage = new Map();
|
|
389
|
+
for (const entry of entries) {
|
|
390
|
+
const normalizedTargets = normalizedTargetsByPackage.get(entry.packageName) ?? new Set();
|
|
391
|
+
normalizedTargets.add(normalizeVersionSpec(entry.targetVersion));
|
|
392
|
+
normalizedTargetsByPackage.set(entry.packageName, normalizedTargets);
|
|
393
|
+
}
|
|
394
|
+
return Array.from(normalizedTargetsByPackage.entries())
|
|
395
|
+
.map(([packageName, targets]) => {
|
|
396
|
+
if (targets.size !== 1) {
|
|
397
|
+
throw new Error(`Protected singleton upgrade target is ambiguous for "${packageName}".`);
|
|
398
|
+
}
|
|
399
|
+
const [targetVersion] = Array.from(targets);
|
|
400
|
+
const currentOverride = currentOverrides[packageName] ?? null;
|
|
401
|
+
return {
|
|
402
|
+
packageName,
|
|
403
|
+
currentOverride,
|
|
404
|
+
targetVersion: deriveProtectedOverrideTargetVersion(currentOverride, targetVersion),
|
|
405
|
+
upstreamHints: hints[packageName] ?? [],
|
|
406
|
+
};
|
|
407
|
+
})
|
|
408
|
+
.sort((left, right) => left.packageName.localeCompare(right.packageName));
|
|
409
|
+
}
|
|
410
|
+
async function getReleaseDate(runtime, packageName, version) {
|
|
411
|
+
const command = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
412
|
+
const result = await runCommandBuffered({ command, args: ['view', packageName, 'time', '--json'] }, runtime.cwd);
|
|
413
|
+
if (result.code !== 0 || !result.output.trim()) {
|
|
414
|
+
throw new Error(`Unable to read npm release metadata for "${packageName}".`);
|
|
415
|
+
}
|
|
416
|
+
try {
|
|
417
|
+
const times = parseJsonObjectFromCommandOutput(result.output);
|
|
418
|
+
const releaseTime = times[version];
|
|
419
|
+
if (typeof releaseTime !== 'string')
|
|
420
|
+
return null;
|
|
421
|
+
if (!releaseTime)
|
|
422
|
+
return null;
|
|
423
|
+
const releaseDate = new Date(releaseTime);
|
|
424
|
+
return Number.isNaN(releaseDate.getTime()) ? null : releaseDate;
|
|
425
|
+
}
|
|
426
|
+
catch {
|
|
427
|
+
throw new Error(`Invalid npm release metadata for "${packageName}".`);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
async function getCooldownRejectList(runtime, days, target) {
|
|
431
|
+
if (days <= 0)
|
|
432
|
+
return [];
|
|
433
|
+
try {
|
|
434
|
+
console.info(colorize(`Checking release age for ${target} updates (${days}-day cooldown)...`, colors.cyan));
|
|
435
|
+
const candidatesByFile = await collectUpgradeCandidates(runtime, target, [], true);
|
|
436
|
+
const allCandidates = new Map();
|
|
437
|
+
for (const fileUpdates of Object.values(candidatesByFile)) {
|
|
438
|
+
for (const [packageName, targetVersion] of Object.entries(fileUpdates)) {
|
|
439
|
+
allCandidates.set(packageName, targetVersion);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
const rejectList = [];
|
|
443
|
+
let processed = 0;
|
|
444
|
+
const candidates = Array.from(allCandidates.entries());
|
|
445
|
+
for (const [packageName, targetVersion] of candidates) {
|
|
446
|
+
const releaseDate = await getReleaseDate(runtime, packageName, normalizeVersionSpec(targetVersion));
|
|
447
|
+
processed += 1;
|
|
448
|
+
process.stdout.write(`\rCooldown ${processed}/${candidates.length}`);
|
|
449
|
+
if (!releaseDate) {
|
|
450
|
+
rejectList.push(packageName);
|
|
451
|
+
console.warn(colorize(`\nCooldown hold: ${packageName} has unknown release age.`, colors.yellow));
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
const ageInDays = (Date.now() - releaseDate.getTime()) / (1000 * 60 * 60 * 24);
|
|
455
|
+
if (ageInDays < days)
|
|
456
|
+
rejectList.push(packageName);
|
|
457
|
+
}
|
|
458
|
+
if (candidates.length > 0)
|
|
459
|
+
console.info('');
|
|
460
|
+
return rejectList.sort((left, right) => left.localeCompare(right));
|
|
461
|
+
}
|
|
462
|
+
catch (error) {
|
|
463
|
+
throw new Error(`Cooldown pre-check failed before manifests were changed. ${error.message} Rerun with --no-cooldown to bypass release-age checks explicitly.`);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
function formatEntryGroups(entries) {
|
|
467
|
+
const byFile = new Map();
|
|
468
|
+
for (const entry of entries) {
|
|
469
|
+
const list = byFile.get(entry.filePath) ?? [];
|
|
470
|
+
list.push(entry);
|
|
471
|
+
byFile.set(entry.filePath, list);
|
|
472
|
+
}
|
|
473
|
+
return Array.from(byFile.entries()).flatMap(([filePath, fileEntries]) => [
|
|
474
|
+
colorize(filePath, colors.cyan),
|
|
475
|
+
...fileEntries.map((entry) => ` ${entry.packageName}: ${entry.currentVersion ?? '?'} -> ${entry.targetVersion}`),
|
|
476
|
+
]);
|
|
477
|
+
}
|
|
478
|
+
async function applyWorkspaceUpgrades(runtime, target, rejectList = [], filterPackages = []) {
|
|
479
|
+
const args = ['exec', 'ncu', '--workspaces', '--root', '--color', '-u', '--target', target];
|
|
480
|
+
if (rejectList.length > 0)
|
|
481
|
+
args.push('--reject', rejectList.join(','));
|
|
482
|
+
if (filterPackages.length > 0)
|
|
483
|
+
args.push('--filter', filterPackages.join(','));
|
|
484
|
+
runInheritedPm(runtime, args);
|
|
485
|
+
}
|
|
486
|
+
async function installDependencies(runtime, useFreshPackageManager) {
|
|
487
|
+
try {
|
|
488
|
+
runInheritedPm(runtime, ['install'], useFreshPackageManager ? buildFreshPackageManagerCommand : buildPackageManagerCommand);
|
|
489
|
+
}
|
|
490
|
+
catch (error) {
|
|
491
|
+
throw new Error([
|
|
492
|
+
'Dependency install failed after manifest updates.',
|
|
493
|
+
'The package.json files may already be changed while the lockfile/install is incomplete.',
|
|
494
|
+
`Run ${formatCommand(runtime.config.packageManager, ['install'])} after fixing the package-manager environment.`,
|
|
495
|
+
error.message,
|
|
496
|
+
].join('\n'));
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
async function preparePackageManagerAfterManifestUpdates(runtime, previousPnpmVersion) {
|
|
500
|
+
const currentPnpmVersion = await readRootPnpmPackageManagerVersion(runtime.cwd);
|
|
501
|
+
if (!currentPnpmVersion || currentPnpmVersion === previousPnpmVersion)
|
|
502
|
+
return false;
|
|
503
|
+
console.info(colorize(`Preparing pnpm ${currentPnpmVersion} via Corepack...`, colors.cyan));
|
|
504
|
+
prepareCorepackPnpm(runtime, runtime.cwd, currentPnpmVersion);
|
|
505
|
+
return true;
|
|
506
|
+
}
|
|
507
|
+
function runConfiguredStep(runtime, step) {
|
|
508
|
+
if (!step.command) {
|
|
509
|
+
throw new Error(`Configured upgrade step "${step.label}" must define command.`);
|
|
510
|
+
}
|
|
511
|
+
console.info(colorize(step.label, colors.cyan));
|
|
512
|
+
const code = runCommandInherited({
|
|
513
|
+
command: step.command,
|
|
514
|
+
args: step.args ?? [],
|
|
515
|
+
cwd: step.cwd,
|
|
516
|
+
env: step.env,
|
|
517
|
+
}, runtime.cwd);
|
|
518
|
+
if (code !== 0)
|
|
519
|
+
process.exit(code);
|
|
520
|
+
}
|
|
521
|
+
function parseCliArgs(runtime, rawArgs) {
|
|
522
|
+
const verbose = rawArgs.includes('--verbose');
|
|
523
|
+
const daysArg = rawArgs.find((arg) => arg.startsWith('--days='));
|
|
524
|
+
const defaultDays = runtime.config.upgrade?.defaultCooldownDays ?? 7;
|
|
525
|
+
const days = daysArg
|
|
526
|
+
? Number.parseInt(daysArg.slice('--days='.length), 10)
|
|
527
|
+
: rawArgs.includes('--no-cooldown') ? 0 : defaultDays;
|
|
528
|
+
return {
|
|
529
|
+
allowMajor: rawArgs.includes('--major') || rawArgs.includes('--latest'),
|
|
530
|
+
verbose,
|
|
531
|
+
alignProtectedSingletons: rawArgs.includes('--isolated') || rawArgs.includes('--align-protected-singletons'),
|
|
532
|
+
days: Number.isFinite(days) && days > 0 ? days : 0,
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
export function parseYesNo(answer, defaultValue) {
|
|
536
|
+
const normalized = answer.trim().toLowerCase();
|
|
537
|
+
if (!normalized)
|
|
538
|
+
return defaultValue;
|
|
539
|
+
if (['s', 'sim', 'y', 'yes'].includes(normalized))
|
|
540
|
+
return true;
|
|
541
|
+
if (['n', 'nao', 'não', 'no'].includes(normalized))
|
|
542
|
+
return false;
|
|
543
|
+
return null;
|
|
544
|
+
}
|
|
545
|
+
export function formatYesNoPrompt(icon, question, defaultValue) {
|
|
546
|
+
return `${icon} ${question} ${defaultValue ? '[Y/n]' : '[y/N]'} `;
|
|
547
|
+
}
|
|
548
|
+
async function promptYesNo(prompt, question, defaultValue) {
|
|
549
|
+
while (true) {
|
|
550
|
+
const parsed = parseYesNo(await prompt.question(question), defaultValue);
|
|
551
|
+
if (parsed !== null)
|
|
552
|
+
return parsed;
|
|
553
|
+
console.info(colorize('Invalid answer. Use Y or N.', colors.yellow));
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
async function resolveOptions(runtime, rawArgs) {
|
|
557
|
+
const cliOptions = parseCliArgs(runtime, rawArgs);
|
|
558
|
+
if (rawArgs.includes('--yes') || !input.isTTY || !output.isTTY)
|
|
559
|
+
return cliOptions;
|
|
560
|
+
console.info(colorize('Upgrade configuration', colors.bright));
|
|
561
|
+
const prompt = createInterface({ input, output });
|
|
562
|
+
try {
|
|
563
|
+
const cooldownEnabled = await promptYesNo(prompt, formatYesNoPrompt('❄', 'Cooldown enabled?', cliOptions.days > 0), cliOptions.days > 0);
|
|
564
|
+
const allowMajor = await promptYesNo(prompt, formatYesNoPrompt('↗', 'Major upgrades?', cliOptions.allowMajor), cliOptions.allowMajor);
|
|
565
|
+
const alignProtectedSingletons = await promptYesNo(prompt, formatYesNoPrompt('🔄', 'Protected singleton upgrades?', cliOptions.alignProtectedSingletons), cliOptions.alignProtectedSingletons);
|
|
566
|
+
return {
|
|
567
|
+
allowMajor,
|
|
568
|
+
verbose: cliOptions.verbose,
|
|
569
|
+
alignProtectedSingletons,
|
|
570
|
+
days: cooldownEnabled ? (cliOptions.days > 0 ? cliOptions.days : (runtime.config.upgrade?.defaultCooldownDays ?? 7)) : 0,
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
finally {
|
|
574
|
+
prompt.close();
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
export async function runUpgradeEngine(runtime, rawArgs) {
|
|
578
|
+
const options = await resolveOptions(runtime, rawArgs);
|
|
579
|
+
const target = options.allowMajor ? 'latest' : 'minor';
|
|
580
|
+
const protectedNames = getProtectedDependencyNames(runtime);
|
|
581
|
+
console.info(colorize('Checking eligible dependency updates...', colors.cyan));
|
|
582
|
+
const cooldownRejectList = await getCooldownRejectList(runtime, options.days, target);
|
|
583
|
+
const currentTargetCandidates = await collectUpgradeCandidates(runtime, target, [], true);
|
|
584
|
+
const cooldownHoldUpdates = filterWorkspaceUpdatesByPackageNames(currentTargetCandidates, new Set(cooldownRejectList));
|
|
585
|
+
const cooldownHoldVersionsByFile = await getManifestVersionsByFile(cooldownHoldUpdates, runtime.cwd);
|
|
586
|
+
const skippedEntriesByKey = new Map();
|
|
587
|
+
addSkippedEntries(skippedEntriesByKey, buildSkippedUpgradeEntries(cooldownHoldUpdates, 'cooldown', (filePath, packageName) => cooldownHoldVersionsByFile[filePath]?.[packageName] ?? null));
|
|
588
|
+
if (!options.allowMajor) {
|
|
589
|
+
const latestCandidates = await collectUpgradeCandidates(runtime, 'latest', [], true);
|
|
590
|
+
const majorHoldUpdates = subtractWorkspaceUpdates(latestCandidates, currentTargetCandidates);
|
|
591
|
+
const majorHoldVersionsByFile = await getManifestVersionsByFile(majorHoldUpdates, runtime.cwd);
|
|
592
|
+
addSkippedEntries(skippedEntriesByKey, buildSkippedUpgradeEntries(majorHoldUpdates, 'major', (filePath, packageName) => majorHoldVersionsByFile[filePath]?.[packageName] ?? null));
|
|
593
|
+
}
|
|
594
|
+
const upgradeCandidates = await collectUpgradeCandidates(runtime, target, cooldownRejectList);
|
|
595
|
+
const versionsByFile = await getManifestVersionsByFile(upgradeCandidates, runtime.cwd);
|
|
596
|
+
const updatedEntries = buildUpgradeEntries(upgradeCandidates, (filePath, packageName) => versionsByFile[filePath]?.[packageName] ?? null);
|
|
597
|
+
const protectedHoldUpdates = await collectProtectedHoldUpdates(runtime, target, cooldownRejectList);
|
|
598
|
+
const protectedHoldVersionsByFile = await getManifestVersionsByFile(protectedHoldUpdates, runtime.cwd);
|
|
599
|
+
let protectedHoldEntries = buildUpgradeEntries(protectedHoldUpdates, (filePath, packageName) => protectedHoldVersionsByFile[filePath]?.[packageName] ?? null);
|
|
600
|
+
let protectedUpgradedEntries = [];
|
|
601
|
+
if (updatedEntries.length > 0) {
|
|
602
|
+
console.info(colorize('Applying dependency updates...', colors.cyan));
|
|
603
|
+
const previousPnpmVersion = await readRootPnpmPackageManagerVersion(runtime.cwd);
|
|
604
|
+
await applyWorkspaceUpgrades(runtime, target, mergeRejectLists(cooldownRejectList, protectedNames));
|
|
605
|
+
const useFreshPackageManager = await preparePackageManagerAfterManifestUpdates(runtime, previousPnpmVersion);
|
|
606
|
+
console.info(colorize('Installing dependencies...', colors.cyan));
|
|
607
|
+
await installDependencies(runtime, useFreshPackageManager);
|
|
608
|
+
}
|
|
609
|
+
if (options.alignProtectedSingletons && protectedHoldEntries.length > 0) {
|
|
610
|
+
const plans = buildProtectedUpgradePlans(runtime, protectedHoldEntries);
|
|
611
|
+
const selectedPackages = plans.map((plan) => plan.packageName);
|
|
612
|
+
const overrideUpdates = Object.fromEntries(plans.map((plan) => [plan.packageName, plan.targetVersion]));
|
|
613
|
+
console.info(colorize('Applying protected singleton upgrades...', colors.cyan));
|
|
614
|
+
const previousPnpmVersion = await readRootPnpmPackageManagerVersion(runtime.cwd);
|
|
615
|
+
await applyWorkspaceUpgrades(runtime, target, cooldownRejectList, selectedPackages);
|
|
616
|
+
console.info(colorize('Updating protected dependency overrides...', colors.cyan));
|
|
617
|
+
await updateProtectedOverrides(runtime.cwd, runtime.config.upgrade?.protectedOverridesFile ?? 'pnpm-workspace.yaml', overrideUpdates);
|
|
618
|
+
const useFreshPackageManager = await preparePackageManagerAfterManifestUpdates(runtime, previousPnpmVersion);
|
|
619
|
+
console.info(colorize('Installing dependencies after protected singleton upgrades...', colors.cyan));
|
|
620
|
+
await installDependencies(runtime, useFreshPackageManager);
|
|
621
|
+
if (runtime.config.upgrade?.singletonGuardCommand) {
|
|
622
|
+
runConfiguredStep(runtime, runtime.config.upgrade.singletonGuardCommand);
|
|
623
|
+
}
|
|
624
|
+
const selected = new Set(selectedPackages);
|
|
625
|
+
protectedUpgradedEntries = protectedHoldEntries.filter((entry) => selected.has(entry.packageName));
|
|
626
|
+
protectedHoldEntries = protectedHoldEntries.filter((entry) => !selected.has(entry.packageName));
|
|
627
|
+
}
|
|
628
|
+
addSkippedEntries(skippedEntriesByKey, protectedHoldEntries.map((entry) => ({
|
|
629
|
+
...entry,
|
|
630
|
+
reason: 'protected-singleton',
|
|
631
|
+
})));
|
|
632
|
+
console.info('');
|
|
633
|
+
console.info(colorize('Upgrade complete', `${colors.bright}${colors.green}`));
|
|
634
|
+
console.info('');
|
|
635
|
+
console.info(colorize('Updated', colors.bright));
|
|
636
|
+
if (updatedEntries.length === 0) {
|
|
637
|
+
console.info('- No eligible dependency updates.');
|
|
638
|
+
}
|
|
639
|
+
else {
|
|
640
|
+
for (const line of formatEntryGroups(updatedEntries))
|
|
641
|
+
console.info(`- ${line}`);
|
|
642
|
+
}
|
|
643
|
+
const skippedEntries = Array.from(skippedEntriesByKey.values());
|
|
644
|
+
if (skippedEntries.length > 0) {
|
|
645
|
+
const hints = runtime.config.upgrade?.protectedDependencyUpstreamHints ?? {};
|
|
646
|
+
console.info('');
|
|
647
|
+
console.info(colorize('Not updated', colors.bright));
|
|
648
|
+
for (const reason of skippedReasonOrder) {
|
|
649
|
+
const reasonEntries = skippedEntries.filter((entry) => entry.reason === reason);
|
|
650
|
+
if (reasonEntries.length === 0)
|
|
651
|
+
continue;
|
|
652
|
+
console.info(`- ${colorize(skippedReasonLabels[reason], colors.yellow)}`);
|
|
653
|
+
for (const line of formatEntryGroups(reasonEntries))
|
|
654
|
+
console.info(` ${line}`);
|
|
655
|
+
if (reason !== 'protected-singleton')
|
|
656
|
+
continue;
|
|
657
|
+
for (const packageName of uniqueSorted(reasonEntries.map((entry) => entry.packageName))) {
|
|
658
|
+
const upstreamPackages = hints[packageName] ?? [];
|
|
659
|
+
if (upstreamPackages.length > 0) {
|
|
660
|
+
console.info(` ${colorize(packageName, colors.cyan)}: review/update ${colorize(upstreamPackages.join(', '), colors.bright)} before upgrading.`);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
if (protectedUpgradedEntries.length > 0) {
|
|
666
|
+
console.info('');
|
|
667
|
+
console.info(colorize('Protected singleton upgrades', colors.bright));
|
|
668
|
+
for (const line of formatEntryGroups(protectedUpgradedEntries))
|
|
669
|
+
console.info(`- ${line}`);
|
|
670
|
+
}
|
|
671
|
+
if (options.verbose) {
|
|
672
|
+
console.info(colorize(`Executed with target=${target}.`, colors.gray));
|
|
673
|
+
}
|
|
674
|
+
}
|