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