pnpm-settings-migrator 0.3.0 → 0.4.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 CHANGED
@@ -31,6 +31,18 @@ Current working directory.
31
31
 
32
32
  Sort keys when write `pnpm-workspace.yaml`.
33
33
 
34
+ ### `--compatibility`
35
+
36
+ - **Type**: `'auto' | 'v10' | 'v11'`
37
+ - **Default**: `'auto'`
38
+
39
+ Compatibility target for migrated settings:
40
+
41
+ - `auto`: infer from `packageManager` (`pnpm@11+` => `v11`, otherwise `v10`)
42
+ - `v10`: keep legacy settings as-is
43
+ - `v11`: normalize to v11-compatible settings (`allowBuilds`, `allowUnusedPatches`, etc.)
44
+ and migrate all non auth/registry `.npmrc` entries to `pnpm-workspace.yaml`
45
+
34
46
  ### `--strategy`
35
47
 
36
48
  - **Type**: `'discard' | 'merge' | 'overwrite'`
package/dist/cli.mjs CHANGED
@@ -13,7 +13,7 @@ import { readIniFile } from "read-ini-file";
13
13
  import { kebabCase } from "uncase";
14
14
  //#region package.json
15
15
  var name = "pnpm-settings-migrator";
16
- var version = "0.3.0";
16
+ var version = "0.4.0";
17
17
  //#endregion
18
18
  //#region src/constants.ts
19
19
  const NPMRC = ".npmrc";
@@ -50,6 +50,14 @@ const PNPM_SETTINGS_FIELDS = [
50
50
  "supportedArchitectures",
51
51
  "updateConfig"
52
52
  ];
53
+ const PNPM_V11_REMOVED_SETTINGS = [
54
+ "allowNonAppliedPatches",
55
+ "ignorePatchFailures",
56
+ "ignoredBuiltDependencies",
57
+ "neverBuiltDependencies",
58
+ "onlyBuiltDependencies",
59
+ "onlyBuiltDependenciesFile"
60
+ ];
53
61
  //#endregion
54
62
  //#region src/options.ts
55
63
  /**
@@ -58,6 +66,7 @@ const PNPM_SETTINGS_FIELDS = [
58
66
  const DEFAULT_OPTIONS = {
59
67
  cleanNpmrc: true,
60
68
  cleanPackageJson: true,
69
+ compatibility: "auto",
61
70
  cwd: process.cwd(),
62
71
  newlineBetween: true,
63
72
  sortKeys: false,
@@ -69,6 +78,11 @@ const VALID_STRATEGIES = [
69
78
  "merge",
70
79
  "overwrite"
71
80
  ];
81
+ const VALID_COMPATIBILITIES = [
82
+ "auto",
83
+ "v10",
84
+ "v11"
85
+ ];
72
86
  /**
73
87
  * Resolve and normalize migration options with defaults.
74
88
  *
@@ -93,6 +107,7 @@ const VALID_STRATEGIES = [
93
107
  function resolveOptions(options = {}) {
94
108
  return {
95
109
  cleanNpmrc: options.cleanNpmrc ?? DEFAULT_OPTIONS.cleanNpmrc,
110
+ compatibility: resolveCompatibility(options.compatibility),
96
111
  cwd: options.cwd ?? DEFAULT_OPTIONS.cwd,
97
112
  newlineBetween: options.newlineBetween ?? DEFAULT_OPTIONS.newlineBetween,
98
113
  sortKeys: options.sortKeys ?? DEFAULT_OPTIONS.sortKeys,
@@ -101,6 +116,11 @@ function resolveOptions(options = {}) {
101
116
  cleanPackageJson: options.cleanPackageJson ?? DEFAULT_OPTIONS.cleanPackageJson
102
117
  };
103
118
  }
119
+ function resolveCompatibility(compatibility) {
120
+ if (!compatibility) return DEFAULT_OPTIONS.compatibility;
121
+ if (VALID_COMPATIBILITIES.includes(compatibility)) return compatibility;
122
+ throw new Error(`Invalid compatibility: ${compatibility}. Expected one of: ${VALID_COMPATIBILITIES.join(", ")}`);
123
+ }
104
124
  function resolveStrategy(strategy) {
105
125
  if (!strategy) return DEFAULT_OPTIONS.strategy;
106
126
  if (VALID_STRATEGIES.includes(strategy)) return strategy;
@@ -216,6 +236,22 @@ function mergeWithArrayDedupe(existing, incoming) {
216
236
  }
217
237
  //#endregion
218
238
  //#region src/utils/npmrc.ts
239
+ const NPMRC_AUTH_OR_REGISTRY_KEYS = [
240
+ "_auth",
241
+ "_authtoken",
242
+ "_password",
243
+ "always-auth",
244
+ "ca",
245
+ "cafile",
246
+ "cert",
247
+ "certfile",
248
+ "email",
249
+ "key",
250
+ "keyfile",
251
+ "otp",
252
+ "tokenhelper",
253
+ "username"
254
+ ];
219
255
  /**
220
256
  * Remove pnpm-related settings from `.npmrc` file.
221
257
  *
@@ -234,9 +270,15 @@ function mergeWithArrayDedupe(existing, incoming) {
234
270
  * await pruneNpmrc('/path/to/.npmrc')
235
271
  * ```
236
272
  */
237
- async function pruneNpmrc(path) {
273
+ async function pruneNpmrc(path, compatibility = "v10", migratedKeys = []) {
238
274
  const pnpmSettingsFields = PNPM_SETTINGS_FIELDS.map((v) => kebabCase(v));
239
- await fsWriteFile(path, (await fsReadFile(path)).split(/\r?\n/).filter((line) => !pnpmSettingsFields.some((v) => line.trim().startsWith(v))).join("\n"));
275
+ const migratedKeySet = new Set(migratedKeys.map(normalizeNpmrcKey));
276
+ await fsWriteFile(path, (await fsReadFile(path)).split(/\r?\n/).filter((line) => {
277
+ const key = getNpmrcLineKey(line);
278
+ if (!key) return true;
279
+ if (compatibility === "v11") return !migratedKeySet.has(normalizeNpmrcKey(key));
280
+ return !pnpmSettingsFields.some((v) => key.startsWith(v));
281
+ }).join("\n"));
240
282
  }
241
283
  /**
242
284
  * Read and parse `.npmrc` file with camelCase key conversion.
@@ -257,9 +299,57 @@ async function pruneNpmrc(path) {
257
299
  * // config.allowBuilds, config.packageExtensions, etc.
258
300
  * ```
259
301
  */
302
+ /**
303
+ * Read `.npmrc` and return settings that should be migrated into workspace config.
304
+ *
305
+ * - `v10`: returns all keys (legacy whitelist filtering happens in caller).
306
+ * - `v11`: excludes auth/registry keys because pnpm still reads them from `.npmrc`.
307
+ */
308
+ async function readMigratableNpmrc(path, compatibility) {
309
+ const raw = await readIniFile(path);
310
+ if (compatibility === "v10") return {
311
+ keys: Object.keys(raw),
312
+ settings: camelcaseKeys(raw)
313
+ };
314
+ const migratable = {};
315
+ const keys = [];
316
+ for (const [key, value] of Object.entries(raw)) if (!isNpmrcAuthOrRegistryKey(key)) {
317
+ migratable[key] = value;
318
+ keys.push(key);
319
+ }
320
+ return {
321
+ keys,
322
+ settings: camelcaseKeys(migratable)
323
+ };
324
+ }
260
325
  async function readNpmrc(path) {
261
326
  return camelcaseKeys(await readIniFile(path));
262
327
  }
328
+ /**
329
+ * Extract key name from a raw `.npmrc` line.
330
+ */
331
+ function getNpmrcLineKey(line) {
332
+ const trimmed = line.trim();
333
+ if (!trimmed || trimmed.startsWith(";") || trimmed.startsWith("#")) return;
334
+ const index = trimmed.indexOf("=");
335
+ if (index <= 0) return;
336
+ return normalizeNpmrcKey(trimmed.slice(0, index));
337
+ }
338
+ /**
339
+ * Check whether a `.npmrc` key is auth/registry-related and should stay in v11.
340
+ */
341
+ function isNpmrcAuthOrRegistryKey(key) {
342
+ const normalized = normalizeNpmrcKey(key);
343
+ if (normalized.startsWith("//")) return true;
344
+ if (normalized === "registry" || normalized.endsWith(":registry")) return true;
345
+ return NPMRC_AUTH_OR_REGISTRY_KEYS.some((v) => normalized === v || normalized.endsWith(`:${v}`));
346
+ }
347
+ /**
348
+ * Normalize `.npmrc` key for case-insensitive matching.
349
+ */
350
+ function normalizeNpmrcKey(key) {
351
+ return key.trim().replace(/\[\]$/, "").toLowerCase();
352
+ }
263
353
  //#endregion
264
354
  //#region src/core.ts
265
355
  /**
@@ -327,7 +417,15 @@ async function migratePnpmSettings(rawOptions = {}) {
327
417
  pnpmWorkspaceYamlIndent = detectIndent(content).amount;
328
418
  pnpmWorkspaceYamlObject = parse(content);
329
419
  }
330
- const pnpmSettingsInNpmrc = npmrcExists ? pick(await readNpmrc(npmrcPath), PNPM_SETTINGS_FIELDS) : {};
420
+ const compatibility = resolveCompatibilityTarget(options.compatibility, packageJsonObject.packageManager);
421
+ const npmrcMigratable = npmrcExists ? compatibility === "v10" ? {
422
+ keys: PNPM_SETTINGS_FIELDS,
423
+ settings: pick(await readNpmrc(npmrcPath), PNPM_SETTINGS_FIELDS)
424
+ } : await readMigratableNpmrc(npmrcPath, compatibility) : {
425
+ keys: [],
426
+ settings: {}
427
+ };
428
+ const pnpmSettingsInNpmrc = npmrcMigratable.settings;
331
429
  const hasPnpmInPackageJson = !!packageJsonObject.pnpm;
332
430
  const hasResolutions = options.yarnResolutions && !!packageJsonObject.resolutions;
333
431
  const hasNpmrcSettings = Object.keys(pnpmSettingsInNpmrc).length > 0;
@@ -344,6 +442,7 @@ async function migratePnpmSettings(rawOptions = {}) {
344
442
  ...pnpmSettingsInNpmrc,
345
443
  ...pnpmSettingsInPackageJson
346
444
  };
445
+ normalizeIncomingSettings(incomingSettings, compatibility);
347
446
  const pnpmWorkspaceResult = mergeByStrategy(pnpmWorkspaceYamlObject, incomingSettings, options.strategy);
348
447
  const yamlDocument = new Document({}, { sortMapEntries: options.sortKeys });
349
448
  Object.entries(pnpmWorkspaceResult).forEach(([key, value]) => {
@@ -354,7 +453,7 @@ async function migratePnpmSettings(rawOptions = {}) {
354
453
  });
355
454
  const yamlContent = yamlDocument.toString({ indent: pnpmWorkspaceYamlIndent });
356
455
  await fsWriteFile(pnpmWorkspaceYamlPath, options.newlineBetween ? yamlContent.replace(/\n(?=[^\s#][^:\n]*:)/g, "\n\n") : yamlContent);
357
- if (npmrcExists && options.cleanNpmrc) await pruneNpmrc(npmrcPath);
456
+ if (npmrcExists && options.cleanNpmrc) await pruneNpmrc(npmrcPath, compatibility, npmrcMigratable.keys);
358
457
  if (packageJsonExists && options.cleanPackageJson && (packageJsonObject.pnpm || packageJsonObject.resolutions)) {
359
458
  delete packageJsonObject.pnpm;
360
459
  if (options.yarnResolutions) delete packageJsonObject.resolutions;
@@ -365,10 +464,41 @@ async function migratePnpmSettings(rawOptions = {}) {
365
464
  throw err;
366
465
  }
367
466
  }
467
+ /**
468
+ * Build v11 `allowBuilds` map from legacy build-script settings.
469
+ */
470
+ function collectAllowBuildsFromLegacy(incomingSettings) {
471
+ const allowBuilds = {};
472
+ for (const name of incomingSettings.onlyBuiltDependencies || []) allowBuilds[name] = true;
473
+ for (const name of incomingSettings.ignoredBuiltDependencies || []) allowBuilds[name] = false;
474
+ for (const name of incomingSettings.neverBuiltDependencies || []) allowBuilds[name] = false;
475
+ return Object.keys(allowBuilds).length ? allowBuilds : void 0;
476
+ }
477
+ /**
478
+ * Normalize incoming settings according to compatibility target.
479
+ */
480
+ function normalizeIncomingSettings(incomingSettings, compatibility) {
481
+ if (compatibility === "v10") return;
482
+ if (incomingSettings.allowNonAppliedPatches !== void 0) incomingSettings.allowUnusedPatches = incomingSettings.allowUnusedPatches ?? incomingSettings.allowNonAppliedPatches;
483
+ const allowBuildsFromLegacy = collectAllowBuildsFromLegacy(incomingSettings);
484
+ if (allowBuildsFromLegacy) incomingSettings.allowBuilds = {
485
+ ...allowBuildsFromLegacy,
486
+ ...incomingSettings.allowBuilds || {}
487
+ };
488
+ for (const key of PNPM_V11_REMOVED_SETTINGS) delete incomingSettings[key];
489
+ }
490
+ /**
491
+ * Resolve final compatibility target from user option and package manager hint.
492
+ */
493
+ function resolveCompatibilityTarget(compatibility, packageManager) {
494
+ if (compatibility !== "auto") return compatibility;
495
+ const [, major] = (packageManager || "").match(/^pnpm@(\d+)(?:\.|$)/) || [];
496
+ return Number(major) >= 11 ? "v11" : "v10";
497
+ }
368
498
  //#endregion
369
499
  //#region src/cli.ts
370
500
  const cli = cac(name);
371
- cli.version(version).option("--cwd [cwd]", "Current working directory").option("--sort-keys", "Sort keys when write pnpm-workspace.yaml").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();
501
+ 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("--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();
372
502
  cli.command("").action(async (options) => {
373
503
  try {
374
504
  consola$1.log(`\n${bold(magenta(name))} ${dim(`v${version}`)}`);
package/dist/index.d.mts CHANGED
@@ -2,7 +2,11 @@ import { PnpmSettings } from "@pnpm/types";
2
2
 
3
3
  //#region src/types.d.ts
4
4
  /**
5
- * Merge strategy for combining pnpm settings
5
+ * Compatibility target used by the migrator.
6
+ */
7
+ type CompatibilityTarget = 'auto' | 'v10' | 'v11';
8
+ /**
9
+ * Merge strategy for combining pnpm settings.
6
10
  */
7
11
  type MergeStrategy = 'discard' | 'merge' | 'overwrite';
8
12
  /**
@@ -21,6 +25,15 @@ interface Options {
21
25
  * @default true
22
26
  */
23
27
  cleanPackageJson?: boolean;
28
+ /**
29
+ * pnpm compatibility target.
30
+ * - `auto`: detect from `packageManager` (fallback to `v10`)
31
+ * - `v10`: keep legacy v10 settings
32
+ * - `v11`: normalize to v11-compatible settings
33
+ *
34
+ * @default 'auto'
35
+ */
36
+ compatibility?: CompatibilityTarget;
24
37
  /**
25
38
  * Current working directory
26
39
  *
@@ -68,6 +81,10 @@ interface PackageJson {
68
81
  * @compatibility npm, bun
69
82
  */
70
83
  overrides?: Record<string, string>;
84
+ /**
85
+ * Package manager declaration, for example `pnpm@11.0.0`
86
+ */
87
+ packageManager?: string;
71
88
  /**
72
89
  * pnpm settings
73
90
  */
@@ -89,10 +106,18 @@ type NpmRC = Record<string, any>;
89
106
  * @see {@link https://github.com/pnpm/pnpm/blob/main/core/types/CHANGELOG.md#major-changes}
90
107
  */
91
108
  interface PnpmSettingsDeprecated {
109
+ /**
110
+ * @deprecated
111
+ */
112
+ allowNonAppliedPatches?: boolean;
92
113
  /**
93
114
  * @deprecated
94
115
  */
95
116
  ignoredBuiltDependencies?: string[];
117
+ /**
118
+ * @deprecated
119
+ */
120
+ ignorePatchFailures?: boolean;
96
121
  /**
97
122
  * @deprecated
98
123
  */
@@ -173,4 +198,4 @@ declare function migratePnpmSettings(rawOptions?: Options): Promise<void>;
173
198
  */
174
199
  declare function resolveOptions(options?: Options): Required<Options>;
175
200
  //#endregion
176
- export { MergeStrategy, NpmRC, Options, PackageJson, PnpmSettingsDeprecated, PnpmWorkspace, PnpmWorkspaceLegacy, migratePnpmSettings, resolveOptions };
201
+ export { CompatibilityTarget, MergeStrategy, NpmRC, Options, PackageJson, PnpmSettingsDeprecated, PnpmWorkspace, PnpmWorkspaceLegacy, migratePnpmSettings, resolveOptions };
package/dist/index.mjs CHANGED
@@ -45,6 +45,14 @@ const PNPM_SETTINGS_FIELDS = [
45
45
  "supportedArchitectures",
46
46
  "updateConfig"
47
47
  ];
48
+ const PNPM_V11_REMOVED_SETTINGS = [
49
+ "allowNonAppliedPatches",
50
+ "ignorePatchFailures",
51
+ "ignoredBuiltDependencies",
52
+ "neverBuiltDependencies",
53
+ "onlyBuiltDependencies",
54
+ "onlyBuiltDependenciesFile"
55
+ ];
48
56
  //#endregion
49
57
  //#region src/options.ts
50
58
  /**
@@ -53,6 +61,7 @@ const PNPM_SETTINGS_FIELDS = [
53
61
  const DEFAULT_OPTIONS = {
54
62
  cleanNpmrc: true,
55
63
  cleanPackageJson: true,
64
+ compatibility: "auto",
56
65
  cwd: process.cwd(),
57
66
  newlineBetween: true,
58
67
  sortKeys: false,
@@ -64,6 +73,11 @@ const VALID_STRATEGIES = [
64
73
  "merge",
65
74
  "overwrite"
66
75
  ];
76
+ const VALID_COMPATIBILITIES = [
77
+ "auto",
78
+ "v10",
79
+ "v11"
80
+ ];
67
81
  /**
68
82
  * Resolve and normalize migration options with defaults.
69
83
  *
@@ -88,6 +102,7 @@ const VALID_STRATEGIES = [
88
102
  function resolveOptions(options = {}) {
89
103
  return {
90
104
  cleanNpmrc: options.cleanNpmrc ?? DEFAULT_OPTIONS.cleanNpmrc,
105
+ compatibility: resolveCompatibility(options.compatibility),
91
106
  cwd: options.cwd ?? DEFAULT_OPTIONS.cwd,
92
107
  newlineBetween: options.newlineBetween ?? DEFAULT_OPTIONS.newlineBetween,
93
108
  sortKeys: options.sortKeys ?? DEFAULT_OPTIONS.sortKeys,
@@ -96,6 +111,11 @@ function resolveOptions(options = {}) {
96
111
  cleanPackageJson: options.cleanPackageJson ?? DEFAULT_OPTIONS.cleanPackageJson
97
112
  };
98
113
  }
114
+ function resolveCompatibility(compatibility) {
115
+ if (!compatibility) return DEFAULT_OPTIONS.compatibility;
116
+ if (VALID_COMPATIBILITIES.includes(compatibility)) return compatibility;
117
+ throw new Error(`Invalid compatibility: ${compatibility}. Expected one of: ${VALID_COMPATIBILITIES.join(", ")}`);
118
+ }
99
119
  function resolveStrategy(strategy) {
100
120
  if (!strategy) return DEFAULT_OPTIONS.strategy;
101
121
  if (VALID_STRATEGIES.includes(strategy)) return strategy;
@@ -211,6 +231,22 @@ function mergeWithArrayDedupe(existing, incoming) {
211
231
  }
212
232
  //#endregion
213
233
  //#region src/utils/npmrc.ts
234
+ const NPMRC_AUTH_OR_REGISTRY_KEYS = [
235
+ "_auth",
236
+ "_authtoken",
237
+ "_password",
238
+ "always-auth",
239
+ "ca",
240
+ "cafile",
241
+ "cert",
242
+ "certfile",
243
+ "email",
244
+ "key",
245
+ "keyfile",
246
+ "otp",
247
+ "tokenhelper",
248
+ "username"
249
+ ];
214
250
  /**
215
251
  * Remove pnpm-related settings from `.npmrc` file.
216
252
  *
@@ -229,9 +265,15 @@ function mergeWithArrayDedupe(existing, incoming) {
229
265
  * await pruneNpmrc('/path/to/.npmrc')
230
266
  * ```
231
267
  */
232
- async function pruneNpmrc(path) {
268
+ async function pruneNpmrc(path, compatibility = "v10", migratedKeys = []) {
233
269
  const pnpmSettingsFields = PNPM_SETTINGS_FIELDS.map((v) => kebabCase(v));
234
- await fsWriteFile(path, (await fsReadFile(path)).split(/\r?\n/).filter((line) => !pnpmSettingsFields.some((v) => line.trim().startsWith(v))).join("\n"));
270
+ const migratedKeySet = new Set(migratedKeys.map(normalizeNpmrcKey));
271
+ await fsWriteFile(path, (await fsReadFile(path)).split(/\r?\n/).filter((line) => {
272
+ const key = getNpmrcLineKey(line);
273
+ if (!key) return true;
274
+ if (compatibility === "v11") return !migratedKeySet.has(normalizeNpmrcKey(key));
275
+ return !pnpmSettingsFields.some((v) => key.startsWith(v));
276
+ }).join("\n"));
235
277
  }
236
278
  /**
237
279
  * Read and parse `.npmrc` file with camelCase key conversion.
@@ -252,9 +294,57 @@ async function pruneNpmrc(path) {
252
294
  * // config.allowBuilds, config.packageExtensions, etc.
253
295
  * ```
254
296
  */
297
+ /**
298
+ * Read `.npmrc` and return settings that should be migrated into workspace config.
299
+ *
300
+ * - `v10`: returns all keys (legacy whitelist filtering happens in caller).
301
+ * - `v11`: excludes auth/registry keys because pnpm still reads them from `.npmrc`.
302
+ */
303
+ async function readMigratableNpmrc(path, compatibility) {
304
+ const raw = await readIniFile(path);
305
+ if (compatibility === "v10") return {
306
+ keys: Object.keys(raw),
307
+ settings: camelcaseKeys(raw)
308
+ };
309
+ const migratable = {};
310
+ const keys = [];
311
+ for (const [key, value] of Object.entries(raw)) if (!isNpmrcAuthOrRegistryKey(key)) {
312
+ migratable[key] = value;
313
+ keys.push(key);
314
+ }
315
+ return {
316
+ keys,
317
+ settings: camelcaseKeys(migratable)
318
+ };
319
+ }
255
320
  async function readNpmrc(path) {
256
321
  return camelcaseKeys(await readIniFile(path));
257
322
  }
323
+ /**
324
+ * Extract key name from a raw `.npmrc` line.
325
+ */
326
+ function getNpmrcLineKey(line) {
327
+ const trimmed = line.trim();
328
+ if (!trimmed || trimmed.startsWith(";") || trimmed.startsWith("#")) return;
329
+ const index = trimmed.indexOf("=");
330
+ if (index <= 0) return;
331
+ return normalizeNpmrcKey(trimmed.slice(0, index));
332
+ }
333
+ /**
334
+ * Check whether a `.npmrc` key is auth/registry-related and should stay in v11.
335
+ */
336
+ function isNpmrcAuthOrRegistryKey(key) {
337
+ const normalized = normalizeNpmrcKey(key);
338
+ if (normalized.startsWith("//")) return true;
339
+ if (normalized === "registry" || normalized.endsWith(":registry")) return true;
340
+ return NPMRC_AUTH_OR_REGISTRY_KEYS.some((v) => normalized === v || normalized.endsWith(`:${v}`));
341
+ }
342
+ /**
343
+ * Normalize `.npmrc` key for case-insensitive matching.
344
+ */
345
+ function normalizeNpmrcKey(key) {
346
+ return key.trim().replace(/\[\]$/, "").toLowerCase();
347
+ }
258
348
  //#endregion
259
349
  //#region src/core.ts
260
350
  /**
@@ -322,7 +412,15 @@ async function migratePnpmSettings(rawOptions = {}) {
322
412
  pnpmWorkspaceYamlIndent = detectIndent(content).amount;
323
413
  pnpmWorkspaceYamlObject = parse(content);
324
414
  }
325
- const pnpmSettingsInNpmrc = npmrcExists ? pick(await readNpmrc(npmrcPath), PNPM_SETTINGS_FIELDS) : {};
415
+ const compatibility = resolveCompatibilityTarget(options.compatibility, packageJsonObject.packageManager);
416
+ const npmrcMigratable = npmrcExists ? compatibility === "v10" ? {
417
+ keys: PNPM_SETTINGS_FIELDS,
418
+ settings: pick(await readNpmrc(npmrcPath), PNPM_SETTINGS_FIELDS)
419
+ } : await readMigratableNpmrc(npmrcPath, compatibility) : {
420
+ keys: [],
421
+ settings: {}
422
+ };
423
+ const pnpmSettingsInNpmrc = npmrcMigratable.settings;
326
424
  const hasPnpmInPackageJson = !!packageJsonObject.pnpm;
327
425
  const hasResolutions = options.yarnResolutions && !!packageJsonObject.resolutions;
328
426
  const hasNpmrcSettings = Object.keys(pnpmSettingsInNpmrc).length > 0;
@@ -339,6 +437,7 @@ async function migratePnpmSettings(rawOptions = {}) {
339
437
  ...pnpmSettingsInNpmrc,
340
438
  ...pnpmSettingsInPackageJson
341
439
  };
440
+ normalizeIncomingSettings(incomingSettings, compatibility);
342
441
  const pnpmWorkspaceResult = mergeByStrategy(pnpmWorkspaceYamlObject, incomingSettings, options.strategy);
343
442
  const yamlDocument = new Document({}, { sortMapEntries: options.sortKeys });
344
443
  Object.entries(pnpmWorkspaceResult).forEach(([key, value]) => {
@@ -349,7 +448,7 @@ async function migratePnpmSettings(rawOptions = {}) {
349
448
  });
350
449
  const yamlContent = yamlDocument.toString({ indent: pnpmWorkspaceYamlIndent });
351
450
  await fsWriteFile(pnpmWorkspaceYamlPath, options.newlineBetween ? yamlContent.replace(/\n(?=[^\s#][^:\n]*:)/g, "\n\n") : yamlContent);
352
- if (npmrcExists && options.cleanNpmrc) await pruneNpmrc(npmrcPath);
451
+ if (npmrcExists && options.cleanNpmrc) await pruneNpmrc(npmrcPath, compatibility, npmrcMigratable.keys);
353
452
  if (packageJsonExists && options.cleanPackageJson && (packageJsonObject.pnpm || packageJsonObject.resolutions)) {
354
453
  delete packageJsonObject.pnpm;
355
454
  if (options.yarnResolutions) delete packageJsonObject.resolutions;
@@ -360,5 +459,36 @@ async function migratePnpmSettings(rawOptions = {}) {
360
459
  throw err;
361
460
  }
362
461
  }
462
+ /**
463
+ * Build v11 `allowBuilds` map from legacy build-script settings.
464
+ */
465
+ function collectAllowBuildsFromLegacy(incomingSettings) {
466
+ const allowBuilds = {};
467
+ for (const name of incomingSettings.onlyBuiltDependencies || []) allowBuilds[name] = true;
468
+ for (const name of incomingSettings.ignoredBuiltDependencies || []) allowBuilds[name] = false;
469
+ for (const name of incomingSettings.neverBuiltDependencies || []) allowBuilds[name] = false;
470
+ return Object.keys(allowBuilds).length ? allowBuilds : void 0;
471
+ }
472
+ /**
473
+ * Normalize incoming settings according to compatibility target.
474
+ */
475
+ function normalizeIncomingSettings(incomingSettings, compatibility) {
476
+ if (compatibility === "v10") return;
477
+ if (incomingSettings.allowNonAppliedPatches !== void 0) incomingSettings.allowUnusedPatches = incomingSettings.allowUnusedPatches ?? incomingSettings.allowNonAppliedPatches;
478
+ const allowBuildsFromLegacy = collectAllowBuildsFromLegacy(incomingSettings);
479
+ if (allowBuildsFromLegacy) incomingSettings.allowBuilds = {
480
+ ...allowBuildsFromLegacy,
481
+ ...incomingSettings.allowBuilds || {}
482
+ };
483
+ for (const key of PNPM_V11_REMOVED_SETTINGS) delete incomingSettings[key];
484
+ }
485
+ /**
486
+ * Resolve final compatibility target from user option and package manager hint.
487
+ */
488
+ function resolveCompatibilityTarget(compatibility, packageManager) {
489
+ if (compatibility !== "auto") return compatibility;
490
+ const [, major] = (packageManager || "").match(/^pnpm@(\d+)(?:\.|$)/) || [];
491
+ return Number(major) >= 11 ? "v11" : "v10";
492
+ }
363
493
  //#endregion
364
494
  export { migratePnpmSettings, resolveOptions };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pnpm-settings-migrator",
3
3
  "type": "module",
4
- "version": "0.3.0",
4
+ "version": "0.4.0",
5
5
  "description": "Move pnpm settings from `pnpm` field in `package.json` and `.npmrc` file to `pnpm-workspace.yaml`",
6
6
  "keywords": [
7
7
  "migrator",
@@ -27,7 +27,6 @@
27
27
  },
28
28
  "main": "./dist/index.mjs",
29
29
  "types": "./dist/index.d.mts",
30
- "bin": "./bin.mjs",
31
30
  "files": [
32
31
  "bin.mjs",
33
32
  "dist"
@@ -37,8 +36,8 @@
37
36
  },
38
37
  "sideEffects": false,
39
38
  "dependencies": {
40
- "@ntnyq/utils": "^0.11.6",
41
- "@pnpm/types": "^1100.0.0",
39
+ "@ntnyq/utils": "^0.13.2",
40
+ "@pnpm/types": "^1101.0.0",
42
41
  "cac": "^7.0.0",
43
42
  "camelcase-keys": "^10.0.2",
44
43
  "consola": "^3.4.2",
@@ -47,21 +46,21 @@
47
46
  "pathe": "^2.0.3",
48
47
  "read-ini-file": "^5.0.0",
49
48
  "uncase": "^0.2.0",
50
- "yaml": "^2.8.3"
49
+ "yaml": "^2.8.4"
51
50
  },
52
51
  "devDependencies": {
53
- "@ntnyq/eslint-config": "^6.0.1",
54
- "@types/node": "^25.6.0",
55
- "@typescript/native-preview": "^7.0.0-dev.20260410.1",
56
- "bumpp": "^11.0.1",
57
- "eslint": "^10.2.0",
52
+ "@ntnyq/eslint-config": "^6.1.3",
53
+ "@types/node": "^25.6.2",
54
+ "@typescript/native-preview": "^7.0.0-dev.20260510.1",
55
+ "bumpp": "^11.1.0",
56
+ "eslint": "^10.3.0",
58
57
  "husky": "^9.1.7",
59
58
  "nano-staged": "^1.0.2",
60
59
  "npm-run-all2": "^8.0.4",
61
- "oxfmt": "^0.44.0",
62
- "tsdown": "^0.21.7",
63
- "typescript": "^6.0.2",
64
- "vitest": "^4.1.4"
60
+ "oxfmt": "^0.48.0",
61
+ "tsdown": "^0.22.0",
62
+ "typescript": "^6.0.3",
63
+ "vitest": "^4.1.5"
65
64
  },
66
65
  "nano-staged": {
67
66
  "*.{js,ts,mjs,tsx,md,yml,yaml,toml,json}": "eslint --fix",
@@ -78,5 +77,8 @@
78
77
  "release:version": "bumpp",
79
78
  "test": "vitest",
80
79
  "typecheck": "tsgo --noEmit"
80
+ },
81
+ "bin": {
82
+ "pnpm-settings-migrator": "./bin.mjs"
81
83
  }
82
84
  }