pnpm-settings-migrator 0.11.0 → 0.12.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 +27 -3
- package/dist/cli.mjs +150 -31
- package/dist/index.d.mts +51 -7
- package/dist/index.mjs +149 -30
- package/package.json +9 -8
package/README.md
CHANGED
|
@@ -132,6 +132,24 @@ For pnpm 12.4.0 and later stable v12 releases, the migrator additionally accepts
|
|
|
132
132
|
`cargoTargetDir`. On older or unconfirmed targets, a `tasks` object containing
|
|
133
133
|
these fields stays in its source as a whole.
|
|
134
134
|
|
|
135
|
+
For pnpm 12.5.0 and later stable v12 releases, the migrator also accepts
|
|
136
|
+
`concurrencyGroups`, task `concurrencyGroup`, platform lists in
|
|
137
|
+
`supportedArchitectures`, Python `versions` / `overrides` / `constraints`, and
|
|
138
|
+
`registries` declarations with `ecosystem`. Python registry `packages` routes
|
|
139
|
+
require 12.5.1. Unsupported nested settings stay in their source as a whole.
|
|
140
|
+
The legacy OS/CPU/libc architecture mapping continues to work.
|
|
141
|
+
|
|
142
|
+
pnpm 12.5 removes `python.indexUrl` and `cargo.indexUrl`. Settings containing
|
|
143
|
+
these fields are retained with an incompatibility warning; existing workspaces
|
|
144
|
+
containing them fail validation before any files are written. Replace them with
|
|
145
|
+
URL-keyed `registries` entries using `ecosystem: pypi` or `ecosystem: cargo`.
|
|
146
|
+
The registry prefix `pkg` is reserved from 12.5 onward. Tool download mirrors
|
|
147
|
+
under `tools` belong in global `config.yaml` or `PNPM_CONFIG_TOOLS` and are
|
|
148
|
+
never migrated into the project workspace.
|
|
149
|
+
|
|
150
|
+
On the v11 line, `trustPolicyExcludePrune` requires a confirmed stable version
|
|
151
|
+
from 11.27.0 onward; v12 support still starts at 12.4.0.
|
|
152
|
+
|
|
135
153
|
Automated v10 to v11 conversions include:
|
|
136
154
|
|
|
137
155
|
- `managePackageManagerVersions`, `packageManagerStrict`, and
|
|
@@ -151,6 +169,9 @@ Notes:
|
|
|
151
169
|
- `.npmrc` migration is aligned with the target pnpm workspace schema. Unknown
|
|
152
170
|
keys and settings supported only by a different pnpm version stay in `.npmrc`
|
|
153
171
|
with a warning.
|
|
172
|
+
- Scalar list settings such as `public-hoist-pattern=*eslint*` become YAML
|
|
173
|
+
arrays (`publicHoistPattern: ['*eslint*']`). Existing arrays and patterns
|
|
174
|
+
containing commas are preserved without splitting their values.
|
|
154
175
|
- In `v11` and `v12`, auth/registry keys and project-refused machine settings
|
|
155
176
|
such as `globalDir`, `stateDir`, `configDir`, and `scope` stay in `.npmrc`.
|
|
156
177
|
- Registry declarations containing embedded credentials or dynamic `${...}`
|
|
@@ -177,6 +198,8 @@ Notes:
|
|
|
177
198
|
resolutions remain in their original source.
|
|
178
199
|
Unrecognized, refused, incompatible, or otherwise unsupported
|
|
179
200
|
`package.json#pnpm` child keys remain in `package.json`.
|
|
201
|
+
INI sections in `.npmrc` are retained conservatively; their child names are
|
|
202
|
+
never treated as root keys during cleanup.
|
|
180
203
|
- Runtime migration writes `devEngines.runtime` before removing a workspace
|
|
181
204
|
runtime declaration. A failed write preserves source settings for retry.
|
|
182
205
|
If an existing runtime declaration conflicts with a workspace runtime, migration
|
|
@@ -191,9 +214,10 @@ Notes:
|
|
|
191
214
|
- The migrator does not update the `packageManager` version, CI environment variables,
|
|
192
215
|
shell setup, or pnpm commands in scripts. When `packageManager` still pins pnpm 10,
|
|
193
216
|
pass `--compatibility v11` explicitly and update the pin separately.
|
|
194
|
-
- Compatibility checks cover pnpm 11.
|
|
195
|
-
11.
|
|
196
|
-
|
|
217
|
+
- Compatibility checks cover pnpm 11.27.1 and 12.5.1, retaining 11.25.0,
|
|
218
|
+
11.26.0, 12.2.1, 12.3.4, 12.4.0, 12.4.2, and 12.5.0 regressions.
|
|
219
|
+
As audited on 2026-09-22, npm `latest` is 12.5.1 and `latest-11` is 11.27.1;
|
|
220
|
+
the project is pinned to 12.5.1. Its removed `pnpm install --resolution-only` CLI
|
|
197
221
|
flag is outside this settings migrator's scope; replace it with
|
|
198
222
|
`pnpm peers check` in scripts before upgrading.
|
|
199
223
|
|
package/dist/cli.mjs
CHANGED
|
@@ -110,6 +110,30 @@ const NPMRC_AUTH_OR_REGISTRY_KEYS = [
|
|
|
110
110
|
* Pattern for pnpm's channel-specific Node.js mirror keys.
|
|
111
111
|
*/
|
|
112
112
|
const NODE_MIRROR_KEY_PATTERN = /^node-mirror:(?<channel>.+)$/iu;
|
|
113
|
+
/**
|
|
114
|
+
* List settings accepting a single string in legacy `.npmrc` files.
|
|
115
|
+
* Workspace YAML requires these values to be arrays.
|
|
116
|
+
* Legacy build permission lists keep their explicit array validation.
|
|
117
|
+
*/
|
|
118
|
+
const NPMRC_STRING_ARRAY_SETTINGS = [
|
|
119
|
+
"changedFilesIgnorePattern",
|
|
120
|
+
"externalDependencies",
|
|
121
|
+
"extraBinPaths",
|
|
122
|
+
"filter",
|
|
123
|
+
"filterProd",
|
|
124
|
+
"gitShallowHosts",
|
|
125
|
+
"hoistPattern",
|
|
126
|
+
"ignoredOptionalDependencies",
|
|
127
|
+
"mergeGitBranchLockfilesBranchPattern",
|
|
128
|
+
"minimumReleaseAgeExclude",
|
|
129
|
+
"packages",
|
|
130
|
+
"publicHoistPattern",
|
|
131
|
+
"requiredScripts",
|
|
132
|
+
"syncInjectedDepsAfterScripts",
|
|
133
|
+
"testPattern",
|
|
134
|
+
"trustPolicyExclude",
|
|
135
|
+
"workspacePackagePatterns"
|
|
136
|
+
];
|
|
113
137
|
//#endregion
|
|
114
138
|
//#region src/constants/pnpm-v10.ts
|
|
115
139
|
/**
|
|
@@ -298,6 +322,7 @@ const PNPM_PROJECT_REFUSED_SETTINGS = [
|
|
|
298
322
|
"scope",
|
|
299
323
|
"selectedProjectsGraph",
|
|
300
324
|
"stateDir",
|
|
325
|
+
"tools",
|
|
301
326
|
"userConfig",
|
|
302
327
|
"userconfig",
|
|
303
328
|
"wantedPackageManager",
|
|
@@ -586,32 +611,64 @@ const PNPM_WORKSPACE_YAML = "pnpm-workspace.yaml";
|
|
|
586
611
|
//#endregion
|
|
587
612
|
//#region src/constants/pnpm-capabilities.ts
|
|
588
613
|
/**
|
|
614
|
+
* Release introducing platform lists, registry ecosystems, and Python options.
|
|
615
|
+
*/
|
|
616
|
+
const PNPM_V12_5_MINIMUM_VERSION = [
|
|
617
|
+
12,
|
|
618
|
+
5,
|
|
619
|
+
0
|
|
620
|
+
];
|
|
621
|
+
/**
|
|
622
|
+
* Release introducing package routes in Python registry declarations.
|
|
623
|
+
*/
|
|
624
|
+
const PNPM_V12_5_1_MINIMUM_VERSION = [
|
|
625
|
+
12,
|
|
626
|
+
5,
|
|
627
|
+
1
|
|
628
|
+
];
|
|
629
|
+
/**
|
|
589
630
|
* Version-gated settings. Add a release entry only when supported fields change.
|
|
590
631
|
*
|
|
591
632
|
* @see https://github.com/pnpm/pnpm/blob/v12.4.0/pnpm/crates/config/src/workspace_yaml.rs
|
|
592
633
|
*/
|
|
593
|
-
const PNPM_SETTINGS_CAPABILITIES = [
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
"
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
634
|
+
const PNPM_SETTINGS_CAPABILITIES = [
|
|
635
|
+
{
|
|
636
|
+
minimumVersion: [
|
|
637
|
+
11,
|
|
638
|
+
27,
|
|
639
|
+
0
|
|
640
|
+
],
|
|
641
|
+
workspaceFields: ["trustPolicyExcludePrune"],
|
|
642
|
+
taskFields: []
|
|
643
|
+
},
|
|
644
|
+
{
|
|
645
|
+
minimumVersion: [
|
|
646
|
+
12,
|
|
647
|
+
4,
|
|
648
|
+
0
|
|
649
|
+
],
|
|
650
|
+
workspaceFields: [
|
|
651
|
+
"cargo",
|
|
652
|
+
"packageConfigs",
|
|
653
|
+
"pipelineBase",
|
|
654
|
+
"pipelines",
|
|
655
|
+
"python",
|
|
656
|
+
"trustPolicyExcludePrune"
|
|
657
|
+
],
|
|
658
|
+
taskFields: [
|
|
659
|
+
"cache",
|
|
660
|
+
"cargoTargetDir",
|
|
661
|
+
"env",
|
|
662
|
+
"inputs",
|
|
663
|
+
"outputs"
|
|
664
|
+
]
|
|
665
|
+
},
|
|
666
|
+
{
|
|
667
|
+
minimumVersion: PNPM_V12_5_MINIMUM_VERSION,
|
|
668
|
+
workspaceFields: ["concurrencyGroups"],
|
|
669
|
+
taskFields: ["concurrencyGroup"]
|
|
670
|
+
}
|
|
671
|
+
];
|
|
615
672
|
/**
|
|
616
673
|
* Known workspace fields that require a confirmed version.
|
|
617
674
|
*/
|
|
@@ -761,8 +818,13 @@ function resolveOptions(options = {}) {
|
|
|
761
818
|
function discardMerge(priority, fallback) {
|
|
762
819
|
const result = { ...priority };
|
|
763
820
|
for (const [key, fallbackValue] of Object.entries(fallback)) {
|
|
764
|
-
const priorityValue = result[key];
|
|
765
|
-
if (isUndefined(priorityValue)) result
|
|
821
|
+
const priorityValue = Object.hasOwn(result, key) ? result[key] : void 0;
|
|
822
|
+
if (isUndefined(priorityValue)) Object.defineProperty(result, key, {
|
|
823
|
+
configurable: true,
|
|
824
|
+
enumerable: true,
|
|
825
|
+
value: fallbackValue,
|
|
826
|
+
writable: true
|
|
827
|
+
});
|
|
766
828
|
else if (isPlainObject(priorityValue) && isPlainObject(fallbackValue)) result[key] = discardMerge(priorityValue, fallbackValue);
|
|
767
829
|
}
|
|
768
830
|
return result;
|
|
@@ -782,8 +844,13 @@ function discardMerge(priority, fallback) {
|
|
|
782
844
|
function mergeWithArrayDedupe(existing, incoming) {
|
|
783
845
|
const result = { ...existing };
|
|
784
846
|
for (const [key, incomingValue] of Object.entries(incoming)) {
|
|
785
|
-
const existingValue = result[key];
|
|
786
|
-
if (isUndefined(existingValue)) result
|
|
847
|
+
const existingValue = Object.hasOwn(result, key) ? result[key] : void 0;
|
|
848
|
+
if (isUndefined(existingValue)) Object.defineProperty(result, key, {
|
|
849
|
+
configurable: true,
|
|
850
|
+
enumerable: true,
|
|
851
|
+
value: incomingValue,
|
|
852
|
+
writable: true
|
|
853
|
+
});
|
|
787
854
|
else if (Array.isArray(existingValue) && Array.isArray(incomingValue)) {
|
|
788
855
|
const items = Array.from(/* @__PURE__ */ new Set([...existingValue, ...incomingValue]));
|
|
789
856
|
result[key] = items.filter((item, index) => !item || typeof item !== "object" || !items.slice(0, index).some((previous) => isDeepStrictEqual(previous, item)));
|
|
@@ -863,6 +930,41 @@ function formatSettingsIssues(options) {
|
|
|
863
930
|
return warnings;
|
|
864
931
|
}
|
|
865
932
|
//#endregion
|
|
933
|
+
//#region src/features/settings/versioned-values.ts
|
|
934
|
+
/**
|
|
935
|
+
* Check value shapes that changed within a pnpm major release.
|
|
936
|
+
*
|
|
937
|
+
* Reject the whole setting so cleanup cannot discard unsupported nested values.
|
|
938
|
+
*
|
|
939
|
+
* @param key - Canonical workspace setting name
|
|
940
|
+
* @param value - Incoming or existing setting value
|
|
941
|
+
* @param target - Confirmed target version and major schema
|
|
942
|
+
*
|
|
943
|
+
* @returns Whether the value uses a shape unsupported by the selected release
|
|
944
|
+
*/
|
|
945
|
+
function hasIncompatibleSettingValue(key, value, target) {
|
|
946
|
+
const supportsV12_5 = supportsMinimumVersion(target.version, PNPM_V12_5_MINIMUM_VERSION);
|
|
947
|
+
if (key === "supportedArchitectures" && Array.isArray(value)) return !supportsV12_5;
|
|
948
|
+
if (!value || typeof value !== "object") return false;
|
|
949
|
+
if (key === "python" || key === "cargo") {
|
|
950
|
+
if (supportsV12_5 && Object.hasOwn(value, "indexUrl")) return true;
|
|
951
|
+
if (key === "python" && !supportsV12_5) return [
|
|
952
|
+
"versions",
|
|
953
|
+
"overrides",
|
|
954
|
+
"constraints"
|
|
955
|
+
].some((field) => Object.hasOwn(value, field));
|
|
956
|
+
}
|
|
957
|
+
if (key === "namedRegistries" && supportsV12_5) return Object.keys(value).some((prefix) => prefix.toLowerCase() === "pkg");
|
|
958
|
+
if (key === "registries") return Object.values(value).some((entry) => {
|
|
959
|
+
if (!entry || typeof entry !== "object") return false;
|
|
960
|
+
if (Object.hasOwn(entry, "ecosystem") && !supportsV12_5) return true;
|
|
961
|
+
if (Object.hasOwn(entry, "packages") && !supportsMinimumVersion(target.version, PNPM_V12_5_1_MINIMUM_VERSION)) return true;
|
|
962
|
+
const prefix = Reflect.get(entry, "prefix");
|
|
963
|
+
return supportsV12_5 && typeof prefix === "string" && prefix.toLowerCase() === "pkg";
|
|
964
|
+
});
|
|
965
|
+
return false;
|
|
966
|
+
}
|
|
967
|
+
//#endregion
|
|
866
968
|
//#region src/features/settings/schema.ts
|
|
867
969
|
/**
|
|
868
970
|
* Create an empty collection of schema selection issues.
|
|
@@ -969,6 +1071,7 @@ function resolveSettingIssue({ allowedFields, target, key, npmrc, value }) {
|
|
|
969
1071
|
if (compatibility !== "v10" && PROJECT_REFUSED_SETTINGS.has(key)) return "refused";
|
|
970
1072
|
if (REGISTRY_SETTINGS.has(key) && containsUnsafeRegistryValue(value)) return "unsafe";
|
|
971
1073
|
if (PROXY_SETTINGS.has(key) && typeof value === "string" && value.includes("${")) return "unsafe";
|
|
1074
|
+
if (hasIncompatibleSettingValue(key, value, target)) return "incompatible";
|
|
972
1075
|
if (key === "tasks" && value && typeof value === "object" && Object.values(value).some((task) => task && typeof task === "object" && Object.keys(task).some((field) => !taskSettings.has(field)))) return "incompatible";
|
|
973
1076
|
if (!workspaceSettings.has(key)) return isSettingFromAnotherVersion(key, compatibility) ? "incompatible" : "unknown";
|
|
974
1077
|
if (allowedFields && !allowedFields.has(key)) return "unsupported";
|
|
@@ -1351,6 +1454,17 @@ function normalizeNpmrcKey(key) {
|
|
|
1351
1454
|
return key.trim().replace(/\[\]$/u, "").toLowerCase();
|
|
1352
1455
|
}
|
|
1353
1456
|
/**
|
|
1457
|
+
* Convert scalar list settings to the array representation required by YAML.
|
|
1458
|
+
*
|
|
1459
|
+
* @param settings - Selected `.npmrc` settings with camelCase keys
|
|
1460
|
+
*
|
|
1461
|
+
* @returns Settings with each scalar list value preserved as one array item
|
|
1462
|
+
*/
|
|
1463
|
+
function normalizeNpmrcSettings(settings) {
|
|
1464
|
+
for (const key of NPMRC_STRING_ARRAY_SETTINGS) if (typeof settings[key] === "string") settings[key] = [settings[key]];
|
|
1465
|
+
return settings;
|
|
1466
|
+
}
|
|
1467
|
+
/**
|
|
1354
1468
|
* Extract key name from a raw `.npmrc` line.
|
|
1355
1469
|
*
|
|
1356
1470
|
* @param line - Raw line from an `.npmrc` file
|
|
@@ -1382,7 +1496,8 @@ function isNpmrcAuthOrRegistryKey(key) {
|
|
|
1382
1496
|
* Remove pnpm-related settings from `.npmrc` file.
|
|
1383
1497
|
*
|
|
1384
1498
|
* This function reads the `.npmrc` file, removes lines whose exact normalized
|
|
1385
|
-
* keys were migrated, and writes the cleaned content back to the file.
|
|
1499
|
+
* root keys were migrated, and writes the cleaned content back to the file.
|
|
1500
|
+
* INI sections are retained because their child keys are not root settings.
|
|
1386
1501
|
*
|
|
1387
1502
|
* @param path - Absolute path to the `.npmrc` file
|
|
1388
1503
|
* @param compatibility - Concrete pnpm compatibility target
|
|
@@ -1399,7 +1514,11 @@ function isNpmrcAuthOrRegistryKey(key) {
|
|
|
1399
1514
|
*/
|
|
1400
1515
|
async function pruneNpmrc(path, compatibility, migratedKeys) {
|
|
1401
1516
|
const migratedKeySet = new Set(migratedKeys.map(normalizeNpmrcKey));
|
|
1402
|
-
const
|
|
1517
|
+
const content = await fsReadFile(path);
|
|
1518
|
+
let inSection = false;
|
|
1519
|
+
const updatedContent = content.split(/\r?\n/u).filter((line) => {
|
|
1520
|
+
if (/^\[[^\]]*\]\s*$/u.test(line.replace(/^\uFEFF/u, ""))) inSection = true;
|
|
1521
|
+
if (inSection) return true;
|
|
1403
1522
|
const key = getNpmrcLineKey(line);
|
|
1404
1523
|
if (!key) return true;
|
|
1405
1524
|
return !migratedKeySet.has(key);
|
|
@@ -1443,7 +1562,7 @@ async function readMigratableNpmrc(path, target, options = {}) {
|
|
|
1443
1562
|
return {
|
|
1444
1563
|
issues,
|
|
1445
1564
|
keys,
|
|
1446
|
-
settings: camelcaseKeys(migratable)
|
|
1565
|
+
settings: normalizeNpmrcSettings(camelcaseKeys(migratable))
|
|
1447
1566
|
};
|
|
1448
1567
|
}
|
|
1449
1568
|
const migratable = {};
|
|
@@ -1465,7 +1584,7 @@ async function readMigratableNpmrc(path, target, options = {}) {
|
|
|
1465
1584
|
mergeSettingsIssues(issues, selected.issues);
|
|
1466
1585
|
}
|
|
1467
1586
|
}
|
|
1468
|
-
const settings = camelcaseKeys(migratable);
|
|
1587
|
+
const settings = normalizeNpmrcSettings(camelcaseKeys(migratable));
|
|
1469
1588
|
if (Object.keys(nodeDownloadMirrors).length) {
|
|
1470
1589
|
const selected = selectPnpmSettings({ nodeDownloadMirrors }, target, { allowedFields: options.allowedFields });
|
|
1471
1590
|
if (selected.keys.length) {
|
|
@@ -2554,7 +2673,7 @@ const bold = getColor("bold");
|
|
|
2554
2673
|
//#endregion
|
|
2555
2674
|
//#region package.json
|
|
2556
2675
|
var name = "pnpm-settings-migrator";
|
|
2557
|
-
var version = "0.
|
|
2676
|
+
var version = "0.12.0";
|
|
2558
2677
|
//#endregion
|
|
2559
2678
|
//#region src/cli/options.ts
|
|
2560
2679
|
/**
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Document } from "yaml";
|
|
2
|
-
import { PnpmSettings } from "@pnpm/types";
|
|
2
|
+
import { PnpmSettings, RegistryDeclaration } from "@pnpm/types";
|
|
3
3
|
//#region src/types/options.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* Compatibility target used by the migrator.
|
|
@@ -93,15 +93,19 @@ type ResolvedOptions = Required<Omit<Options, 'targetVersion'>> & Pick<Options,
|
|
|
93
93
|
*/
|
|
94
94
|
type GlobalShimPolicy = boolean | 'always' | 'auto' | 'prompt';
|
|
95
95
|
/**
|
|
96
|
-
*
|
|
96
|
+
* Task configuration, including pnpm 12.4 caching and 12.5 concurrency groups.
|
|
97
97
|
*
|
|
98
|
-
* @see https://github.com/pnpm/pnpm/blob/v12.
|
|
98
|
+
* @see https://github.com/pnpm/pnpm/blob/v12.5.1/pnpm/crates/config/src/workspace_yaml/sections.rs
|
|
99
99
|
*/
|
|
100
100
|
interface PnpmTaskSettings {
|
|
101
101
|
/**
|
|
102
102
|
* Maximum concurrent executions of this task.
|
|
103
103
|
*/
|
|
104
104
|
concurrency?: number;
|
|
105
|
+
/**
|
|
106
|
+
* Machine-wide concurrency group to count this task against (pnpm 12.5+).
|
|
107
|
+
*/
|
|
108
|
+
concurrencyGroup?: string;
|
|
105
109
|
/**
|
|
106
110
|
* Prerequisite tasks; a caret prefix refers to workspace dependencies.
|
|
107
111
|
*/
|
|
@@ -140,7 +144,8 @@ interface PnpmPythonSettings {
|
|
|
140
144
|
*/
|
|
141
145
|
executable?: string;
|
|
142
146
|
/**
|
|
143
|
-
* Python package index URL.
|
|
147
|
+
* Python package index URL accepted by pnpm 12.4.
|
|
148
|
+
* @deprecated pnpm 12.5+ uses a registries entry with ecosystem: pypi.
|
|
144
149
|
*/
|
|
145
150
|
indexUrl?: string;
|
|
146
151
|
/**
|
|
@@ -151,6 +156,18 @@ interface PnpmPythonSettings {
|
|
|
151
156
|
* Python dependency groups to install.
|
|
152
157
|
*/
|
|
153
158
|
groups?: string[];
|
|
159
|
+
/**
|
|
160
|
+
* Python versions to resolve the lockfile for (pnpm 12.5+).
|
|
161
|
+
*/
|
|
162
|
+
versions?: string[];
|
|
163
|
+
/**
|
|
164
|
+
* Python requirements that override dependency versions (pnpm 12.5+).
|
|
165
|
+
*/
|
|
166
|
+
overrides?: string[];
|
|
167
|
+
/**
|
|
168
|
+
* Additional constraints on Python dependency versions (pnpm 12.5+).
|
|
169
|
+
*/
|
|
170
|
+
constraints?: string[];
|
|
154
171
|
}
|
|
155
172
|
/**
|
|
156
173
|
* Cargo dependency management configuration introduced in pnpm 12.4.0.
|
|
@@ -161,14 +178,41 @@ interface PnpmCargoSettings {
|
|
|
161
178
|
*/
|
|
162
179
|
enabled?: boolean;
|
|
163
180
|
/**
|
|
164
|
-
* Cargo registry index URL.
|
|
181
|
+
* Cargo registry index URL accepted by pnpm 12.4.
|
|
182
|
+
* @deprecated pnpm 12.5+ uses a registries entry with ecosystem: cargo.
|
|
165
183
|
*/
|
|
166
184
|
indexUrl?: string;
|
|
167
185
|
}
|
|
186
|
+
/**
|
|
187
|
+
* URL-keyed registry declaration with pnpm 12.5 ecosystem support.
|
|
188
|
+
*/
|
|
189
|
+
interface PnpmRegistryDeclaration extends RegistryDeclaration {
|
|
190
|
+
/**
|
|
191
|
+
* Package ecosystem served by the registry; omitted means npm (pnpm 12.5+).
|
|
192
|
+
*/
|
|
193
|
+
ecosystem?: 'cargo' | 'npm' | 'pypi';
|
|
194
|
+
/**
|
|
195
|
+
* Python package names or trailing-prefix patterns; * selects the default
|
|
196
|
+
* index (pnpm 12.5.1+).
|
|
197
|
+
*/
|
|
198
|
+
packages?: string[];
|
|
199
|
+
}
|
|
168
200
|
/**
|
|
169
201
|
* Settings supported by pnpm v12, subject to the resolved version capabilities.
|
|
170
202
|
*/
|
|
171
203
|
interface PnpmSettingsV12 {
|
|
204
|
+
/**
|
|
205
|
+
* Machine-wide limits shared by tasks in each group (pnpm 12.5+).
|
|
206
|
+
*/
|
|
207
|
+
concurrencyGroups?: Record<string, number>;
|
|
208
|
+
/**
|
|
209
|
+
* Legacy OS/CPU/libc axes, or platform names and Rust triples (pnpm 12.5+).
|
|
210
|
+
*/
|
|
211
|
+
supportedArchitectures?: PnpmSettings['supportedArchitectures'] | string[];
|
|
212
|
+
/**
|
|
213
|
+
* Legacy scope routes or URL-keyed declarations with ecosystem settings.
|
|
214
|
+
*/
|
|
215
|
+
registries?: Record<string, PnpmRegistryDeclaration | string>;
|
|
172
216
|
/**
|
|
173
217
|
* Peer auto-installation policy accepted only by the v12 target.
|
|
174
218
|
*/
|
|
@@ -360,7 +404,7 @@ interface PnpmSettingsV11 {
|
|
|
360
404
|
/**
|
|
361
405
|
* `pnpm-workspace.yaml` types.
|
|
362
406
|
*/
|
|
363
|
-
type PnpmWorkspace = Omit<PnpmSettings, 'tasks'> & PnpmSettingsDeprecated & PnpmSettingsV11 & PnpmSettingsV12 & PnpmWorkspaceLegacy;
|
|
407
|
+
type PnpmWorkspace = Omit<PnpmSettings, 'registries' | 'supportedArchitectures' | 'tasks'> & PnpmSettingsDeprecated & PnpmSettingsV11 & PnpmSettingsV12 & PnpmWorkspaceLegacy;
|
|
364
408
|
//#endregion
|
|
365
409
|
//#region src/types/package-json.d.ts
|
|
366
410
|
/**
|
|
@@ -568,4 +612,4 @@ export declare function migratePnpmSettings(rawOptions?: Options): Promise<Migra
|
|
|
568
612
|
*/
|
|
569
613
|
export declare function resolveOptions(options?: Options): ResolvedOptions;
|
|
570
614
|
//#endregion
|
|
571
|
-
export type { CompatibilityTarget, GlobalShimPolicy, MergeStrategy, MigrationResult, NpmRC, Options, PackageJson, PackageManagerEngine, PnpmCargoSettings, PnpmPythonSettings, PnpmSettingsDeprecated, PnpmSettingsV11, PnpmSettingsV12, PnpmTaskSettings, PnpmWorkspace, PnpmWorkspaceLegacy, ResolvedOptions, RuntimeEngine, SettingsChange };
|
|
615
|
+
export type { CompatibilityTarget, GlobalShimPolicy, MergeStrategy, MigrationResult, NpmRC, Options, PackageJson, PackageManagerEngine, PnpmCargoSettings, PnpmPythonSettings, PnpmRegistryDeclaration, PnpmSettingsDeprecated, PnpmSettingsV11, PnpmSettingsV12, PnpmTaskSettings, PnpmWorkspace, PnpmWorkspaceLegacy, ResolvedOptions, RuntimeEngine, SettingsChange };
|
package/dist/index.mjs
CHANGED
|
@@ -108,6 +108,30 @@ const NPMRC_AUTH_OR_REGISTRY_KEYS = [
|
|
|
108
108
|
* Pattern for pnpm's channel-specific Node.js mirror keys.
|
|
109
109
|
*/
|
|
110
110
|
const NODE_MIRROR_KEY_PATTERN = /^node-mirror:(?<channel>.+)$/iu;
|
|
111
|
+
/**
|
|
112
|
+
* List settings accepting a single string in legacy `.npmrc` files.
|
|
113
|
+
* Workspace YAML requires these values to be arrays.
|
|
114
|
+
* Legacy build permission lists keep their explicit array validation.
|
|
115
|
+
*/
|
|
116
|
+
const NPMRC_STRING_ARRAY_SETTINGS = [
|
|
117
|
+
"changedFilesIgnorePattern",
|
|
118
|
+
"externalDependencies",
|
|
119
|
+
"extraBinPaths",
|
|
120
|
+
"filter",
|
|
121
|
+
"filterProd",
|
|
122
|
+
"gitShallowHosts",
|
|
123
|
+
"hoistPattern",
|
|
124
|
+
"ignoredOptionalDependencies",
|
|
125
|
+
"mergeGitBranchLockfilesBranchPattern",
|
|
126
|
+
"minimumReleaseAgeExclude",
|
|
127
|
+
"packages",
|
|
128
|
+
"publicHoistPattern",
|
|
129
|
+
"requiredScripts",
|
|
130
|
+
"syncInjectedDepsAfterScripts",
|
|
131
|
+
"testPattern",
|
|
132
|
+
"trustPolicyExclude",
|
|
133
|
+
"workspacePackagePatterns"
|
|
134
|
+
];
|
|
111
135
|
//#endregion
|
|
112
136
|
//#region src/constants/pnpm-v10.ts
|
|
113
137
|
/**
|
|
@@ -296,6 +320,7 @@ const PNPM_PROJECT_REFUSED_SETTINGS = [
|
|
|
296
320
|
"scope",
|
|
297
321
|
"selectedProjectsGraph",
|
|
298
322
|
"stateDir",
|
|
323
|
+
"tools",
|
|
299
324
|
"userConfig",
|
|
300
325
|
"userconfig",
|
|
301
326
|
"wantedPackageManager",
|
|
@@ -579,32 +604,64 @@ const PNPM_WORKSPACE_YAML = "pnpm-workspace.yaml";
|
|
|
579
604
|
//#endregion
|
|
580
605
|
//#region src/constants/pnpm-capabilities.ts
|
|
581
606
|
/**
|
|
607
|
+
* Release introducing platform lists, registry ecosystems, and Python options.
|
|
608
|
+
*/
|
|
609
|
+
const PNPM_V12_5_MINIMUM_VERSION = [
|
|
610
|
+
12,
|
|
611
|
+
5,
|
|
612
|
+
0
|
|
613
|
+
];
|
|
614
|
+
/**
|
|
615
|
+
* Release introducing package routes in Python registry declarations.
|
|
616
|
+
*/
|
|
617
|
+
const PNPM_V12_5_1_MINIMUM_VERSION = [
|
|
618
|
+
12,
|
|
619
|
+
5,
|
|
620
|
+
1
|
|
621
|
+
];
|
|
622
|
+
/**
|
|
582
623
|
* Version-gated settings. Add a release entry only when supported fields change.
|
|
583
624
|
*
|
|
584
625
|
* @see https://github.com/pnpm/pnpm/blob/v12.4.0/pnpm/crates/config/src/workspace_yaml.rs
|
|
585
626
|
*/
|
|
586
|
-
const PNPM_SETTINGS_CAPABILITIES = [
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
"
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
627
|
+
const PNPM_SETTINGS_CAPABILITIES = [
|
|
628
|
+
{
|
|
629
|
+
minimumVersion: [
|
|
630
|
+
11,
|
|
631
|
+
27,
|
|
632
|
+
0
|
|
633
|
+
],
|
|
634
|
+
workspaceFields: ["trustPolicyExcludePrune"],
|
|
635
|
+
taskFields: []
|
|
636
|
+
},
|
|
637
|
+
{
|
|
638
|
+
minimumVersion: [
|
|
639
|
+
12,
|
|
640
|
+
4,
|
|
641
|
+
0
|
|
642
|
+
],
|
|
643
|
+
workspaceFields: [
|
|
644
|
+
"cargo",
|
|
645
|
+
"packageConfigs",
|
|
646
|
+
"pipelineBase",
|
|
647
|
+
"pipelines",
|
|
648
|
+
"python",
|
|
649
|
+
"trustPolicyExcludePrune"
|
|
650
|
+
],
|
|
651
|
+
taskFields: [
|
|
652
|
+
"cache",
|
|
653
|
+
"cargoTargetDir",
|
|
654
|
+
"env",
|
|
655
|
+
"inputs",
|
|
656
|
+
"outputs"
|
|
657
|
+
]
|
|
658
|
+
},
|
|
659
|
+
{
|
|
660
|
+
minimumVersion: PNPM_V12_5_MINIMUM_VERSION,
|
|
661
|
+
workspaceFields: ["concurrencyGroups"],
|
|
662
|
+
taskFields: ["concurrencyGroup"]
|
|
663
|
+
}
|
|
664
|
+
];
|
|
608
665
|
/**
|
|
609
666
|
* Known workspace fields that require a confirmed version.
|
|
610
667
|
*/
|
|
@@ -754,8 +811,13 @@ function resolveOptions(options = {}) {
|
|
|
754
811
|
function discardMerge(priority, fallback) {
|
|
755
812
|
const result = { ...priority };
|
|
756
813
|
for (const [key, fallbackValue] of Object.entries(fallback)) {
|
|
757
|
-
const priorityValue = result[key];
|
|
758
|
-
if (isUndefined(priorityValue)) result
|
|
814
|
+
const priorityValue = Object.hasOwn(result, key) ? result[key] : void 0;
|
|
815
|
+
if (isUndefined(priorityValue)) Object.defineProperty(result, key, {
|
|
816
|
+
configurable: true,
|
|
817
|
+
enumerable: true,
|
|
818
|
+
value: fallbackValue,
|
|
819
|
+
writable: true
|
|
820
|
+
});
|
|
759
821
|
else if (isPlainObject(priorityValue) && isPlainObject(fallbackValue)) result[key] = discardMerge(priorityValue, fallbackValue);
|
|
760
822
|
}
|
|
761
823
|
return result;
|
|
@@ -775,8 +837,13 @@ function discardMerge(priority, fallback) {
|
|
|
775
837
|
function mergeWithArrayDedupe(existing, incoming) {
|
|
776
838
|
const result = { ...existing };
|
|
777
839
|
for (const [key, incomingValue] of Object.entries(incoming)) {
|
|
778
|
-
const existingValue = result[key];
|
|
779
|
-
if (isUndefined(existingValue)) result
|
|
840
|
+
const existingValue = Object.hasOwn(result, key) ? result[key] : void 0;
|
|
841
|
+
if (isUndefined(existingValue)) Object.defineProperty(result, key, {
|
|
842
|
+
configurable: true,
|
|
843
|
+
enumerable: true,
|
|
844
|
+
value: incomingValue,
|
|
845
|
+
writable: true
|
|
846
|
+
});
|
|
780
847
|
else if (Array.isArray(existingValue) && Array.isArray(incomingValue)) {
|
|
781
848
|
const items = Array.from(/* @__PURE__ */ new Set([...existingValue, ...incomingValue]));
|
|
782
849
|
result[key] = items.filter((item, index) => !item || typeof item !== "object" || !items.slice(0, index).some((previous) => isDeepStrictEqual(previous, item)));
|
|
@@ -856,6 +923,41 @@ function formatSettingsIssues(options) {
|
|
|
856
923
|
return warnings;
|
|
857
924
|
}
|
|
858
925
|
//#endregion
|
|
926
|
+
//#region src/features/settings/versioned-values.ts
|
|
927
|
+
/**
|
|
928
|
+
* Check value shapes that changed within a pnpm major release.
|
|
929
|
+
*
|
|
930
|
+
* Reject the whole setting so cleanup cannot discard unsupported nested values.
|
|
931
|
+
*
|
|
932
|
+
* @param key - Canonical workspace setting name
|
|
933
|
+
* @param value - Incoming or existing setting value
|
|
934
|
+
* @param target - Confirmed target version and major schema
|
|
935
|
+
*
|
|
936
|
+
* @returns Whether the value uses a shape unsupported by the selected release
|
|
937
|
+
*/
|
|
938
|
+
function hasIncompatibleSettingValue(key, value, target) {
|
|
939
|
+
const supportsV12_5 = supportsMinimumVersion(target.version, PNPM_V12_5_MINIMUM_VERSION);
|
|
940
|
+
if (key === "supportedArchitectures" && Array.isArray(value)) return !supportsV12_5;
|
|
941
|
+
if (!value || typeof value !== "object") return false;
|
|
942
|
+
if (key === "python" || key === "cargo") {
|
|
943
|
+
if (supportsV12_5 && Object.hasOwn(value, "indexUrl")) return true;
|
|
944
|
+
if (key === "python" && !supportsV12_5) return [
|
|
945
|
+
"versions",
|
|
946
|
+
"overrides",
|
|
947
|
+
"constraints"
|
|
948
|
+
].some((field) => Object.hasOwn(value, field));
|
|
949
|
+
}
|
|
950
|
+
if (key === "namedRegistries" && supportsV12_5) return Object.keys(value).some((prefix) => prefix.toLowerCase() === "pkg");
|
|
951
|
+
if (key === "registries") return Object.values(value).some((entry) => {
|
|
952
|
+
if (!entry || typeof entry !== "object") return false;
|
|
953
|
+
if (Object.hasOwn(entry, "ecosystem") && !supportsV12_5) return true;
|
|
954
|
+
if (Object.hasOwn(entry, "packages") && !supportsMinimumVersion(target.version, PNPM_V12_5_1_MINIMUM_VERSION)) return true;
|
|
955
|
+
const prefix = Reflect.get(entry, "prefix");
|
|
956
|
+
return supportsV12_5 && typeof prefix === "string" && prefix.toLowerCase() === "pkg";
|
|
957
|
+
});
|
|
958
|
+
return false;
|
|
959
|
+
}
|
|
960
|
+
//#endregion
|
|
859
961
|
//#region src/features/settings/schema.ts
|
|
860
962
|
/**
|
|
861
963
|
* Create an empty collection of schema selection issues.
|
|
@@ -962,6 +1064,7 @@ function resolveSettingIssue({ allowedFields, target, key, npmrc, value }) {
|
|
|
962
1064
|
if (compatibility !== "v10" && PROJECT_REFUSED_SETTINGS.has(key)) return "refused";
|
|
963
1065
|
if (REGISTRY_SETTINGS.has(key) && containsUnsafeRegistryValue(value)) return "unsafe";
|
|
964
1066
|
if (PROXY_SETTINGS.has(key) && typeof value === "string" && value.includes("${")) return "unsafe";
|
|
1067
|
+
if (hasIncompatibleSettingValue(key, value, target)) return "incompatible";
|
|
965
1068
|
if (key === "tasks" && value && typeof value === "object" && Object.values(value).some((task) => task && typeof task === "object" && Object.keys(task).some((field) => !taskSettings.has(field)))) return "incompatible";
|
|
966
1069
|
if (!workspaceSettings.has(key)) return isSettingFromAnotherVersion(key, compatibility) ? "incompatible" : "unknown";
|
|
967
1070
|
if (allowedFields && !allowedFields.has(key)) return "unsupported";
|
|
@@ -1344,6 +1447,17 @@ function normalizeNpmrcKey(key) {
|
|
|
1344
1447
|
return key.trim().replace(/\[\]$/u, "").toLowerCase();
|
|
1345
1448
|
}
|
|
1346
1449
|
/**
|
|
1450
|
+
* Convert scalar list settings to the array representation required by YAML.
|
|
1451
|
+
*
|
|
1452
|
+
* @param settings - Selected `.npmrc` settings with camelCase keys
|
|
1453
|
+
*
|
|
1454
|
+
* @returns Settings with each scalar list value preserved as one array item
|
|
1455
|
+
*/
|
|
1456
|
+
function normalizeNpmrcSettings(settings) {
|
|
1457
|
+
for (const key of NPMRC_STRING_ARRAY_SETTINGS) if (typeof settings[key] === "string") settings[key] = [settings[key]];
|
|
1458
|
+
return settings;
|
|
1459
|
+
}
|
|
1460
|
+
/**
|
|
1347
1461
|
* Extract key name from a raw `.npmrc` line.
|
|
1348
1462
|
*
|
|
1349
1463
|
* @param line - Raw line from an `.npmrc` file
|
|
@@ -1375,7 +1489,8 @@ function isNpmrcAuthOrRegistryKey(key) {
|
|
|
1375
1489
|
* Remove pnpm-related settings from `.npmrc` file.
|
|
1376
1490
|
*
|
|
1377
1491
|
* This function reads the `.npmrc` file, removes lines whose exact normalized
|
|
1378
|
-
* keys were migrated, and writes the cleaned content back to the file.
|
|
1492
|
+
* root keys were migrated, and writes the cleaned content back to the file.
|
|
1493
|
+
* INI sections are retained because their child keys are not root settings.
|
|
1379
1494
|
*
|
|
1380
1495
|
* @param path - Absolute path to the `.npmrc` file
|
|
1381
1496
|
* @param compatibility - Concrete pnpm compatibility target
|
|
@@ -1392,7 +1507,11 @@ function isNpmrcAuthOrRegistryKey(key) {
|
|
|
1392
1507
|
*/
|
|
1393
1508
|
async function pruneNpmrc(path, compatibility, migratedKeys) {
|
|
1394
1509
|
const migratedKeySet = new Set(migratedKeys.map(normalizeNpmrcKey));
|
|
1395
|
-
const
|
|
1510
|
+
const content = await fsReadFile(path);
|
|
1511
|
+
let inSection = false;
|
|
1512
|
+
const updatedContent = content.split(/\r?\n/u).filter((line) => {
|
|
1513
|
+
if (/^\[[^\]]*\]\s*$/u.test(line.replace(/^\uFEFF/u, ""))) inSection = true;
|
|
1514
|
+
if (inSection) return true;
|
|
1396
1515
|
const key = getNpmrcLineKey(line);
|
|
1397
1516
|
if (!key) return true;
|
|
1398
1517
|
return !migratedKeySet.has(key);
|
|
@@ -1436,7 +1555,7 @@ async function readMigratableNpmrc(path, target, options = {}) {
|
|
|
1436
1555
|
return {
|
|
1437
1556
|
issues,
|
|
1438
1557
|
keys,
|
|
1439
|
-
settings: camelcaseKeys(migratable)
|
|
1558
|
+
settings: normalizeNpmrcSettings(camelcaseKeys(migratable))
|
|
1440
1559
|
};
|
|
1441
1560
|
}
|
|
1442
1561
|
const migratable = {};
|
|
@@ -1458,7 +1577,7 @@ async function readMigratableNpmrc(path, target, options = {}) {
|
|
|
1458
1577
|
mergeSettingsIssues(issues, selected.issues);
|
|
1459
1578
|
}
|
|
1460
1579
|
}
|
|
1461
|
-
const settings = camelcaseKeys(migratable);
|
|
1580
|
+
const settings = normalizeNpmrcSettings(camelcaseKeys(migratable));
|
|
1462
1581
|
if (Object.keys(nodeDownloadMirrors).length) {
|
|
1463
1582
|
const selected = selectPnpmSettings({ nodeDownloadMirrors }, target, { allowedFields: options.allowedFields });
|
|
1464
1583
|
if (selected.keys.length) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pnpm-settings-migrator",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Move pnpm settings from `pnpm` field in `package.json` and `.npmrc` file to `pnpm-workspace.yaml`",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"migrator",
|
|
@@ -42,10 +42,10 @@
|
|
|
42
42
|
"access": "public"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@ntnyq/utils": "^0.
|
|
45
|
+
"@ntnyq/utils": "^0.23.0",
|
|
46
46
|
"@pnpm/types": "^1102.1.0",
|
|
47
47
|
"cac": "^7.0.0",
|
|
48
|
-
"camelcase-keys": "^10.0.
|
|
48
|
+
"camelcase-keys": "^10.0.3",
|
|
49
49
|
"consola": "^3.4.2",
|
|
50
50
|
"defu": "^6.1.7",
|
|
51
51
|
"detect-indent": "^7.0.2",
|
|
@@ -56,16 +56,17 @@
|
|
|
56
56
|
"yaml": "^2.9.1"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
|
-
"@types/node": "^26.
|
|
59
|
+
"@types/node": "^26.6.2",
|
|
60
60
|
"bumpp": "^12.3.0",
|
|
61
61
|
"husky": "^9.1.7",
|
|
62
62
|
"nano-staged": "^1.0.2",
|
|
63
63
|
"npm-run-all2": "^9.0.3",
|
|
64
|
-
"oxfmt": "^0.
|
|
65
|
-
"oxlint": "^1.
|
|
64
|
+
"oxfmt": "^0.70.0",
|
|
65
|
+
"oxlint": "^1.85.0",
|
|
66
66
|
"tsdown": "^0.23.0",
|
|
67
|
+
"tsx": "^4.23.15",
|
|
67
68
|
"typescript": "^7.0.2",
|
|
68
|
-
"vitest": "^5.0.
|
|
69
|
+
"vitest": "^5.0.1"
|
|
69
70
|
},
|
|
70
71
|
"nano-staged": {
|
|
71
72
|
"*.{js,ts,mjs,tsx}": "oxlint --fix",
|
|
@@ -84,7 +85,7 @@
|
|
|
84
85
|
"release:check": "run-s lint format:check typecheck test",
|
|
85
86
|
"release:version": "bumpp",
|
|
86
87
|
"test": "vitest",
|
|
87
|
-
"test:compatibility": "
|
|
88
|
+
"test:compatibility": "tsx scripts/verify-pnpm-compatibility.ts",
|
|
88
89
|
"typecheck": "tsc --noEmit"
|
|
89
90
|
}
|
|
90
91
|
}
|