pnpm-settings-migrator 0.6.0 → 0.7.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 +34 -3
- package/dist/cli.mjs +350 -168
- package/dist/index.d.mts +74 -7
- package/dist/index.mjs +341 -159
- package/package.json +27 -28
package/dist/cli.mjs
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import process from "node:process";
|
|
2
2
|
import { cac } from "cac";
|
|
3
|
-
import consola, { consola
|
|
4
|
-
import { isPlainObject, isUndefined, pick } from "@ntnyq/utils";
|
|
5
|
-
import { defu } from "defu";
|
|
6
|
-
import detectIndent from "detect-indent";
|
|
3
|
+
import consola$1, { consola } from "consola";
|
|
7
4
|
import { resolve } from "pathe";
|
|
8
5
|
import { Document, parse } from "yaml";
|
|
9
|
-
import { access, readFile, writeFile } from "node:fs/promises";
|
|
6
|
+
import { access, readFile, rm, writeFile } from "node:fs/promises";
|
|
10
7
|
import { getColor } from "consola/utils";
|
|
8
|
+
import { isPlainObject, isUndefined, pick } from "@ntnyq/utils";
|
|
9
|
+
import { defu } from "defu";
|
|
11
10
|
import camelcaseKeys from "camelcase-keys";
|
|
12
11
|
import { readIniFile } from "read-ini-file";
|
|
13
12
|
import { kebabCase } from "uncase";
|
|
13
|
+
import detectIndent from "detect-indent";
|
|
14
14
|
//#region package.json
|
|
15
15
|
var name = "pnpm-settings-migrator";
|
|
16
|
-
var version = "0.
|
|
16
|
+
var version = "0.7.0";
|
|
17
17
|
//#endregion
|
|
18
18
|
//#region src/constants.ts
|
|
19
19
|
const NPMRC = ".npmrc";
|
|
@@ -60,6 +60,7 @@ const PNPM_SETTINGS_FIELDS = [
|
|
|
60
60
|
"httpProxy",
|
|
61
61
|
"httpsProxy",
|
|
62
62
|
"ignoredBuiltDependencies",
|
|
63
|
+
"ignoreDepScripts",
|
|
63
64
|
"ignoreDeps",
|
|
64
65
|
"ignoredOptionalDependencies",
|
|
65
66
|
"ignorePatchFailures",
|
|
@@ -87,11 +88,14 @@ const PNPM_SETTINGS_FIELDS = [
|
|
|
87
88
|
"overrides",
|
|
88
89
|
"packageExtensions",
|
|
89
90
|
"packageImportMethod",
|
|
91
|
+
"packageManagerStrict",
|
|
92
|
+
"packageManagerStrictVersion",
|
|
90
93
|
"patchedDependencies",
|
|
91
94
|
"peerDependencyRules",
|
|
92
95
|
"peersSuffixMaxLength",
|
|
93
96
|
"pnpmfile",
|
|
94
97
|
"pnpmfileIgnorePkgManifest",
|
|
98
|
+
"pmOnFail",
|
|
95
99
|
"preferFrozenLockfile",
|
|
96
100
|
"preferWorkspacePackages",
|
|
97
101
|
"production",
|
|
@@ -130,11 +134,17 @@ const PNPM_SETTINGS_FIELDS = [
|
|
|
130
134
|
];
|
|
131
135
|
const PNPM_V11_REMOVED_SETTINGS = [
|
|
132
136
|
"allowNonAppliedPatches",
|
|
137
|
+
"executionEnv",
|
|
138
|
+
"ignoreDepScripts",
|
|
133
139
|
"ignorePatchFailures",
|
|
134
140
|
"ignoredBuiltDependencies",
|
|
141
|
+
"managePackageManagerVersions",
|
|
135
142
|
"neverBuiltDependencies",
|
|
136
143
|
"onlyBuiltDependencies",
|
|
137
|
-
"onlyBuiltDependenciesFile"
|
|
144
|
+
"onlyBuiltDependenciesFile",
|
|
145
|
+
"packageManagerStrict",
|
|
146
|
+
"packageManagerStrictVersion",
|
|
147
|
+
"useNodeVersion"
|
|
138
148
|
];
|
|
139
149
|
//#endregion
|
|
140
150
|
//#region src/options.ts
|
|
@@ -160,8 +170,19 @@ const VALID_STRATEGIES = [
|
|
|
160
170
|
const VALID_COMPATIBILITIES = [
|
|
161
171
|
"auto",
|
|
162
172
|
"v10",
|
|
163
|
-
"v11"
|
|
173
|
+
"v11",
|
|
174
|
+
"v12"
|
|
164
175
|
];
|
|
176
|
+
function resolveCompatibility(compatibility) {
|
|
177
|
+
if (!compatibility) return DEFAULT_OPTIONS.compatibility;
|
|
178
|
+
if (VALID_COMPATIBILITIES.includes(compatibility)) return compatibility;
|
|
179
|
+
throw new Error(`Invalid compatibility: ${compatibility}. Expected one of: ${VALID_COMPATIBILITIES.join(", ")}`);
|
|
180
|
+
}
|
|
181
|
+
function resolveStrategy(strategy) {
|
|
182
|
+
if (!strategy) return DEFAULT_OPTIONS.strategy;
|
|
183
|
+
if (VALID_STRATEGIES.includes(strategy)) return strategy;
|
|
184
|
+
throw new Error(`Invalid strategy: ${strategy}. Expected one of: ${VALID_STRATEGIES.join(", ")}`);
|
|
185
|
+
}
|
|
165
186
|
/**
|
|
166
187
|
* Resolve and normalize migration options with defaults.
|
|
167
188
|
*
|
|
@@ -196,16 +217,6 @@ function resolveOptions(options = {}) {
|
|
|
196
217
|
replaceDeprecated: options.replaceDeprecated ?? DEFAULT_OPTIONS.replaceDeprecated
|
|
197
218
|
};
|
|
198
219
|
}
|
|
199
|
-
function resolveCompatibility(compatibility) {
|
|
200
|
-
if (!compatibility) return DEFAULT_OPTIONS.compatibility;
|
|
201
|
-
if (VALID_COMPATIBILITIES.includes(compatibility)) return compatibility;
|
|
202
|
-
throw new Error(`Invalid compatibility: ${compatibility}. Expected one of: ${VALID_COMPATIBILITIES.join(", ")}`);
|
|
203
|
-
}
|
|
204
|
-
function resolveStrategy(strategy) {
|
|
205
|
-
if (!strategy) return DEFAULT_OPTIONS.strategy;
|
|
206
|
-
if (VALID_STRATEGIES.includes(strategy)) return strategy;
|
|
207
|
-
throw new Error(`Invalid strategy: ${strategy}. Expected one of: ${VALID_STRATEGIES.join(", ")}`);
|
|
208
|
-
}
|
|
209
220
|
//#endregion
|
|
210
221
|
//#region src/utils/fs.ts
|
|
211
222
|
/**
|
|
@@ -225,6 +236,13 @@ async function fsReadFile(path) {
|
|
|
225
236
|
return await readFile(path, "utf-8");
|
|
226
237
|
}
|
|
227
238
|
/**
|
|
239
|
+
* Remove a file if it exists.
|
|
240
|
+
* @param path - file path
|
|
241
|
+
*/
|
|
242
|
+
async function fsRemoveFile(path) {
|
|
243
|
+
await rm(path, { force: true });
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
228
246
|
* Write file
|
|
229
247
|
* @param path - file path
|
|
230
248
|
* @param content - file content
|
|
@@ -240,39 +258,143 @@ const red = getColor("red");
|
|
|
240
258
|
const bold = getColor("bold");
|
|
241
259
|
const magenta = getColor("magenta");
|
|
242
260
|
//#endregion
|
|
243
|
-
//#region src/utils/
|
|
261
|
+
//#region src/utils/compatibility.ts
|
|
262
|
+
const PNPM_REPLACEABLE_IN_V10_SETTINGS = [
|
|
263
|
+
"allowNonAppliedPatches",
|
|
264
|
+
"ignoredBuiltDependencies",
|
|
265
|
+
"neverBuiltDependencies",
|
|
266
|
+
"onlyBuiltDependencies",
|
|
267
|
+
"onlyBuiltDependenciesFile"
|
|
268
|
+
];
|
|
244
269
|
/**
|
|
245
|
-
*
|
|
246
|
-
*
|
|
247
|
-
* @param existing - Existing pnpm-workspace.yaml content
|
|
248
|
-
* @param incoming - New settings from package.json and .npmrc
|
|
249
|
-
* @param strategy - Merge strategy to use
|
|
250
|
-
*
|
|
251
|
-
* @returns Merged pnpm workspace configuration
|
|
252
|
-
*
|
|
253
|
-
* @example
|
|
254
|
-
* ```ts
|
|
255
|
-
* // Discard strategy - keep existing values
|
|
256
|
-
* mergeByStrategy({ packages: ['a'] }, { packages: ['b'] }, 'discard')
|
|
257
|
-
* // => { packages: ['a'] }
|
|
258
|
-
*
|
|
259
|
-
* // Merge strategy - combine arrays with deduplication
|
|
260
|
-
* mergeByStrategy({ packages: ['a'] }, { packages: ['b'] }, 'merge')
|
|
261
|
-
* // => { packages: ['a', 'b'] }
|
|
262
|
-
*
|
|
263
|
-
* // Overwrite strategy - use incoming values
|
|
264
|
-
* mergeByStrategy({ packages: ['a'] }, { packages: ['b'] }, 'overwrite')
|
|
265
|
-
* // => { packages: ['b'] }
|
|
266
|
-
* ```
|
|
270
|
+
* Read packages listed by the legacy `onlyBuiltDependenciesFile` setting.
|
|
267
271
|
*/
|
|
268
|
-
function
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
272
|
+
async function readOnlyBuiltDependenciesFile(cwd, file) {
|
|
273
|
+
if (!file) return [];
|
|
274
|
+
const path = resolve(cwd, file);
|
|
275
|
+
const value = JSON.parse(await fsReadFile(path));
|
|
276
|
+
if (!Array.isArray(value) || !value.every((item) => typeof item === "string")) throw new TypeError(`Invalid onlyBuiltDependenciesFile: ${file}. Expected a JSON array of package names.`);
|
|
277
|
+
return value;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Build an `allowBuilds` map from legacy build-script settings.
|
|
281
|
+
*/
|
|
282
|
+
async function collectAllowBuildsFromLegacy(incomingSettings, cwd) {
|
|
283
|
+
const allowBuilds = {};
|
|
284
|
+
const allowedFromFile = await readOnlyBuiltDependenciesFile(cwd, incomingSettings.onlyBuiltDependenciesFile);
|
|
285
|
+
for (const name of [...incomingSettings.onlyBuiltDependencies || [], ...allowedFromFile]) allowBuilds[name] = true;
|
|
286
|
+
for (const name of incomingSettings.ignoredBuiltDependencies || []) allowBuilds[name] = false;
|
|
287
|
+
for (const name of incomingSettings.neverBuiltDependencies || []) allowBuilds[name] = false;
|
|
288
|
+
return Object.keys(allowBuilds).length ? allowBuilds : void 0;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Resolve the pnpm v11 replacement for legacy package-manager settings.
|
|
292
|
+
*/
|
|
293
|
+
function resolvePmOnFail(settings) {
|
|
294
|
+
if (settings.pmOnFail) return settings.pmOnFail;
|
|
295
|
+
if (settings.packageManagerStrictVersion === true) return "error";
|
|
296
|
+
if (settings.packageManagerStrict === false) return "warn";
|
|
297
|
+
if (settings.managePackageManagerVersions === false) return "ignore";
|
|
298
|
+
if (settings.managePackageManagerVersions === true) return "download";
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Rename legacy audit settings while preserving values for manual ID updates.
|
|
302
|
+
*/
|
|
303
|
+
function normalizeAuditConfig(settings, warnings) {
|
|
304
|
+
const { auditConfig } = settings;
|
|
305
|
+
const ignoreCves = auditConfig?.ignoreCves;
|
|
306
|
+
if (!ignoreCves) return;
|
|
307
|
+
auditConfig.ignoreGhsas ??= ignoreCves;
|
|
308
|
+
Reflect.deleteProperty(auditConfig, "ignoreCves");
|
|
309
|
+
warnings.push("auditConfig.ignoreCves was renamed to auditConfig.ignoreGhsas; replace each CVE ID with its corresponding GHSA ID.");
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Resolve a removed Node.js runtime setting for migration to `package.json`.
|
|
313
|
+
*/
|
|
314
|
+
function resolveRuntimeVersion(settings, warnings) {
|
|
315
|
+
const { useNodeVersion } = settings;
|
|
316
|
+
const executionEnvNodeVersion = settings.executionEnv?.nodeVersion;
|
|
317
|
+
if (useNodeVersion && executionEnvNodeVersion && useNodeVersion !== executionEnvNodeVersion) warnings.push(`Both useNodeVersion (${useNodeVersion}) and executionEnv.nodeVersion (${executionEnvNodeVersion}) were found; useNodeVersion takes precedence.`);
|
|
318
|
+
return useNodeVersion || executionEnvNodeVersion;
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Normalize settings according to the selected compatibility target.
|
|
322
|
+
*/
|
|
323
|
+
async function normalizeIncomingSettings(incomingSettings, options) {
|
|
324
|
+
const { compatibility, cwd, replaceDeprecated } = options;
|
|
325
|
+
const before = JSON.stringify(incomingSettings);
|
|
326
|
+
const warnings = [];
|
|
327
|
+
if (compatibility === "v10" && !replaceDeprecated) return {
|
|
328
|
+
changed: false,
|
|
329
|
+
warnings
|
|
330
|
+
};
|
|
331
|
+
if (incomingSettings.allowNonAppliedPatches !== void 0) incomingSettings.allowUnusedPatches ??= incomingSettings.allowNonAppliedPatches;
|
|
332
|
+
const allowBuildsFromLegacy = await collectAllowBuildsFromLegacy(incomingSettings, cwd);
|
|
333
|
+
if (allowBuildsFromLegacy) incomingSettings.allowBuilds = {
|
|
334
|
+
...allowBuildsFromLegacy,
|
|
335
|
+
...incomingSettings.allowBuilds || {}
|
|
336
|
+
};
|
|
337
|
+
if (compatibility === "v10") {
|
|
338
|
+
for (const key of PNPM_REPLACEABLE_IN_V10_SETTINGS) Reflect.deleteProperty(incomingSettings, key);
|
|
339
|
+
return {
|
|
340
|
+
changed: before !== JSON.stringify(incomingSettings),
|
|
341
|
+
warnings
|
|
342
|
+
};
|
|
274
343
|
}
|
|
344
|
+
const pmOnFail = resolvePmOnFail(incomingSettings);
|
|
345
|
+
if (pmOnFail) incomingSettings.pmOnFail = pmOnFail;
|
|
346
|
+
normalizeAuditConfig(incomingSettings, warnings);
|
|
347
|
+
const runtimeVersion = resolveRuntimeVersion(incomingSettings, warnings);
|
|
348
|
+
if (incomingSettings.ignoreDepScripts !== void 0) warnings.push("ignoreDepScripts was removed in pnpm v11 and has no direct replacement.");
|
|
349
|
+
if (incomingSettings.ignorePatchFailures !== void 0) warnings.push("ignorePatchFailures was removed in pnpm v11; patch failures now always throw.");
|
|
350
|
+
for (const key of PNPM_V11_REMOVED_SETTINGS) Reflect.deleteProperty(incomingSettings, key);
|
|
351
|
+
return {
|
|
352
|
+
changed: before !== JSON.stringify(incomingSettings),
|
|
353
|
+
runtimeVersion,
|
|
354
|
+
warnings
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Move a removed Node.js runtime setting to `package.json#devEngines.runtime`.
|
|
359
|
+
*/
|
|
360
|
+
function migrateRuntimeToPackageJson(packageJson, runtimeVersion) {
|
|
361
|
+
if (!runtimeVersion) return { changed: false };
|
|
362
|
+
if (packageJson.devEngines?.runtime) return {
|
|
363
|
+
changed: false,
|
|
364
|
+
warning: "A devEngines.runtime declaration already exists; the removed pnpm Node.js runtime setting was not applied."
|
|
365
|
+
};
|
|
366
|
+
packageJson.devEngines = {
|
|
367
|
+
...packageJson.devEngines,
|
|
368
|
+
runtime: {
|
|
369
|
+
name: "node",
|
|
370
|
+
version: runtimeVersion
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
return { changed: true };
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Select the runtime setting that wins under the configured merge strategy.
|
|
377
|
+
*/
|
|
378
|
+
function resolveRuntimeVersionByStrategy(existingVersion, incomingVersion, strategy) {
|
|
379
|
+
if (strategy === "overwrite") return incomingVersion ?? existingVersion;
|
|
380
|
+
return existingVersion ?? incomingVersion;
|
|
381
|
+
}
|
|
382
|
+
//#endregion
|
|
383
|
+
//#region src/utils/compatibility-target.ts
|
|
384
|
+
const PNPM_V11_MAJOR = 11;
|
|
385
|
+
const PNPM_V12_MAJOR = 12;
|
|
386
|
+
/**
|
|
387
|
+
* Resolve final compatibility target from user option and package manager hint.
|
|
388
|
+
*/
|
|
389
|
+
function resolveCompatibilityTarget(compatibility, packageManager, devPackageManager) {
|
|
390
|
+
if (compatibility !== "auto") return compatibility;
|
|
391
|
+
const match = (packageManager || (devPackageManager?.name === "pnpm" ? `pnpm@${devPackageManager.version}` : void 0))?.match(/^pnpm@[\s<=>^~]*(?<major>\d+)(?:\.|\s|$)/u);
|
|
392
|
+
const major = Number(match?.groups?.major);
|
|
393
|
+
if (major >= PNPM_V12_MAJOR) return "v12";
|
|
394
|
+
return major >= PNPM_V11_MAJOR ? "v11" : "v10";
|
|
275
395
|
}
|
|
396
|
+
//#endregion
|
|
397
|
+
//#region src/utils/merge.ts
|
|
276
398
|
/**
|
|
277
399
|
* Merge objects with priority to the first argument.
|
|
278
400
|
* Only adds keys from second argument that don't exist in first.
|
|
@@ -309,11 +431,43 @@ function mergeWithArrayDedupe(existing, incoming) {
|
|
|
309
431
|
for (const [key, incomingValue] of Object.entries(incoming)) {
|
|
310
432
|
const existingValue = result[key];
|
|
311
433
|
if (isUndefined(existingValue)) result[key] = incomingValue;
|
|
312
|
-
else if (Array.isArray(existingValue) && Array.isArray(incomingValue)) result[key] = Array.from(new Set([...existingValue, ...incomingValue]));
|
|
434
|
+
else if (Array.isArray(existingValue) && Array.isArray(incomingValue)) result[key] = Array.from(/* @__PURE__ */ new Set([...existingValue, ...incomingValue]));
|
|
313
435
|
else if (isPlainObject(existingValue) && isPlainObject(incomingValue)) result[key] = mergeWithArrayDedupe(existingValue, incomingValue);
|
|
314
436
|
}
|
|
315
437
|
return result;
|
|
316
438
|
}
|
|
439
|
+
/**
|
|
440
|
+
* Merge two objects based on the specified strategy.
|
|
441
|
+
*
|
|
442
|
+
* @param existing - Existing pnpm-workspace.yaml content
|
|
443
|
+
* @param incoming - New settings from package.json and .npmrc
|
|
444
|
+
* @param strategy - Merge strategy to use
|
|
445
|
+
*
|
|
446
|
+
* @returns Merged pnpm workspace configuration
|
|
447
|
+
*
|
|
448
|
+
* @example
|
|
449
|
+
* ```ts
|
|
450
|
+
* // Discard strategy - keep existing values
|
|
451
|
+
* mergeByStrategy({ packages: ['a'] }, { packages: ['b'] }, 'discard')
|
|
452
|
+
* // => { packages: ['a'] }
|
|
453
|
+
*
|
|
454
|
+
* // Merge strategy - combine arrays with deduplication
|
|
455
|
+
* mergeByStrategy({ packages: ['a'] }, { packages: ['b'] }, 'merge')
|
|
456
|
+
* // => { packages: ['a', 'b'] }
|
|
457
|
+
*
|
|
458
|
+
* // Overwrite strategy - use incoming values
|
|
459
|
+
* mergeByStrategy({ packages: ['a'] }, { packages: ['b'] }, 'overwrite')
|
|
460
|
+
* // => { packages: ['b'] }
|
|
461
|
+
* ```
|
|
462
|
+
*/
|
|
463
|
+
function mergeByStrategy(existing, incoming, strategy) {
|
|
464
|
+
switch (strategy) {
|
|
465
|
+
case "discard": return discardMerge(existing, incoming);
|
|
466
|
+
case "merge": return mergeWithArrayDedupe(existing, incoming);
|
|
467
|
+
case "overwrite": return discardMerge(incoming, existing);
|
|
468
|
+
default: return defu(existing, incoming);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
317
471
|
//#endregion
|
|
318
472
|
//#region src/utils/npmrc.ts
|
|
319
473
|
const NPMRC_AUTH_OR_REGISTRY_KEYS = [
|
|
@@ -332,6 +486,33 @@ const NPMRC_AUTH_OR_REGISTRY_KEYS = [
|
|
|
332
486
|
"tokenhelper",
|
|
333
487
|
"username"
|
|
334
488
|
];
|
|
489
|
+
const NODE_MIRROR_KEY_PATTERN = /^node-mirror:(?<channel>.+)$/iu;
|
|
490
|
+
/**
|
|
491
|
+
* Normalize `.npmrc` key for case-insensitive matching.
|
|
492
|
+
*/
|
|
493
|
+
function normalizeNpmrcKey(key) {
|
|
494
|
+
return key.trim().replace(/\[\]$/u, "").toLowerCase();
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Extract key name from a raw `.npmrc` line.
|
|
498
|
+
*/
|
|
499
|
+
function getNpmrcLineKey(line) {
|
|
500
|
+
const trimmed = line.trim();
|
|
501
|
+
if (!trimmed || trimmed.startsWith(";") || trimmed.startsWith("#")) return;
|
|
502
|
+
const index = trimmed.indexOf("=");
|
|
503
|
+
if (index <= 0) return;
|
|
504
|
+
return normalizeNpmrcKey(trimmed.slice(0, index));
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Check whether a `.npmrc` key is auth/registry-related and should stay in
|
|
508
|
+
* pnpm v11 and newer.
|
|
509
|
+
*/
|
|
510
|
+
function isNpmrcAuthOrRegistryKey(key) {
|
|
511
|
+
const normalized = normalizeNpmrcKey(key);
|
|
512
|
+
if (normalized.startsWith("//")) return true;
|
|
513
|
+
if (normalized === "registry" || normalized.endsWith(":registry")) return true;
|
|
514
|
+
return NPMRC_AUTH_OR_REGISTRY_KEYS.some((authKey) => normalized === authKey || normalized.endsWith(`:${authKey}`));
|
|
515
|
+
}
|
|
335
516
|
/**
|
|
336
517
|
* Remove pnpm-related settings from `.npmrc` file.
|
|
337
518
|
*
|
|
@@ -351,14 +532,19 @@ const NPMRC_AUTH_OR_REGISTRY_KEYS = [
|
|
|
351
532
|
* ```
|
|
352
533
|
*/
|
|
353
534
|
async function pruneNpmrc(path, compatibility = "v10", migratedKeys = []) {
|
|
354
|
-
const pnpmSettingsFields = PNPM_SETTINGS_FIELDS.map((
|
|
535
|
+
const pnpmSettingsFields = PNPM_SETTINGS_FIELDS.map((field) => kebabCase(field));
|
|
355
536
|
const migratedKeySet = new Set(migratedKeys.map(normalizeNpmrcKey));
|
|
356
|
-
|
|
537
|
+
const updatedContent = (await fsReadFile(path)).split(/\r?\n/u).filter((line) => {
|
|
357
538
|
const key = getNpmrcLineKey(line);
|
|
358
539
|
if (!key) return true;
|
|
359
|
-
if (compatibility
|
|
360
|
-
return !pnpmSettingsFields.some((
|
|
361
|
-
}).join("\n"));
|
|
540
|
+
if (compatibility !== "v10") return !migratedKeySet.has(normalizeNpmrcKey(key));
|
|
541
|
+
return !pnpmSettingsFields.some((field) => key.startsWith(field));
|
|
542
|
+
}).join("\n").trimEnd();
|
|
543
|
+
if (compatibility !== "v10" && !updatedContent.trim()) {
|
|
544
|
+
await fsRemoveFile(path);
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
await fsWriteFile(path, updatedContent);
|
|
362
548
|
}
|
|
363
549
|
/**
|
|
364
550
|
* Read and parse `.npmrc` file with camelCase key conversion.
|
|
@@ -382,56 +568,87 @@ async function pruneNpmrc(path, compatibility = "v10", migratedKeys = []) {
|
|
|
382
568
|
/**
|
|
383
569
|
* Read `.npmrc` and return settings that should be migrated into workspace config.
|
|
384
570
|
*
|
|
385
|
-
* - `v10`: returns
|
|
386
|
-
* - `v11
|
|
571
|
+
* - `v10`: returns settings from the legacy whitelist.
|
|
572
|
+
* - `v11+`: excludes auth/registry keys because pnpm still reads them from
|
|
573
|
+
* `.npmrc`.
|
|
387
574
|
*/
|
|
388
575
|
async function readMigratableNpmrc(path, compatibility) {
|
|
389
576
|
const raw = await readIniFile(path);
|
|
390
|
-
if (compatibility === "v10")
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
577
|
+
if (compatibility === "v10") {
|
|
578
|
+
const settings = camelcaseKeys(raw);
|
|
579
|
+
return {
|
|
580
|
+
keys: PNPM_SETTINGS_FIELDS,
|
|
581
|
+
settings: pick(settings, PNPM_SETTINGS_FIELDS)
|
|
582
|
+
};
|
|
583
|
+
}
|
|
394
584
|
const migratable = {};
|
|
395
585
|
const keys = [];
|
|
586
|
+
const nodeDownloadMirrors = {};
|
|
396
587
|
for (const [key, value] of Object.entries(raw)) if (!isNpmrcAuthOrRegistryKey(key)) {
|
|
397
|
-
migratable[key] = value;
|
|
398
588
|
keys.push(key);
|
|
589
|
+
const nodeMirrorMatch = normalizeNpmrcKey(key).match(NODE_MIRROR_KEY_PATTERN);
|
|
590
|
+
if (nodeMirrorMatch?.groups?.channel) nodeDownloadMirrors[nodeMirrorMatch.groups.channel] = String(value);
|
|
591
|
+
else migratable[key] = value;
|
|
399
592
|
}
|
|
593
|
+
const settings = camelcaseKeys(migratable);
|
|
594
|
+
if (Object.keys(nodeDownloadMirrors).length) settings.nodeDownloadMirrors = nodeDownloadMirrors;
|
|
400
595
|
return {
|
|
401
596
|
keys,
|
|
402
|
-
settings
|
|
597
|
+
settings
|
|
403
598
|
};
|
|
404
599
|
}
|
|
405
|
-
|
|
406
|
-
|
|
600
|
+
//#endregion
|
|
601
|
+
//#region src/utils/config.ts
|
|
602
|
+
function resolveYamlIndent(content) {
|
|
603
|
+
const detectedIndent = detectIndent(content).amount;
|
|
604
|
+
return detectedIndent > 0 ? detectedIndent : 2;
|
|
407
605
|
}
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
606
|
+
async function readPackageJson(path, exists) {
|
|
607
|
+
if (!exists) return {
|
|
608
|
+
indent: 2,
|
|
609
|
+
value: {}
|
|
610
|
+
};
|
|
611
|
+
const content = await fsReadFile(path);
|
|
612
|
+
return {
|
|
613
|
+
indent: detectIndent(content).indent,
|
|
614
|
+
value: JSON.parse(content)
|
|
615
|
+
};
|
|
417
616
|
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
617
|
+
async function readPnpmWorkspace(path, exists) {
|
|
618
|
+
if (!exists) return {
|
|
619
|
+
indent: 2,
|
|
620
|
+
value: {}
|
|
621
|
+
};
|
|
622
|
+
const content = await fsReadFile(path);
|
|
623
|
+
return {
|
|
624
|
+
indent: resolveYamlIndent(content),
|
|
625
|
+
value: parse(content) ?? {}
|
|
626
|
+
};
|
|
426
627
|
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
628
|
+
function resolvePackageJsonSettings(packageJson, yarnResolutions) {
|
|
629
|
+
const pnpmSettings = yarnResolutions && packageJson.resolutions ? {
|
|
630
|
+
...packageJson.pnpm,
|
|
631
|
+
overrides: defu(packageJson.pnpm?.overrides, packageJson.resolutions)
|
|
632
|
+
} : { ...packageJson.pnpm };
|
|
633
|
+
if (pnpmSettings.overrides && !Object.keys(pnpmSettings.overrides).length) delete pnpmSettings.overrides;
|
|
634
|
+
return pnpmSettings;
|
|
432
635
|
}
|
|
433
636
|
//#endregion
|
|
434
637
|
//#region src/core.ts
|
|
638
|
+
function hasSettingsSources(npmrcExists, packageJsonExists, pnpmWorkspaceExists) {
|
|
639
|
+
if (!npmrcExists) consola$1.info(`${dim(NPMRC)} not found`);
|
|
640
|
+
if (!packageJsonExists) consola$1.info(`${dim(PACKAGE_JSON)} not found`);
|
|
641
|
+
if (npmrcExists || packageJsonExists || pnpmWorkspaceExists) return true;
|
|
642
|
+
consola$1.warn("No pnpm settings files to migrate");
|
|
643
|
+
return false;
|
|
644
|
+
}
|
|
645
|
+
function hasMigratableSettings(sources) {
|
|
646
|
+
const { existingSettingsChanged, packageJson, pnpmSettingsInNpmrc, yarnResolutions } = sources;
|
|
647
|
+
return Boolean(existingSettingsChanged || packageJson.pnpm || yarnResolutions && packageJson.resolutions || Object.keys(pnpmSettingsInNpmrc).length);
|
|
648
|
+
}
|
|
649
|
+
function assertCanMigrateRuntime(runtimeVersion, packageJsonExists) {
|
|
650
|
+
if (runtimeVersion && !packageJsonExists) throw new Error("Cannot migrate the removed Node.js runtime setting without a package.json file.");
|
|
651
|
+
}
|
|
435
652
|
/**
|
|
436
653
|
* Migrate pnpm settings from legacy locations to `pnpm-workspace.yaml`.
|
|
437
654
|
*
|
|
@@ -440,6 +657,7 @@ function normalizeNpmrcKey(key) {
|
|
|
440
657
|
* - `package.json` pnpm field
|
|
441
658
|
* - `.npmrc` pnpm-related settings
|
|
442
659
|
* - `package.json` resolutions (optional, converts to pnpm overrides)
|
|
660
|
+
* - deprecated settings already present in `pnpm-workspace.yaml`
|
|
443
661
|
*
|
|
444
662
|
* @param rawOptions - Migration options
|
|
445
663
|
* @param rawOptions.cwd - Current working directory (default: process.cwd())
|
|
@@ -477,53 +695,43 @@ async function migratePnpmSettings(rawOptions = {}) {
|
|
|
477
695
|
fsExists(packageJsonPath),
|
|
478
696
|
fsExists(pnpmWorkspaceYamlPath)
|
|
479
697
|
]);
|
|
480
|
-
if (!npmrcExists
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
return;
|
|
485
|
-
}
|
|
486
|
-
let packageJsonIndent = 2;
|
|
487
|
-
let packageJsonObject = {};
|
|
488
|
-
let pnpmWorkspaceYamlIndent = 2;
|
|
489
|
-
let pnpmWorkspaceYamlObject = {};
|
|
490
|
-
if (packageJsonExists) {
|
|
491
|
-
const content = await fsReadFile(packageJsonPath);
|
|
492
|
-
packageJsonIndent = detectIndent(content).indent;
|
|
493
|
-
packageJsonObject = JSON.parse(content);
|
|
494
|
-
}
|
|
495
|
-
if (pnpmWorkspaceExists) {
|
|
496
|
-
const content = await fsReadFile(pnpmWorkspaceYamlPath);
|
|
497
|
-
pnpmWorkspaceYamlIndent = detectIndent(content).amount;
|
|
498
|
-
pnpmWorkspaceYamlObject = parse(content);
|
|
499
|
-
}
|
|
500
|
-
const compatibility = resolveCompatibilityTarget(options.compatibility, packageJsonObject.packageManager);
|
|
501
|
-
const npmrcMigratable = npmrcExists ? compatibility === "v10" ? {
|
|
502
|
-
keys: PNPM_SETTINGS_FIELDS,
|
|
503
|
-
settings: pick(await readNpmrc(npmrcPath), PNPM_SETTINGS_FIELDS)
|
|
504
|
-
} : await readMigratableNpmrc(npmrcPath, compatibility) : {
|
|
698
|
+
if (!hasSettingsSources(npmrcExists, packageJsonExists, pnpmWorkspaceExists)) return;
|
|
699
|
+
const [packageJson, pnpmWorkspace] = await Promise.all([readPackageJson(packageJsonPath, packageJsonExists), readPnpmWorkspace(pnpmWorkspaceYamlPath, pnpmWorkspaceExists)]);
|
|
700
|
+
const compatibility = resolveCompatibilityTarget(options.compatibility, packageJson.value.packageManager, packageJson.value.devEngines?.packageManager);
|
|
701
|
+
const npmrcMigratable = npmrcExists ? await readMigratableNpmrc(npmrcPath, compatibility) : {
|
|
505
702
|
keys: [],
|
|
506
703
|
settings: {}
|
|
507
704
|
};
|
|
508
705
|
const pnpmSettingsInNpmrc = npmrcMigratable.settings;
|
|
509
|
-
const
|
|
510
|
-
const hasResolutions = options.yarnResolutions && !!packageJsonObject.resolutions;
|
|
511
|
-
const hasNpmrcSettings = Object.keys(pnpmSettingsInNpmrc).length > 0;
|
|
512
|
-
if (!hasPnpmInPackageJson && !hasResolutions && !hasNpmrcSettings) {
|
|
513
|
-
consola.warn("No pnpm settings fields to migrate");
|
|
514
|
-
return;
|
|
515
|
-
}
|
|
516
|
-
const pnpmSettingsInPackageJson = options.yarnResolutions && packageJsonObject.resolutions ? {
|
|
517
|
-
...packageJsonObject.pnpm,
|
|
518
|
-
overrides: defu(packageJsonObject.pnpm?.overrides, packageJsonObject.resolutions)
|
|
519
|
-
} : { ...packageJsonObject.pnpm };
|
|
520
|
-
if (pnpmSettingsInPackageJson.overrides && !Object.keys(pnpmSettingsInPackageJson.overrides).length) delete pnpmSettingsInPackageJson.overrides;
|
|
706
|
+
const pnpmSettingsInPackageJson = resolvePackageJsonSettings(packageJson.value, options.yarnResolutions);
|
|
521
707
|
const incomingSettings = {
|
|
522
708
|
...pnpmSettingsInNpmrc,
|
|
523
709
|
...pnpmSettingsInPackageJson
|
|
524
710
|
};
|
|
525
|
-
normalizeIncomingSettings(
|
|
526
|
-
|
|
711
|
+
const [existingNormalization, incomingNormalization] = await Promise.all([normalizeIncomingSettings(pnpmWorkspace.value, {
|
|
712
|
+
compatibility,
|
|
713
|
+
cwd: options.cwd,
|
|
714
|
+
replaceDeprecated: options.replaceDeprecated
|
|
715
|
+
}), normalizeIncomingSettings(incomingSettings, {
|
|
716
|
+
compatibility,
|
|
717
|
+
cwd: options.cwd,
|
|
718
|
+
replaceDeprecated: options.replaceDeprecated
|
|
719
|
+
})]);
|
|
720
|
+
for (const warning of [...existingNormalization.warnings, ...incomingNormalization.warnings]) consola$1.warn(warning);
|
|
721
|
+
const runtimeVersion = resolveRuntimeVersionByStrategy(existingNormalization.runtimeVersion, incomingNormalization.runtimeVersion, options.strategy);
|
|
722
|
+
assertCanMigrateRuntime(runtimeVersion, packageJsonExists);
|
|
723
|
+
const runtimeMigration = migrateRuntimeToPackageJson(packageJson.value, runtimeVersion);
|
|
724
|
+
if (runtimeMigration.warning) consola$1.warn(runtimeMigration.warning);
|
|
725
|
+
if (!hasMigratableSettings({
|
|
726
|
+
existingSettingsChanged: existingNormalization.changed,
|
|
727
|
+
packageJson: packageJson.value,
|
|
728
|
+
pnpmSettingsInNpmrc,
|
|
729
|
+
yarnResolutions: options.yarnResolutions
|
|
730
|
+
})) {
|
|
731
|
+
consola$1.warn("No pnpm settings fields to migrate");
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
const pnpmWorkspaceResult = mergeByStrategy(pnpmWorkspace.value, incomingSettings, options.strategy);
|
|
527
735
|
const yamlDocument = new Document({}, { sortMapEntries: options.sortKeys });
|
|
528
736
|
Object.entries(pnpmWorkspaceResult).forEach(([key, value]) => {
|
|
529
737
|
yamlDocument.add({
|
|
@@ -531,63 +739,37 @@ async function migratePnpmSettings(rawOptions = {}) {
|
|
|
531
739
|
value
|
|
532
740
|
});
|
|
533
741
|
});
|
|
534
|
-
const yamlContent = yamlDocument.toString({ indent:
|
|
535
|
-
await fsWriteFile(pnpmWorkspaceYamlPath, options.newlineBetween ? yamlContent.replace(/\n(?=[^\s#][^:\n]*:)/
|
|
742
|
+
const yamlContent = yamlDocument.toString({ indent: pnpmWorkspace.indent });
|
|
743
|
+
await fsWriteFile(pnpmWorkspaceYamlPath, options.newlineBetween ? yamlContent.replace(/\n(?=[^\s#][^:\n]*:)/gu, "\n\n") : yamlContent);
|
|
536
744
|
if (npmrcExists && options.cleanNpmrc) await pruneNpmrc(npmrcPath, compatibility, npmrcMigratable.keys);
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
if (
|
|
540
|
-
|
|
745
|
+
let packageJsonChanged = runtimeMigration.changed;
|
|
746
|
+
if (packageJsonExists && options.cleanPackageJson) {
|
|
747
|
+
if (packageJson.value.pnpm) packageJsonChanged = true;
|
|
748
|
+
delete packageJson.value.pnpm;
|
|
749
|
+
if (options.yarnResolutions && packageJson.value.resolutions) {
|
|
750
|
+
packageJsonChanged = true;
|
|
751
|
+
delete packageJson.value.resolutions;
|
|
752
|
+
}
|
|
541
753
|
}
|
|
754
|
+
if (packageJsonExists && packageJsonChanged) await fsWriteFile(packageJsonPath, JSON.stringify(packageJson.value, null, packageJson.indent));
|
|
542
755
|
} catch (err) {
|
|
543
|
-
consola.error("Failed to migrate pnpm settings:", err);
|
|
756
|
+
consola$1.error("Failed to migrate pnpm settings:", err);
|
|
544
757
|
throw err;
|
|
545
758
|
}
|
|
546
759
|
}
|
|
547
|
-
/**
|
|
548
|
-
* Build v11 `allowBuilds` map from legacy build-script settings.
|
|
549
|
-
*/
|
|
550
|
-
function collectAllowBuildsFromLegacy(incomingSettings) {
|
|
551
|
-
const allowBuilds = {};
|
|
552
|
-
for (const name of incomingSettings.onlyBuiltDependencies || []) allowBuilds[name] = true;
|
|
553
|
-
for (const name of incomingSettings.ignoredBuiltDependencies || []) allowBuilds[name] = false;
|
|
554
|
-
for (const name of incomingSettings.neverBuiltDependencies || []) allowBuilds[name] = false;
|
|
555
|
-
return Object.keys(allowBuilds).length ? allowBuilds : void 0;
|
|
556
|
-
}
|
|
557
|
-
/**
|
|
558
|
-
* Normalize incoming settings according to compatibility target.
|
|
559
|
-
*/
|
|
560
|
-
function normalizeIncomingSettings(incomingSettings, compatibility, replaceDeprecated) {
|
|
561
|
-
if (compatibility === "v10" && !replaceDeprecated) return;
|
|
562
|
-
if (incomingSettings.allowNonAppliedPatches !== void 0) incomingSettings.allowUnusedPatches = incomingSettings.allowUnusedPatches ?? incomingSettings.allowNonAppliedPatches;
|
|
563
|
-
const allowBuildsFromLegacy = collectAllowBuildsFromLegacy(incomingSettings);
|
|
564
|
-
if (allowBuildsFromLegacy) incomingSettings.allowBuilds = {
|
|
565
|
-
...allowBuildsFromLegacy,
|
|
566
|
-
...incomingSettings.allowBuilds || {}
|
|
567
|
-
};
|
|
568
|
-
for (const key of PNPM_V11_REMOVED_SETTINGS) delete incomingSettings[key];
|
|
569
|
-
}
|
|
570
|
-
/**
|
|
571
|
-
* Resolve final compatibility target from user option and package manager hint.
|
|
572
|
-
*/
|
|
573
|
-
function resolveCompatibilityTarget(compatibility, packageManager) {
|
|
574
|
-
if (compatibility !== "auto") return compatibility;
|
|
575
|
-
const [, major] = (packageManager || "").match(/^pnpm@(\d+)(?:\.|$)/) || [];
|
|
576
|
-
return Number(major) >= 11 ? "v11" : "v10";
|
|
577
|
-
}
|
|
578
760
|
//#endregion
|
|
579
761
|
//#region src/cli.ts
|
|
580
762
|
const cli = cac(name);
|
|
581
|
-
cli.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)").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-clean-npmrc", "Disable removing pnpm settings in .npmrc file").option("--no-clean-package-json", "Disable removing pnpm fields in package.json").help();
|
|
763
|
+
cli.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-clean-npmrc", "Disable removing pnpm settings in .npmrc file").option("--no-clean-package-json", "Disable removing pnpm fields in package.json").help();
|
|
582
764
|
cli.command("").action(async (options) => {
|
|
583
765
|
try {
|
|
584
|
-
consola
|
|
585
|
-
consola
|
|
766
|
+
consola.log(`\n${bold(magenta(name))} ${dim(`v${version}`)}`);
|
|
767
|
+
consola.log(dim("\n--------------\n"));
|
|
586
768
|
await migratePnpmSettings(options);
|
|
587
|
-
consola
|
|
769
|
+
consola.success(green("pnpm settings migrate has finished"));
|
|
588
770
|
} catch (err) {
|
|
589
|
-
consola
|
|
590
|
-
if (err instanceof Error && err.stack) consola
|
|
771
|
+
consola.fail(red(String(err)));
|
|
772
|
+
if (err instanceof Error && err.stack) consola.fail(dim(err.stack?.split("\n").slice(1).join("\n")));
|
|
591
773
|
process.exit(1);
|
|
592
774
|
}
|
|
593
775
|
});
|