pnpm-settings-migrator 0.9.0 → 0.10.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 +38 -3
- package/dist/cli.mjs +331 -291
- package/dist/index.d.mts +68 -19
- package/dist/index.mjs +159 -313
- package/package.json +10 -10
package/dist/cli.mjs
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import process from "node:process";
|
|
2
|
-
import consola$1, { consola } from "consola";
|
|
3
2
|
import { cac } from "cac";
|
|
4
|
-
import { basename, dirname, join, relative, resolve } from "pathe";
|
|
5
|
-
import camelcaseKeys from "camelcase-keys";
|
|
6
3
|
import { randomUUID } from "node:crypto";
|
|
7
4
|
import { access, chmod, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
8
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { basename, dirname, join, relative, resolve } from "pathe";
|
|
9
7
|
import { getColor } from "consola/utils";
|
|
10
8
|
import { isPlainObject, isUndefined } from "@ntnyq/utils";
|
|
11
9
|
import { defu } from "defu";
|
|
10
|
+
import camelcaseKeys from "camelcase-keys";
|
|
12
11
|
import { readIniFile } from "read-ini-file";
|
|
13
12
|
import { kebabCase } from "uncase";
|
|
14
13
|
import { glob } from "tinyglobby";
|
|
@@ -17,7 +16,7 @@ import { Document, isMap, isNode, isScalar, isSeq, parseDocument, stringify } fr
|
|
|
17
16
|
import { isDeepStrictEqual } from "node:util";
|
|
18
17
|
//#region package.json
|
|
19
18
|
var name = "pnpm-settings-migrator";
|
|
20
|
-
var version = "0.
|
|
19
|
+
var version = "0.10.0";
|
|
21
20
|
//#endregion
|
|
22
21
|
//#region src/cli-options.ts
|
|
23
22
|
/**
|
|
@@ -26,7 +25,7 @@ var version = "0.9.0";
|
|
|
26
25
|
* @returns Configured command-line parser
|
|
27
26
|
*/
|
|
28
27
|
function createCli() {
|
|
29
|
-
const cli = cac(name).version(version).option("--cwd <cwd>", "Current working directory").option("--sort-keys", "Sort keys when write pnpm-workspace.yaml").option("--compatibility <compatibility>", "Compatibility target (auto, v10, v11, v12)").option("--replace-deprecated", "Replace deprecated pnpm settings with new ones and remove old keys").option("--strategy <strategy>", "Strategy to handle conflicts (discard, merge, overwrite)").option("--no-yarn-resolutions", "Disable migrating resolutions field in package.json").option("--no-newline-between", "Disable adding newlines between each root keys").option("--no-show-changes", "
|
|
28
|
+
const cli = cac(name).version(version).option("--cwd <cwd>", "Current working directory").option("--sort-keys", "Sort keys when write pnpm-workspace.yaml").option("--compatibility <compatibility>", "Compatibility target (auto, v10, v11, v12)").option("--replace-deprecated", "Replace deprecated pnpm settings with new ones and remove old keys").option("--strategy <strategy>", "Strategy to handle conflicts (discard, merge, overwrite)").option("--no-yarn-resolutions", "Disable migrating resolutions field in package.json").option("--no-newline-between", "Disable adding newlines between each root keys").option("--no-show-changes", "Hide settings diff (keep summary and warnings)").option("--no-clean-npmrc", "Disable removing pnpm settings in .npmrc file").option("--no-clean-package-json", "Disable removing pnpm fields in package.json");
|
|
30
29
|
cli.help((sections) => {
|
|
31
30
|
if (!(cli.commands.length > 0 && cli.commands.every((command) => command.isDefaultCommand))) return sections;
|
|
32
31
|
return sections.filter((section) => section.title !== "Commands" && !section.title?.startsWith("For more info"));
|
|
@@ -34,6 +33,139 @@ function createCli() {
|
|
|
34
33
|
return cli;
|
|
35
34
|
}
|
|
36
35
|
//#endregion
|
|
36
|
+
//#region src/utils/fs.ts
|
|
37
|
+
/**
|
|
38
|
+
* Modulus that isolates Unix permission bits from a stat mode.
|
|
39
|
+
*/
|
|
40
|
+
const FILE_PERMISSION_MODULUS = 512;
|
|
41
|
+
/**
|
|
42
|
+
* Resolve a filesystem path to a string suitable for a sibling temp file.
|
|
43
|
+
*
|
|
44
|
+
* @param path - Filesystem path to normalize
|
|
45
|
+
*
|
|
46
|
+
* @returns String path suitable for path manipulation
|
|
47
|
+
*/
|
|
48
|
+
function resolvePathString(path) {
|
|
49
|
+
if (path instanceof URL) return fileURLToPath(path);
|
|
50
|
+
return typeof path === "string" ? path : path.toString();
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Read an existing file's permission bits, if the file exists.
|
|
54
|
+
*
|
|
55
|
+
* @param path - Filesystem path to inspect
|
|
56
|
+
*
|
|
57
|
+
* @returns Unix permission bits, or `undefined` when the file does not exist
|
|
58
|
+
*/
|
|
59
|
+
async function resolveExistingMode(path) {
|
|
60
|
+
try {
|
|
61
|
+
return (await stat(path)).mode % FILE_PERMISSION_MODULUS;
|
|
62
|
+
} catch (error) {
|
|
63
|
+
if (error.code === "ENOENT") return;
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Check whether a filesystem path exists.
|
|
69
|
+
*
|
|
70
|
+
* @param path - Filesystem path to check
|
|
71
|
+
*
|
|
72
|
+
* @returns `true` when the path is accessible, otherwise `false`
|
|
73
|
+
*/
|
|
74
|
+
async function fsExists(path) {
|
|
75
|
+
return access(path).then(() => true).catch(() => false);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Read a UTF-8 text file.
|
|
79
|
+
*
|
|
80
|
+
* @param path - Filesystem path to read
|
|
81
|
+
*
|
|
82
|
+
* @returns Text content of the file
|
|
83
|
+
*/
|
|
84
|
+
async function fsReadFile(path) {
|
|
85
|
+
return await readFile(path, "utf-8");
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Remove a file if it exists.
|
|
89
|
+
*
|
|
90
|
+
* @param path - Filesystem path to remove
|
|
91
|
+
*
|
|
92
|
+
* @returns A promise that resolves after the file is removed
|
|
93
|
+
*/
|
|
94
|
+
async function fsRemoveFile(path) {
|
|
95
|
+
await rm(path, { force: true });
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Write a normalized UTF-8 text file with one trailing newline.
|
|
99
|
+
*
|
|
100
|
+
* @param path - Filesystem path to write
|
|
101
|
+
* @param content - Text content to write
|
|
102
|
+
*
|
|
103
|
+
* @returns A promise that resolves after the file is written
|
|
104
|
+
*/
|
|
105
|
+
async function fsWriteFile(path, content) {
|
|
106
|
+
const pathString = resolvePathString(path);
|
|
107
|
+
const temporaryPath = join(dirname(pathString), `.${basename(pathString)}.${process.pid}.${randomUUID()}.tmp`);
|
|
108
|
+
const mode = await resolveExistingMode(path);
|
|
109
|
+
try {
|
|
110
|
+
await writeFile(temporaryPath, `${content.trimEnd()}\n`, {
|
|
111
|
+
encoding: "utf-8",
|
|
112
|
+
mode
|
|
113
|
+
});
|
|
114
|
+
if (mode !== void 0) await chmod(temporaryPath, mode);
|
|
115
|
+
await rename(temporaryPath, pathString);
|
|
116
|
+
} finally {
|
|
117
|
+
await rm(temporaryPath, { force: true });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Write a normalized text file only when its contents differ or it is missing.
|
|
122
|
+
*
|
|
123
|
+
* @param path - Destination file path
|
|
124
|
+
* @param content - Text content to normalize and write
|
|
125
|
+
*
|
|
126
|
+
* @returns Whether the destination file was created or changed
|
|
127
|
+
*/
|
|
128
|
+
async function fsWriteFileIfChanged(path, content) {
|
|
129
|
+
if (await fsExists(path) && await fsReadFile(path) === `${content.trimEnd()}\n`) return false;
|
|
130
|
+
await fsWriteFile(path, content);
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
//#endregion
|
|
134
|
+
//#region src/utils/color.ts
|
|
135
|
+
/**
|
|
136
|
+
* Apply dim terminal styling to a value.
|
|
137
|
+
*
|
|
138
|
+
* @param text - Text or number to style
|
|
139
|
+
*
|
|
140
|
+
* @returns ANSI-styled string
|
|
141
|
+
*/
|
|
142
|
+
const dim = getColor("dim");
|
|
143
|
+
/**
|
|
144
|
+
* Apply green terminal styling to a value.
|
|
145
|
+
*
|
|
146
|
+
* @param text - Text or number to style
|
|
147
|
+
*
|
|
148
|
+
* @returns ANSI-styled string
|
|
149
|
+
*/
|
|
150
|
+
const green = getColor("green");
|
|
151
|
+
/**
|
|
152
|
+
* Apply red terminal styling to a value.
|
|
153
|
+
*
|
|
154
|
+
* @param text - Text or number to style
|
|
155
|
+
*
|
|
156
|
+
* @returns ANSI-styled string
|
|
157
|
+
*/
|
|
158
|
+
const red = getColor("red");
|
|
159
|
+
/**
|
|
160
|
+
* Apply bold terminal styling to a value.
|
|
161
|
+
*
|
|
162
|
+
* @param text - Text or number to style
|
|
163
|
+
*
|
|
164
|
+
* @returns ANSI-styled string
|
|
165
|
+
*/
|
|
166
|
+
const bold = getColor("bold");
|
|
167
|
+
getColor("magenta");
|
|
168
|
+
//#endregion
|
|
37
169
|
//#region src/constants.ts
|
|
38
170
|
const NPMRC = ".npmrc";
|
|
39
171
|
const PACKAGE_JSON = "package.json";
|
|
@@ -205,133 +337,6 @@ const PNPM_V11_REMOVED_SETTINGS = [
|
|
|
205
337
|
"useNodeVersion"
|
|
206
338
|
];
|
|
207
339
|
//#endregion
|
|
208
|
-
//#region src/utils/fs.ts
|
|
209
|
-
/**
|
|
210
|
-
* Modulus that isolates Unix permission bits from a stat mode.
|
|
211
|
-
*/
|
|
212
|
-
const FILE_PERMISSION_MODULUS = 512;
|
|
213
|
-
/**
|
|
214
|
-
* Resolve a filesystem path to a string suitable for a sibling temp file.
|
|
215
|
-
*
|
|
216
|
-
* @param path - Filesystem path to normalize
|
|
217
|
-
*
|
|
218
|
-
* @returns String path suitable for path manipulation
|
|
219
|
-
*/
|
|
220
|
-
function resolvePathString(path) {
|
|
221
|
-
if (path instanceof URL) return fileURLToPath(path);
|
|
222
|
-
return typeof path === "string" ? path : path.toString();
|
|
223
|
-
}
|
|
224
|
-
/**
|
|
225
|
-
* Read an existing file's permission bits, if the file exists.
|
|
226
|
-
*
|
|
227
|
-
* @param path - Filesystem path to inspect
|
|
228
|
-
*
|
|
229
|
-
* @returns Unix permission bits, or `undefined` when the file does not exist
|
|
230
|
-
*/
|
|
231
|
-
async function resolveExistingMode(path) {
|
|
232
|
-
try {
|
|
233
|
-
return (await stat(path)).mode % FILE_PERMISSION_MODULUS;
|
|
234
|
-
} catch (error) {
|
|
235
|
-
if (error.code === "ENOENT") return;
|
|
236
|
-
throw error;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
/**
|
|
240
|
-
* Check whether a filesystem path exists.
|
|
241
|
-
*
|
|
242
|
-
* @param path - Filesystem path to check
|
|
243
|
-
*
|
|
244
|
-
* @returns `true` when the path is accessible, otherwise `false`
|
|
245
|
-
*/
|
|
246
|
-
async function fsExists(path) {
|
|
247
|
-
return access(path).then(() => true).catch(() => false);
|
|
248
|
-
}
|
|
249
|
-
/**
|
|
250
|
-
* Read a UTF-8 text file.
|
|
251
|
-
*
|
|
252
|
-
* @param path - Filesystem path to read
|
|
253
|
-
*
|
|
254
|
-
* @returns Text content of the file
|
|
255
|
-
*/
|
|
256
|
-
async function fsReadFile(path) {
|
|
257
|
-
return await readFile(path, "utf-8");
|
|
258
|
-
}
|
|
259
|
-
/**
|
|
260
|
-
* Remove a file if it exists.
|
|
261
|
-
*
|
|
262
|
-
* @param path - Filesystem path to remove
|
|
263
|
-
*
|
|
264
|
-
* @returns A promise that resolves after the file is removed
|
|
265
|
-
*/
|
|
266
|
-
async function fsRemoveFile(path) {
|
|
267
|
-
await rm(path, { force: true });
|
|
268
|
-
}
|
|
269
|
-
/**
|
|
270
|
-
* Write a normalized UTF-8 text file with one trailing newline.
|
|
271
|
-
*
|
|
272
|
-
* @param path - Filesystem path to write
|
|
273
|
-
* @param content - Text content to write
|
|
274
|
-
*
|
|
275
|
-
* @returns A promise that resolves after the file is written
|
|
276
|
-
*/
|
|
277
|
-
async function fsWriteFile(path, content) {
|
|
278
|
-
const pathString = resolvePathString(path);
|
|
279
|
-
const temporaryPath = join(dirname(pathString), `.${basename(pathString)}.${process.pid}.${randomUUID()}.tmp`);
|
|
280
|
-
const mode = await resolveExistingMode(path);
|
|
281
|
-
try {
|
|
282
|
-
await writeFile(temporaryPath, `${content.trimEnd()}\n`, {
|
|
283
|
-
encoding: "utf-8",
|
|
284
|
-
mode
|
|
285
|
-
});
|
|
286
|
-
if (mode !== void 0) await chmod(temporaryPath, mode);
|
|
287
|
-
await rename(temporaryPath, pathString);
|
|
288
|
-
} finally {
|
|
289
|
-
await rm(temporaryPath, { force: true });
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
//#endregion
|
|
293
|
-
//#region src/utils/color.ts
|
|
294
|
-
/**
|
|
295
|
-
* Apply dim terminal styling to a value.
|
|
296
|
-
*
|
|
297
|
-
* @param text - Text or number to style
|
|
298
|
-
*
|
|
299
|
-
* @returns ANSI-styled string
|
|
300
|
-
*/
|
|
301
|
-
const dim = getColor("dim");
|
|
302
|
-
/**
|
|
303
|
-
* Apply green terminal styling to a value.
|
|
304
|
-
*
|
|
305
|
-
* @param text - Text or number to style
|
|
306
|
-
*
|
|
307
|
-
* @returns ANSI-styled string
|
|
308
|
-
*/
|
|
309
|
-
const green = getColor("green");
|
|
310
|
-
/**
|
|
311
|
-
* Apply red terminal styling to a value.
|
|
312
|
-
*
|
|
313
|
-
* @param text - Text or number to style
|
|
314
|
-
*
|
|
315
|
-
* @returns ANSI-styled string
|
|
316
|
-
*/
|
|
317
|
-
const red = getColor("red");
|
|
318
|
-
/**
|
|
319
|
-
* Apply bold terminal styling to a value.
|
|
320
|
-
*
|
|
321
|
-
* @param text - Text or number to style
|
|
322
|
-
*
|
|
323
|
-
* @returns ANSI-styled string
|
|
324
|
-
*/
|
|
325
|
-
const bold = getColor("bold");
|
|
326
|
-
/**
|
|
327
|
-
* Apply magenta terminal styling to a value.
|
|
328
|
-
*
|
|
329
|
-
* @param text - Text or number to style
|
|
330
|
-
*
|
|
331
|
-
* @returns ANSI-styled string
|
|
332
|
-
*/
|
|
333
|
-
const magenta = getColor("magenta");
|
|
334
|
-
//#endregion
|
|
335
340
|
//#region src/utils/deprecated-settings.ts
|
|
336
341
|
/**
|
|
337
342
|
* Replace deprecated audit fields with the structured `audit` setting.
|
|
@@ -1408,6 +1413,29 @@ function resolvePackageJsonSettings(packageJson, yarnResolutions, compatibility)
|
|
|
1408
1413
|
yarnResolutions: migrateYarnResolutions
|
|
1409
1414
|
};
|
|
1410
1415
|
}
|
|
1416
|
+
/**
|
|
1417
|
+
* Remove only legacy package settings already applied to their destination.
|
|
1418
|
+
*
|
|
1419
|
+
* @param options - Parsed manifest, applied source keys, and resolutions status
|
|
1420
|
+
*
|
|
1421
|
+
* @returns Whether legacy settings were removed from the manifest
|
|
1422
|
+
*/
|
|
1423
|
+
function cleanPackageJsonSettings(options) {
|
|
1424
|
+
const { migratedKeys, packageJson, settings, yarnResolutionsApplied } = options;
|
|
1425
|
+
let changed = false;
|
|
1426
|
+
if (packageJson.value.pnpm) {
|
|
1427
|
+
for (const key of migratedKeys) {
|
|
1428
|
+
Reflect.deleteProperty(packageJson.value.pnpm, key);
|
|
1429
|
+
changed = true;
|
|
1430
|
+
}
|
|
1431
|
+
if (!Object.keys(packageJson.value.pnpm).length) delete packageJson.value.pnpm;
|
|
1432
|
+
}
|
|
1433
|
+
if (settings.yarnResolutions && yarnResolutionsApplied) {
|
|
1434
|
+
changed = true;
|
|
1435
|
+
delete packageJson.value.resolutions;
|
|
1436
|
+
}
|
|
1437
|
+
return changed;
|
|
1438
|
+
}
|
|
1411
1439
|
//#endregion
|
|
1412
1440
|
//#region src/utils/project-npmrc.ts
|
|
1413
1441
|
function resolvePackageJsonPatterns(patterns) {
|
|
@@ -1668,20 +1696,18 @@ function createSettingsDiffLines(change) {
|
|
|
1668
1696
|
}));
|
|
1669
1697
|
}
|
|
1670
1698
|
/**
|
|
1671
|
-
*
|
|
1699
|
+
* Format a colored YAML diff, with one blank line between changed settings.
|
|
1672
1700
|
*
|
|
1673
|
-
* @param changes - Root setting changes to
|
|
1701
|
+
* @param changes - Root setting changes to format
|
|
1674
1702
|
*
|
|
1675
|
-
* @returns
|
|
1703
|
+
* @returns Diff text without leading or trailing blank lines
|
|
1676
1704
|
*/
|
|
1677
|
-
function
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
if (
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
else consola$1.log(dim(` ${line.value}`));
|
|
1684
|
-
}
|
|
1705
|
+
function formatSettingsChanges(changes) {
|
|
1706
|
+
return changes.map((change) => createSettingsDiffLines(change).map((line) => {
|
|
1707
|
+
if (line.kind === "added") return green(`+ ${line.value}`);
|
|
1708
|
+
if (line.kind === "removed") return red(`- ${line.value}`);
|
|
1709
|
+
return dim(` ${line.value}`);
|
|
1710
|
+
}).join("\n")).join("\n\n");
|
|
1685
1711
|
}
|
|
1686
1712
|
//#endregion
|
|
1687
1713
|
//#region src/utils/settings-issue-report.ts
|
|
@@ -1689,23 +1715,25 @@ function formatIssueKeys(keys) {
|
|
|
1689
1715
|
return keys.map((key) => JSON.stringify(key)).join(", ");
|
|
1690
1716
|
}
|
|
1691
1717
|
/**
|
|
1692
|
-
*
|
|
1718
|
+
* Format warnings for settings that were intentionally left in their source.
|
|
1693
1719
|
*
|
|
1694
1720
|
* @param options - Retained settings and destination context
|
|
1695
1721
|
*
|
|
1696
|
-
* @returns
|
|
1722
|
+
* @returns Warning messages in issue-category order
|
|
1697
1723
|
*/
|
|
1698
|
-
function
|
|
1724
|
+
function formatSettingsIssues(options) {
|
|
1699
1725
|
const { compatibility, issues, projectConfig = false, source } = options;
|
|
1700
|
-
|
|
1701
|
-
if (issues.
|
|
1702
|
-
if (issues.
|
|
1703
|
-
if (issues.
|
|
1726
|
+
const warnings = [];
|
|
1727
|
+
if (issues.refused.length) warnings.push(`Kept project-refused settings in ${source}: ${formatIssueKeys(issues.refused)}. Configure machine settings globally and current-run paths on the command line.`);
|
|
1728
|
+
if (issues.incompatible.length) warnings.push(`Kept settings in ${source} that are incompatible with pnpm ${compatibility.slice(1)}: ${formatIssueKeys(issues.incompatible)}.`);
|
|
1729
|
+
if (issues.nonCamelCase.length) warnings.push(`Kept non-camelCase settings in ${source}: ${formatIssueKeys(issues.nonCamelCase)}. Workspace manifest keys must use camelCase.`);
|
|
1730
|
+
if (issues.unknown.length) warnings.push(`Kept settings in ${source} that pnpm ${compatibility.slice(1)} does not recognize: ${formatIssueKeys(issues.unknown)}.`);
|
|
1704
1731
|
if (issues.unsupported.length) {
|
|
1705
1732
|
const destination = projectConfig && compatibility === "v12" ? "pnpm v12 does not support packageConfigs" : "packageConfigs only accepts hoist, modulesDir, overrides, saveExact, and savePrefix";
|
|
1706
|
-
|
|
1733
|
+
warnings.push(`Kept subproject settings in ${source}: ${formatIssueKeys(issues.unsupported)}; ${destination}.`);
|
|
1707
1734
|
}
|
|
1708
|
-
if (issues.unsafe.length)
|
|
1735
|
+
if (issues.unsafe.length) warnings.push(`Kept unsafe registry settings in ${source}: ${formatIssueKeys(issues.unsafe)}. Remove credentials and dynamic URL interpolation before migrating them.`);
|
|
1736
|
+
return warnings;
|
|
1709
1737
|
}
|
|
1710
1738
|
//#endregion
|
|
1711
1739
|
//#region src/utils/yaml-document.ts
|
|
@@ -1753,6 +1781,32 @@ function formatRootSpacing(content, newlineBetween) {
|
|
|
1753
1781
|
return content.replace(/\n+(?=[^\s#][^:\n]*:)/gu, newlineBetween ? "\n\n" : "\n");
|
|
1754
1782
|
}
|
|
1755
1783
|
//#endregion
|
|
1784
|
+
//#region src/cli-output.ts
|
|
1785
|
+
function formatMigrationSummary(result) {
|
|
1786
|
+
if (!result.hasConfigurationFiles) return "ℹ No configuration files found.";
|
|
1787
|
+
const count = result.settingsChanges.length;
|
|
1788
|
+
if (count) return `${green("✔")} ${count} ${count === 1 ? "setting" : "settings"} changed`;
|
|
1789
|
+
if (result.packageJsonRuntimeChanged) return `${green("✔")} Migration completed. Node.js runtime updated in package.json.`;
|
|
1790
|
+
if (result.sourceSettingsCleaned) return `${green("✔")} Migration completed. Source settings cleaned up.`;
|
|
1791
|
+
if (result.changedFiles.length) return `${green("✔")} Migration completed. Configuration files updated.`;
|
|
1792
|
+
return "ℹ No changes needed.";
|
|
1793
|
+
}
|
|
1794
|
+
/**
|
|
1795
|
+
* Format warnings, one outcome summary, and optional settings details.
|
|
1796
|
+
*
|
|
1797
|
+
* @param result - Completed migration outcome
|
|
1798
|
+
* @param showChanges - Whether to include the settings diff
|
|
1799
|
+
*
|
|
1800
|
+
* @returns Terminal output with one blank line between blocks and none at its edges
|
|
1801
|
+
*/
|
|
1802
|
+
function formatMigrationOutput(result, showChanges = true) {
|
|
1803
|
+
const blocks = [];
|
|
1804
|
+
if (result.warnings.length) blocks.push(result.warnings.map((warning) => `${bold("WARN")} ${warning}`).join("\n"));
|
|
1805
|
+
blocks.push(formatMigrationSummary(result));
|
|
1806
|
+
if (showChanges && result.settingsChanges.length) blocks.push(formatSettingsChanges(result.settingsChanges));
|
|
1807
|
+
return blocks.join("\n\n");
|
|
1808
|
+
}
|
|
1809
|
+
//#endregion
|
|
1756
1810
|
//#region src/migration-persistence.ts
|
|
1757
1811
|
const SETTINGS_WITHOUT_REPLACEMENT = /* @__PURE__ */ new Set(["ignoreDepScripts", "ignorePatchFailures"]);
|
|
1758
1812
|
const REPLACEMENT_SETTING_KEYS = {
|
|
@@ -1773,21 +1827,6 @@ const REPLACEMENT_SETTING_KEYS = {
|
|
|
1773
1827
|
sideEffectsCacheReadonly: "sideEffectsCache",
|
|
1774
1828
|
updateConfig: "update"
|
|
1775
1829
|
};
|
|
1776
|
-
function cleanPackageJsonSettings({ migratedKeys, packageJson, settings, yarnResolutionsApplied }) {
|
|
1777
|
-
let changed = false;
|
|
1778
|
-
if (packageJson.value.pnpm) {
|
|
1779
|
-
for (const key of migratedKeys) {
|
|
1780
|
-
Reflect.deleteProperty(packageJson.value.pnpm, key);
|
|
1781
|
-
changed = true;
|
|
1782
|
-
}
|
|
1783
|
-
if (!Object.keys(packageJson.value.pnpm).length) delete packageJson.value.pnpm;
|
|
1784
|
-
}
|
|
1785
|
-
if (settings.yarnResolutions && yarnResolutionsApplied) {
|
|
1786
|
-
changed = true;
|
|
1787
|
-
delete packageJson.value.resolutions;
|
|
1788
|
-
}
|
|
1789
|
-
return changed;
|
|
1790
|
-
}
|
|
1791
1830
|
function containsMigratedValue(actual, expected) {
|
|
1792
1831
|
if (Array.isArray(actual) && Array.isArray(expected)) return expected.every((expectedItem) => actual.some((actualItem) => containsMigratedValue(actualItem, expectedItem)));
|
|
1793
1832
|
if (actual && expected && typeof actual === "object" && typeof expected === "object") return Object.entries(expected).every(([key, expectedValue]) => containsMigratedValue(actual[key], expectedValue));
|
|
@@ -1838,7 +1877,7 @@ function selectAppliedProjectKeys(project, finalSettings) {
|
|
|
1838
1877
|
*
|
|
1839
1878
|
* @param options - Destination files, selected sources, and cleanup policy
|
|
1840
1879
|
*
|
|
1841
|
-
* @returns
|
|
1880
|
+
* @returns Changed file paths and applied cleanup/runtime changes
|
|
1842
1881
|
*/
|
|
1843
1882
|
async function persistMigration(options) {
|
|
1844
1883
|
const { cleanNpmrc, cleanPackageJson, compatibility, finalSettings, incomingSettings, npmrc, npmrcExists, npmrcPath, packageJson, packageJsonExists, packageJsonPath, packageJsonRuntimeChanged, packageJsonSettings, pnpmWorkspaceContent, pnpmWorkspacePath, projectNpmrcs, runtimeVersion } = options;
|
|
@@ -1866,20 +1905,31 @@ async function persistMigration(options) {
|
|
|
1866
1905
|
settings: packageJsonSettings,
|
|
1867
1906
|
yarnResolutionsApplied
|
|
1868
1907
|
}) : false;
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1908
|
+
const result = {
|
|
1909
|
+
changedFiles: [],
|
|
1910
|
+
sourceSettingsCleaned: packageJsonSettingsChanged,
|
|
1911
|
+
packageJsonRuntimeChanged
|
|
1912
|
+
};
|
|
1913
|
+
if (await fsWriteFileIfChanged(pnpmWorkspacePath, pnpmWorkspaceContent)) result.changedFiles.push(pnpmWorkspacePath);
|
|
1914
|
+
if (packageJsonExists && (packageJsonRuntimeChanged || packageJsonSettingsChanged)) {
|
|
1915
|
+
await fsWriteFileIfChanged(packageJsonPath, JSON.stringify(packageJson.value, null, packageJson.indent));
|
|
1916
|
+
result.changedFiles.push(packageJsonPath);
|
|
1917
|
+
}
|
|
1918
|
+
if (!cleanNpmrc) return result;
|
|
1872
1919
|
const pruneTasks = projectNpmrcs.projects.flatMap((project) => {
|
|
1873
1920
|
const appliedKeys = selectAppliedProjectKeys(project, finalSettings);
|
|
1874
|
-
return appliedKeys.length ? [pruneNpmrc(project.npmrcPath, compatibility, appliedKeys)] : [];
|
|
1921
|
+
return appliedKeys.length ? [pruneNpmrc(project.npmrcPath, compatibility, appliedKeys).then(() => project.npmrcPath)] : [];
|
|
1875
1922
|
});
|
|
1876
|
-
if (npmrcExists && appliedNpmrcKeys.length) pruneTasks.push(pruneNpmrc(npmrcPath, compatibility, appliedNpmrcKeys));
|
|
1877
|
-
await Promise.all(pruneTasks);
|
|
1923
|
+
if (npmrcExists && appliedNpmrcKeys.length) pruneTasks.push(pruneNpmrc(npmrcPath, compatibility, appliedNpmrcKeys).then(() => npmrcPath));
|
|
1924
|
+
const cleanedNpmrcPaths = await Promise.all(pruneTasks);
|
|
1925
|
+
result.changedFiles.push(...cleanedNpmrcPaths);
|
|
1926
|
+
result.sourceSettingsCleaned ||= cleanedNpmrcPaths.length > 0;
|
|
1927
|
+
return result;
|
|
1878
1928
|
}
|
|
1879
1929
|
//#endregion
|
|
1880
1930
|
//#region src/migration-sources.ts
|
|
1881
1931
|
/**
|
|
1882
|
-
* Read, schema-filter,
|
|
1932
|
+
* Read, schema-filter, and merge legacy settings while collecting warnings.
|
|
1883
1933
|
*
|
|
1884
1934
|
* @param options - Migration target and source configuration
|
|
1885
1935
|
*
|
|
@@ -1887,32 +1937,34 @@ async function persistMigration(options) {
|
|
|
1887
1937
|
*/
|
|
1888
1938
|
async function resolveMigrationSources(options) {
|
|
1889
1939
|
const { compatibility, cwd, npmrcExists, npmrcPath, packageJson, pnpmWorkspace, strategy, yarnResolutions } = options;
|
|
1940
|
+
const warnings = [];
|
|
1890
1941
|
const npmrc = npmrcExists ? await readMigratableNpmrc(npmrcPath, compatibility) : {
|
|
1891
1942
|
issues: createSettingsIssues(),
|
|
1892
1943
|
keys: [],
|
|
1893
1944
|
settings: {}
|
|
1894
1945
|
};
|
|
1895
|
-
|
|
1946
|
+
warnings.push(...formatSettingsIssues({
|
|
1896
1947
|
compatibility,
|
|
1897
1948
|
issues: npmrc.issues,
|
|
1898
1949
|
source: NPMRC
|
|
1899
|
-
});
|
|
1950
|
+
}));
|
|
1900
1951
|
const packageJsonSettings = resolvePackageJsonSettings(packageJson, yarnResolutions, compatibility);
|
|
1901
|
-
|
|
1952
|
+
warnings.push(...formatSettingsIssues({
|
|
1902
1953
|
compatibility,
|
|
1903
1954
|
issues: packageJsonSettings.issues,
|
|
1904
1955
|
source: `${PACKAGE_JSON}#pnpm`
|
|
1905
|
-
});
|
|
1956
|
+
}));
|
|
1906
1957
|
const projectNpmrcs = await readProjectNpmrcMigrations(cwd, collectWorkspacePackagePatterns(mergeByStrategy(pnpmWorkspace, mergeByStrategy(packageJsonSettings.settings, npmrc.settings, "merge"), strategy)), compatibility);
|
|
1907
|
-
|
|
1908
|
-
for (const project of projectNpmrcs.projects)
|
|
1958
|
+
warnings.push(...projectNpmrcs.warnings);
|
|
1959
|
+
for (const project of projectNpmrcs.projects) warnings.push(...formatSettingsIssues({
|
|
1909
1960
|
compatibility,
|
|
1910
1961
|
issues: project.migratable.issues,
|
|
1911
1962
|
projectConfig: true,
|
|
1912
1963
|
source: relative(cwd, project.npmrcPath)
|
|
1913
|
-
});
|
|
1964
|
+
}));
|
|
1914
1965
|
const npmrcSettings = mergeByStrategy(npmrc.settings, Object.keys(projectNpmrcs.packageConfigs).length ? { packageConfigs: projectNpmrcs.packageConfigs } : {}, "merge");
|
|
1915
1966
|
return {
|
|
1967
|
+
warnings,
|
|
1916
1968
|
incomingSettings: mergeByStrategy(packageJsonSettings.settings, npmrcSettings, "merge"),
|
|
1917
1969
|
npmrc,
|
|
1918
1970
|
packageJson: packageJsonSettings,
|
|
@@ -1993,13 +2045,6 @@ function resolveOptions(options = {}) {
|
|
|
1993
2045
|
}
|
|
1994
2046
|
//#endregion
|
|
1995
2047
|
//#region src/core.ts
|
|
1996
|
-
function hasSettingsSources(npmrcExists, packageJsonExists, pnpmWorkspaceExists) {
|
|
1997
|
-
if (!npmrcExists) consola$1.info(`${dim(NPMRC)} not found`);
|
|
1998
|
-
if (!packageJsonExists) consola$1.info(`${dim(PACKAGE_JSON)} not found`);
|
|
1999
|
-
if (npmrcExists || packageJsonExists || pnpmWorkspaceExists) return true;
|
|
2000
|
-
consola$1.warn("No pnpm settings files to migrate");
|
|
2001
|
-
return false;
|
|
2002
|
-
}
|
|
2003
2048
|
function hasMigratableSettings(sources) {
|
|
2004
2049
|
const { existingSettingsChanged, npmrcKeys, packageJsonKeys, projectNpmrcKeys, yarnResolutions } = sources;
|
|
2005
2050
|
return Boolean(existingSettingsChanged || npmrcKeys.length || packageJsonKeys.length || projectNpmrcKeys.length || yarnResolutions);
|
|
@@ -2007,10 +2052,6 @@ function hasMigratableSettings(sources) {
|
|
|
2007
2052
|
function assertCanMigrateRuntime(runtimeVersion, packageJsonExists) {
|
|
2008
2053
|
if (runtimeVersion && !packageJsonExists) throw new Error("Cannot migrate the removed Node.js runtime setting without a package.json file.");
|
|
2009
2054
|
}
|
|
2010
|
-
function reportMigrationChanges(showChanges, before = {}, after = {}) {
|
|
2011
|
-
if (!showChanges) return;
|
|
2012
|
-
reportSettingsChanges(collectSettingsChanges(before, after));
|
|
2013
|
-
}
|
|
2014
2055
|
/**
|
|
2015
2056
|
* Migrate pnpm settings from legacy locations to `pnpm-workspace.yaml`.
|
|
2016
2057
|
*
|
|
@@ -2030,10 +2071,10 @@ function reportMigrationChanges(showChanges, before = {}, after = {}) {
|
|
|
2030
2071
|
* @param rawOptions.sortKeys - Whether to sort keys in output YAML (default: false)
|
|
2031
2072
|
* @param rawOptions.newlineBetween - Add newlines between root keys (default: true)
|
|
2032
2073
|
* @param rawOptions.replaceDeprecated - Whether to replace deprecated settings (default: false)
|
|
2033
|
-
* @param rawOptions.showChanges -
|
|
2074
|
+
* @param rawOptions.showChanges - CLI display preference; does not affect the result
|
|
2034
2075
|
* @param rawOptions.strategy - Conflict handling strategy (default: merge)
|
|
2035
2076
|
*
|
|
2036
|
-
* @returns
|
|
2077
|
+
* @returns Settings changes, changed files, cleanup status, and warnings; no logs are emitted
|
|
2037
2078
|
*
|
|
2038
2079
|
* @throws {Error} When file operations fail or JSON/YAML parsing errors occur
|
|
2039
2080
|
*
|
|
@@ -2051,107 +2092,106 @@ function reportMigrationChanges(showChanges, before = {}, after = {}) {
|
|
|
2051
2092
|
* ```
|
|
2052
2093
|
*/
|
|
2053
2094
|
async function migratePnpmSettings(rawOptions = {}) {
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
}
|
|
2095
|
+
const options = resolveOptions(rawOptions);
|
|
2096
|
+
const npmrcPath = resolve(options.cwd, NPMRC);
|
|
2097
|
+
const packageJsonPath = resolve(options.cwd, PACKAGE_JSON);
|
|
2098
|
+
const pnpmWorkspaceYamlPath = resolve(options.cwd, PNPM_WORKSPACE_YAML);
|
|
2099
|
+
const [npmrcExists, packageJsonExists, pnpmWorkspaceExists] = await Promise.all([
|
|
2100
|
+
fsExists(npmrcPath),
|
|
2101
|
+
fsExists(packageJsonPath),
|
|
2102
|
+
fsExists(pnpmWorkspaceYamlPath)
|
|
2103
|
+
]);
|
|
2104
|
+
const result = {
|
|
2105
|
+
hasConfigurationFiles: npmrcExists || packageJsonExists || pnpmWorkspaceExists,
|
|
2106
|
+
settingsChanges: [],
|
|
2107
|
+
changedFiles: [],
|
|
2108
|
+
sourceSettingsCleaned: false,
|
|
2109
|
+
packageJsonRuntimeChanged: false,
|
|
2110
|
+
warnings: []
|
|
2111
|
+
};
|
|
2112
|
+
if (!result.hasConfigurationFiles) return result;
|
|
2113
|
+
const [packageJson, pnpmWorkspace] = await Promise.all([readPackageJson(packageJsonPath, packageJsonExists), readPnpmWorkspace(pnpmWorkspaceYamlPath, pnpmWorkspaceExists)]);
|
|
2114
|
+
const pnpmWorkspaceBefore = structuredClone(pnpmWorkspace.value);
|
|
2115
|
+
const compatibility = resolveCompatibilityTarget(options.compatibility, packageJson.value.packageManager, packageJson.value.devEngines?.packageManager);
|
|
2116
|
+
assertCompatibleWorkspaceSettings(pnpmWorkspace.value, compatibility);
|
|
2117
|
+
const sources = await resolveMigrationSources({
|
|
2118
|
+
compatibility,
|
|
2119
|
+
cwd: options.cwd,
|
|
2120
|
+
npmrcExists,
|
|
2121
|
+
npmrcPath,
|
|
2122
|
+
packageJson: packageJson.value,
|
|
2123
|
+
pnpmWorkspace: pnpmWorkspace.value,
|
|
2124
|
+
strategy: options.strategy,
|
|
2125
|
+
yarnResolutions: options.yarnResolutions
|
|
2126
|
+
});
|
|
2127
|
+
const { incomingSettings } = sources;
|
|
2128
|
+
const [existingNormalization, incomingNormalization] = await Promise.all([normalizeIncomingSettings(pnpmWorkspace.value, {
|
|
2129
|
+
compatibility,
|
|
2130
|
+
cwd: options.cwd,
|
|
2131
|
+
replaceDeprecated: options.replaceDeprecated
|
|
2132
|
+
}), normalizeIncomingSettings(incomingSettings, {
|
|
2133
|
+
compatibility,
|
|
2134
|
+
cwd: options.cwd,
|
|
2135
|
+
replaceDeprecated: options.replaceDeprecated
|
|
2136
|
+
})]);
|
|
2137
|
+
result.warnings.push(...sources.warnings, ...existingNormalization.warnings, ...incomingNormalization.warnings);
|
|
2138
|
+
const runtimeVersion = resolveRuntimeVersionByStrategy(existingNormalization.runtimeVersion, incomingNormalization.runtimeVersion, options.strategy);
|
|
2139
|
+
assertCanMigrateRuntime(runtimeVersion, packageJsonExists);
|
|
2140
|
+
const runtimeMigration = migrateRuntimeToPackageJson(packageJson.value, runtimeVersion);
|
|
2141
|
+
if (runtimeMigration.warning) result.warnings.push(runtimeMigration.warning);
|
|
2142
|
+
if (!hasMigratableSettings({
|
|
2143
|
+
existingSettingsChanged: existingNormalization.changed,
|
|
2144
|
+
npmrcKeys: sources.npmrc.keys,
|
|
2145
|
+
packageJsonKeys: sources.packageJson.keys,
|
|
2146
|
+
projectNpmrcKeys: sources.projectNpmrcs.projects.flatMap((project) => project.migratable.keys),
|
|
2147
|
+
yarnResolutions: sources.packageJson.yarnResolutions
|
|
2148
|
+
})) return result;
|
|
2149
|
+
const pnpmWorkspaceResult = mergeByStrategy(pnpmWorkspace.value, incomingSettings, options.strategy);
|
|
2150
|
+
updateYamlDocument(pnpmWorkspace.document, {
|
|
2151
|
+
after: pnpmWorkspaceResult,
|
|
2152
|
+
before: pnpmWorkspaceBefore,
|
|
2153
|
+
sortKeys: options.sortKeys
|
|
2154
|
+
});
|
|
2155
|
+
const finalYamlContent = formatRootSpacing(pnpmWorkspace.document.toString({ indent: pnpmWorkspace.indent }), options.newlineBetween);
|
|
2156
|
+
const persistence = await persistMigration({
|
|
2157
|
+
cleanNpmrc: options.cleanNpmrc,
|
|
2158
|
+
cleanPackageJson: options.cleanPackageJson,
|
|
2159
|
+
compatibility,
|
|
2160
|
+
finalSettings: pnpmWorkspaceResult,
|
|
2161
|
+
incomingSettings,
|
|
2162
|
+
npmrc: sources.npmrc,
|
|
2163
|
+
npmrcExists,
|
|
2164
|
+
npmrcPath,
|
|
2165
|
+
packageJson,
|
|
2166
|
+
packageJsonExists,
|
|
2167
|
+
packageJsonPath,
|
|
2168
|
+
packageJsonRuntimeChanged: runtimeMigration.changed,
|
|
2169
|
+
packageJsonSettings: sources.packageJson,
|
|
2170
|
+
pnpmWorkspaceContent: finalYamlContent,
|
|
2171
|
+
pnpmWorkspacePath: pnpmWorkspaceYamlPath,
|
|
2172
|
+
projectNpmrcs: sources.projectNpmrcs,
|
|
2173
|
+
runtimeVersion
|
|
2174
|
+
});
|
|
2175
|
+
return {
|
|
2176
|
+
...result,
|
|
2177
|
+
...persistence,
|
|
2178
|
+
settingsChanges: collectSettingsChanges(pnpmWorkspaceBefore, pnpmWorkspaceResult)
|
|
2179
|
+
};
|
|
2139
2180
|
}
|
|
2140
2181
|
//#endregion
|
|
2141
2182
|
//#region src/cli.ts
|
|
2142
2183
|
const cli = createCli();
|
|
2143
2184
|
cli.command("").usage("[options]").action(async (options) => {
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
consola.log(dim("\n--------------\n"));
|
|
2147
|
-
await migratePnpmSettings(options);
|
|
2148
|
-
consola.success(green("pnpm settings migrate has finished"));
|
|
2149
|
-
} catch (err) {
|
|
2150
|
-
consola.fail(red(String(err)));
|
|
2151
|
-
if (err instanceof Error && err.stack) consola.fail(dim(err.stack?.split("\n").slice(1).join("\n")));
|
|
2152
|
-
process.exit(1);
|
|
2153
|
-
}
|
|
2185
|
+
const result = await migratePnpmSettings(options);
|
|
2186
|
+
process.stdout.write(`${formatMigrationOutput(result, options.showChanges)}\n`);
|
|
2154
2187
|
});
|
|
2155
|
-
|
|
2188
|
+
try {
|
|
2189
|
+
cli.parse(process.argv, { run: false });
|
|
2190
|
+
await cli.runMatchedCommand();
|
|
2191
|
+
} catch (error) {
|
|
2192
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2193
|
+
process.stderr.write(`${red("✖")} ${message.trim()}\n`);
|
|
2194
|
+
process.exitCode = 1;
|
|
2195
|
+
}
|
|
2156
2196
|
//#endregion
|
|
2157
2197
|
export {};
|