pnpm-settings-migrator 0.16.0 → 0.18.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 +63 -9
- package/dist/cli.mjs +307 -29
- package/dist/index.d.mts +67 -3
- package/dist/index.mjs +306 -28
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -202,7 +202,8 @@ From 11.28.3, non-null `allowBuilds` values must be maps of booleans or strings.
|
|
|
202
202
|
From 11.28.4, non-null `allowUnusedPatches` must be boolean, and non-null
|
|
203
203
|
`ignoredOptionalDependencies` / `requiredScripts` must be string arrays.
|
|
204
204
|
From 11.28.5, `httpProxy` and `httpsProxy` must be strings, including when empty;
|
|
205
|
-
null is incompatible.
|
|
205
|
+
null is incompatible in 11.28.5. From 11.28.6, null is accepted again and leaves
|
|
206
|
+
the proxy unset. Invalid values remain in their sources with diagnostics.
|
|
206
207
|
|
|
207
208
|
For pnpm 12.9.0 and later stable v12 releases, URL-keyed `registries` entries
|
|
208
209
|
can set `networkConcurrency` to a positive safe integer, or null to leave it
|
|
@@ -232,11 +233,54 @@ For example, targeting 12.11.0 migrates
|
|
|
232
233
|
to equivalent workspace YAML. Targeting 12.10.1 retains these settings with
|
|
233
234
|
diagnostics.
|
|
234
235
|
|
|
236
|
+
For pnpm 12.12.0 and later stable v12 releases, `addMissingPeerTypes` accepts
|
|
237
|
+
boolean or null values and `remoteCache` accepts the project organization:
|
|
238
|
+
`{ org: 'acme' }`. Empty and null remote-cache sections are preserved. Server,
|
|
239
|
+
team, signing, and publication fields belong in trusted global configuration
|
|
240
|
+
or environment variables. Objects containing those fields, unknown fields,
|
|
241
|
+
or invalid values remain intact in their source with diagnostics; existing
|
|
242
|
+
incompatible workspace values block migration before writes.
|
|
243
|
+
|
|
244
|
+
`remoteCache` and legacy `sideEffectsCache.remote` are kept independently so
|
|
245
|
+
pnpm applies its field-level precedence. The migrator does not insert defaults
|
|
246
|
+
or rewrite `allowBuilds`: pnpm 12.12 itself now honors explicit build denials
|
|
247
|
+
when `dangerouslyAllowAllBuilds` is true.
|
|
248
|
+
|
|
249
|
+
From 12.12.1, the registry prefix `tool` is reserved. Both `namedRegistries`
|
|
250
|
+
and URL-keyed `registries` using that exact prefix remain in their sources;
|
|
251
|
+
existing workspace declarations block migration. This restriction is
|
|
252
|
+
case-sensitive (`ToOl` remains valid), unlike the existing `pkg` restriction.
|
|
253
|
+
|
|
254
|
+
For pnpm 12.13.0 and later stable v12 releases, `nodePackageMap` accepts
|
|
255
|
+
`mode: off | write-only | load` and `type: standard | loose`. The section and
|
|
256
|
+
its fields may be null to leave them unset. New and legacy
|
|
257
|
+
`nodeExperimentalPackageMap` / `nodePackageMapType` values are preserved so pnpm
|
|
258
|
+
applies the new section's field-level precedence. `storeIndexFormat` accepts
|
|
259
|
+
`sqlite`, `files`, or null; `files` supports stores shared over network filesystems.
|
|
260
|
+
Enum values may use environment placeholders, which are preserved without expansion.
|
|
261
|
+
Older or unconfirmed targets retain these settings in their source.
|
|
262
|
+
|
|
263
|
+
For pnpm 12.14.0 and later stable v12 releases, `sideEffectsCache.exclude`
|
|
264
|
+
accepts package patterns such as `['java', '@native/*', 'example@1']`, an empty
|
|
265
|
+
array, or null. Excluded builds are neither restored nor saved in local or remote
|
|
266
|
+
caches. Cache `read` and `write` values may also use preserved environment
|
|
267
|
+
placeholders. On older targets, the complete cache section stays in its source.
|
|
268
|
+
Invalid nested values and unknown fields also preserve the complete source
|
|
269
|
+
section; existing incompatible workspace values block writes.
|
|
270
|
+
|
|
235
271
|
pnpm v12 reads `filter` and `filterProd` from CLI arguments or `updateConfig`
|
|
236
272
|
hooks, not workspace YAML. These settings remain in their source with
|
|
237
273
|
diagnostics for v12 targets; existing YAML containing them blocks migration.
|
|
238
274
|
The 12.11.2 hook fix does not add workspace support.
|
|
239
275
|
|
|
276
|
+
Other legacy fields ignored by the v12 workspace reader also stay in their
|
|
277
|
+
sources with diagnostics. These include `production`, `dev`, `recursive`,
|
|
278
|
+
`lockfileOnly`, `force`, `depth`, `saveDev`, `fetchingConcurrency`,
|
|
279
|
+
`requiredScripts`, `extraBinPaths`, `extraEnv`, and `workspacePackagePatterns`.
|
|
280
|
+
Existing YAML containing ignored fields blocks migration before writes.
|
|
281
|
+
For example, `production=true` cannot be moved into v12 YAML; use
|
|
282
|
+
`pnpm install --prod` to select production dependencies.
|
|
283
|
+
|
|
240
284
|
</details>
|
|
241
285
|
|
|
242
286
|
<details>
|
|
@@ -273,6 +317,9 @@ Notes:
|
|
|
273
317
|
such as `globalDir`, `stateDir`, `configDir`, and `scope` stay in `.npmrc`.
|
|
274
318
|
- Registry declarations containing embedded credentials or dynamic `${...}`
|
|
275
319
|
URLs stay in their source with a warning.
|
|
320
|
+
- Invalid registry locations, including relative paths and object property names
|
|
321
|
+
such as `__proto__`, remain in their sources with diagnostics. Registry
|
|
322
|
+
conversion never uses inherited object properties as declarations.
|
|
276
323
|
- Dynamic `${...}` values in `httpProxy`, `httpsProxy`, `noProxy`, `proxy`, and
|
|
277
324
|
`noproxy` stay in their source for every target. Project YAML does not expand
|
|
278
325
|
these placeholders; configure them globally in trusted configuration or use
|
|
@@ -297,10 +344,16 @@ Notes:
|
|
|
297
344
|
`package.json#pnpm` child keys remain in `package.json`.
|
|
298
345
|
INI sections in `.npmrc` are retained conservatively; their child names are
|
|
299
346
|
never treated as root keys during cleanup.
|
|
347
|
+
Bare boolean flags such as `save-exact` and quoted keys such as
|
|
348
|
+
`"save-exact"=true` are cleaned when migrated. Retained sections alone do not
|
|
349
|
+
cause repeated cleanup reports or rewrites.
|
|
300
350
|
- Runtime migration writes `devEngines.runtime` before removing a workspace
|
|
301
351
|
runtime declaration. A failed write preserves source settings for retry.
|
|
302
352
|
If an existing runtime declaration conflicts with a workspace runtime, migration
|
|
303
353
|
stops before writing any files. An identical Node.js declaration is reused.
|
|
354
|
+
- An `executionEnv` object containing fields besides `nodeVersion` remains
|
|
355
|
+
intact in its source with a warning. Existing workspace declarations with
|
|
356
|
+
such fields block migration before writes, so unmigrated values cannot be lost.
|
|
304
357
|
- YAML aliases remain when their anchors are unchanged and precede them.
|
|
305
358
|
If replacement, removal, or sorting would invalidate an alias, its original
|
|
306
359
|
value is written explicitly so unrelated settings keep their values.
|
|
@@ -311,17 +364,18 @@ Notes:
|
|
|
311
364
|
- The migrator does not update the `packageManager` version, CI environment variables,
|
|
312
365
|
shell setup, or pnpm commands in scripts. When `packageManager` still pins pnpm 10,
|
|
313
366
|
pass `--compatibility v11` explicitly and update the pin separately.
|
|
314
|
-
- Compatibility checks cover pnpm 11.28.
|
|
315
|
-
11.28.1, 11.28.2, 11.28.3, 11.28.4,
|
|
316
|
-
12.9.0, 12.9.1, 12.10.0, 12.10.1, 12.11.0,
|
|
367
|
+
- Compatibility checks cover pnpm 11.28.6 and 12.14.0, including 11.28.0,
|
|
368
|
+
11.28.1, 11.28.2, 11.28.3, 11.28.4, 11.28.5, 12.7.0, 12.8.0, 12.8.1,
|
|
369
|
+
12.8.2, 12.9.0, 12.9.1, 12.10.0, 12.10.1, 12.11.0, 12.11.1, 12.11.2,
|
|
370
|
+
12.12.0, 12.12.1, and 12.13.0.
|
|
317
371
|
Earlier regressions remain at 11.25.0,
|
|
318
372
|
11.26.0, 11.27.1, 12.2.1, 12.3.4, 12.4.0, 12.4.2, 12.5.0, 12.5.1, and 12.6.0.
|
|
319
|
-
As [audited on 2026-10-
|
|
320
|
-
npm `latest`
|
|
321
|
-
`next-12`. The v11 target is 11.28.
|
|
322
|
-
11
|
|
373
|
+
As [audited on 2026-10-17](docs/research/pnpm-settings-bump-2026-10-17.md),
|
|
374
|
+
npm `latest` and the highest confirmed stable release are 12.14.0, also on
|
|
375
|
+
`latest-12` and `next-12`. The v11 target is 11.28.6 on both `latest-11` and
|
|
376
|
+
`next-11`. The project is pinned to 12.14.0. These checks verify the documented
|
|
323
377
|
fixtures, not every setting in every release. v10 has unit regressions and
|
|
324
|
-
source review through 10.34.
|
|
378
|
+
source review through 10.34.7; real v10 consumption remains unverified.
|
|
325
379
|
The removed `pnpm install --resolution-only` CLI flag is outside this settings
|
|
326
380
|
migrator's scope; replace it with `pnpm peers check` in scripts before upgrading.
|
|
327
381
|
|
package/dist/cli.mjs
CHANGED
|
@@ -587,6 +587,14 @@ const PNPM_V11_28_5_MINIMUM_VERSION = [
|
|
|
587
587
|
5
|
|
588
588
|
];
|
|
589
589
|
/**
|
|
590
|
+
* Release restoring null proxy values as unset configuration.
|
|
591
|
+
*/
|
|
592
|
+
const PNPM_V11_28_6_MINIMUM_VERSION = [
|
|
593
|
+
11,
|
|
594
|
+
28,
|
|
595
|
+
6
|
|
596
|
+
];
|
|
597
|
+
/**
|
|
590
598
|
* Release adding force, publishing, and reporter settings.
|
|
591
599
|
*/
|
|
592
600
|
const PNPM_V12_7_MINIMUM_VERSION = [
|
|
@@ -627,6 +635,38 @@ const PNPM_V12_11_MINIMUM_VERSION = [
|
|
|
627
635
|
0
|
|
628
636
|
];
|
|
629
637
|
/**
|
|
638
|
+
* Release introducing remote cache configuration and missing peer types.
|
|
639
|
+
*/
|
|
640
|
+
const PNPM_V12_12_MINIMUM_VERSION = [
|
|
641
|
+
12,
|
|
642
|
+
12,
|
|
643
|
+
0
|
|
644
|
+
];
|
|
645
|
+
/**
|
|
646
|
+
* Release reserving the tool registry prefix.
|
|
647
|
+
*/
|
|
648
|
+
const PNPM_V12_12_1_MINIMUM_VERSION = [
|
|
649
|
+
12,
|
|
650
|
+
12,
|
|
651
|
+
1
|
|
652
|
+
];
|
|
653
|
+
/**
|
|
654
|
+
* Release introducing package map options and file-based store indexes.
|
|
655
|
+
*/
|
|
656
|
+
const PNPM_V12_13_MINIMUM_VERSION = [
|
|
657
|
+
12,
|
|
658
|
+
13,
|
|
659
|
+
0
|
|
660
|
+
];
|
|
661
|
+
/**
|
|
662
|
+
* Release introducing side-effects cache exclusions.
|
|
663
|
+
*/
|
|
664
|
+
const PNPM_V12_14_MINIMUM_VERSION = [
|
|
665
|
+
12,
|
|
666
|
+
14,
|
|
667
|
+
0
|
|
668
|
+
];
|
|
669
|
+
/**
|
|
630
670
|
* Workspace settings first consumed by pnpm v12 in 12.11.0.
|
|
631
671
|
*/
|
|
632
672
|
const PNPM_V12_11_WORKSPACE_SETTINGS = [
|
|
@@ -652,6 +692,16 @@ const PNPM_V12_7_WORKSPACE_SETTINGS = [
|
|
|
652
692
|
* @see https://github.com/pnpm/pnpm/blob/v12.4.0/pnpm/crates/config/src/workspace_yaml.rs
|
|
653
693
|
*/
|
|
654
694
|
const PNPM_SETTINGS_CAPABILITIES = [
|
|
695
|
+
{
|
|
696
|
+
minimumVersion: PNPM_V12_13_MINIMUM_VERSION,
|
|
697
|
+
workspaceFields: ["nodePackageMap", "storeIndexFormat"],
|
|
698
|
+
taskFields: []
|
|
699
|
+
},
|
|
700
|
+
{
|
|
701
|
+
minimumVersion: PNPM_V12_12_MINIMUM_VERSION,
|
|
702
|
+
workspaceFields: ["addMissingPeerTypes", "remoteCache"],
|
|
703
|
+
taskFields: []
|
|
704
|
+
},
|
|
655
705
|
{
|
|
656
706
|
minimumVersion: PNPM_V12_11_MINIMUM_VERSION,
|
|
657
707
|
workspaceFields: PNPM_V12_11_WORKSPACE_SETTINGS,
|
|
@@ -733,9 +783,82 @@ const PNPM_V12_ONLY_WORKSPACE_SETTINGS = [
|
|
|
733
783
|
"globalShims"
|
|
734
784
|
];
|
|
735
785
|
/**
|
|
786
|
+
* Legacy fields absent from pnpm v12's workspace reader through 12.14.0.
|
|
787
|
+
* Removed aliases with explicit migration replacements are kept separately.
|
|
788
|
+
*
|
|
789
|
+
* @see https://github.com/pnpm/pnpm/blob/v12.14.0/pnpm/crates/config/src/workspace_yaml/settings.rs
|
|
790
|
+
*/
|
|
791
|
+
const PNPM_V12_IGNORED_WORKSPACE_SETTINGS = [
|
|
792
|
+
"access",
|
|
793
|
+
"allowNew",
|
|
794
|
+
"allowSameVersion",
|
|
795
|
+
"auditIgnorePrune",
|
|
796
|
+
"autoConfirmAllPrompts",
|
|
797
|
+
"binLinks",
|
|
798
|
+
"commitHooks",
|
|
799
|
+
"cpu",
|
|
800
|
+
"depth",
|
|
801
|
+
"description",
|
|
802
|
+
"dev",
|
|
803
|
+
"dryRun",
|
|
804
|
+
"enablePnp",
|
|
805
|
+
"extraBinPaths",
|
|
806
|
+
"extraEnv",
|
|
807
|
+
"fetchingConcurrency",
|
|
808
|
+
"force",
|
|
809
|
+
"git",
|
|
810
|
+
"gitTagVersion",
|
|
811
|
+
"global",
|
|
812
|
+
"globalPath",
|
|
813
|
+
"globalPrefix",
|
|
814
|
+
"ignoreCurrentSpecifiers",
|
|
815
|
+
"json",
|
|
816
|
+
"libc",
|
|
817
|
+
"localAddress",
|
|
818
|
+
"lockfileOnly",
|
|
819
|
+
"long",
|
|
820
|
+
"message",
|
|
821
|
+
"npmPath",
|
|
822
|
+
"only",
|
|
823
|
+
"os",
|
|
824
|
+
"packDestination",
|
|
825
|
+
"packGzipLevel",
|
|
826
|
+
"parseable",
|
|
827
|
+
"pnpmExecPath",
|
|
828
|
+
"preserveAbsolutePaths",
|
|
829
|
+
"production",
|
|
830
|
+
"publishBranch",
|
|
831
|
+
"recursive",
|
|
832
|
+
"registriesByPrefix",
|
|
833
|
+
"registriesByScope",
|
|
834
|
+
"registryOptionsByUrl",
|
|
835
|
+
"requiredScripts",
|
|
836
|
+
"runtime",
|
|
837
|
+
"save",
|
|
838
|
+
"saveDev",
|
|
839
|
+
"saveOptional",
|
|
840
|
+
"saveProd",
|
|
841
|
+
"sideEffectsCacheRead",
|
|
842
|
+
"sideEffectsCacheWrite",
|
|
843
|
+
"signGitTag",
|
|
844
|
+
"strictSsl",
|
|
845
|
+
"tag",
|
|
846
|
+
"tryLoadDefaultPnpmfile",
|
|
847
|
+
"umask",
|
|
848
|
+
"useGitBranchLockfile",
|
|
849
|
+
"useLockfile",
|
|
850
|
+
"useRunningStoreServer",
|
|
851
|
+
"version",
|
|
852
|
+
"workspacePackagePatterns",
|
|
853
|
+
"workspacePackages",
|
|
854
|
+
"workspacePrefix",
|
|
855
|
+
"workspaceRoot",
|
|
856
|
+
"yes"
|
|
857
|
+
];
|
|
858
|
+
/**
|
|
736
859
|
* Lookup used to remove v11-only fields from the v12 workspace allowlist.
|
|
737
860
|
*/
|
|
738
|
-
const v11OnlyWorkspaceSettings = new Set(PNPM_V11_ONLY_WORKSPACE_SETTINGS);
|
|
861
|
+
const v11OnlyWorkspaceSettings = /* @__PURE__ */ new Set([...PNPM_V11_ONLY_WORKSPACE_SETTINGS, ...PNPM_V12_IGNORED_WORKSPACE_SETTINGS]);
|
|
739
862
|
/**
|
|
740
863
|
* Fields accepted by registry declarations with pnpm 12.9 concurrency limits.
|
|
741
864
|
*/
|
|
@@ -770,7 +893,7 @@ const PROJECT_REFUSED_SETTINGS = new Set(PNPM_PROJECT_REFUSED_SETTINGS);
|
|
|
770
893
|
/**
|
|
771
894
|
* Fields reported as incompatible when selecting settings for v12.
|
|
772
895
|
*/
|
|
773
|
-
const PNPM_V11_ONLY_SETTINGS = new Set(PNPM_V11_ONLY_WORKSPACE_SETTINGS);
|
|
896
|
+
const PNPM_V11_ONLY_SETTINGS = /* @__PURE__ */ new Set([...PNPM_V11_ONLY_WORKSPACE_SETTINGS, ...PNPM_V12_IGNORED_WORKSPACE_SETTINGS]);
|
|
774
897
|
/**
|
|
775
898
|
* Allowed workspace fields for the v11 target.
|
|
776
899
|
*/
|
|
@@ -1119,6 +1242,32 @@ function formatSettingsIssues(options) {
|
|
|
1119
1242
|
return warnings;
|
|
1120
1243
|
}
|
|
1121
1244
|
//#endregion
|
|
1245
|
+
//#region src/features/settings/registries.ts
|
|
1246
|
+
/**
|
|
1247
|
+
* Check that a registry location is a URL rather than an object property name.
|
|
1248
|
+
*
|
|
1249
|
+
* @param value - Untrusted registry location
|
|
1250
|
+
*
|
|
1251
|
+
* @returns Whether the location is an absolute or scheme-relative URL
|
|
1252
|
+
*/
|
|
1253
|
+
function isRegistryUrl(value) {
|
|
1254
|
+
return isString(value) && URL.canParse(value.startsWith("//") ? `https:${value}` : value);
|
|
1255
|
+
}
|
|
1256
|
+
/**
|
|
1257
|
+
* Validate locations in legacy registry maps and URL-keyed declarations.
|
|
1258
|
+
*
|
|
1259
|
+
* @param key - Canonical workspace setting name
|
|
1260
|
+
* @param value - Untrusted registry configuration
|
|
1261
|
+
*
|
|
1262
|
+
* @returns Whether the complete setting must be retained in its source
|
|
1263
|
+
*/
|
|
1264
|
+
function hasInvalidRegistryLocations(key, value) {
|
|
1265
|
+
if (!["namedRegistries", "registries"].includes(key) || value === null) return false;
|
|
1266
|
+
if (!isPlainObject(value)) return true;
|
|
1267
|
+
if (key === "namedRegistries") return Object.values(value).some((url) => !isRegistryUrl(url));
|
|
1268
|
+
return Object.entries(value).some(([url, entry]) => isString(entry) ? !isRegistryUrl(entry) : !isRegistryUrl(url));
|
|
1269
|
+
}
|
|
1270
|
+
//#endregion
|
|
1122
1271
|
//#region src/features/settings/permissions.ts
|
|
1123
1272
|
/**
|
|
1124
1273
|
* Validate a dependency's capability map without dropping unknown permissions.
|
|
@@ -1152,6 +1301,65 @@ function hasInvalidPermissionSettings(key, value) {
|
|
|
1152
1301
|
//#endregion
|
|
1153
1302
|
//#region src/features/settings/pnpm-v12-values.ts
|
|
1154
1303
|
/**
|
|
1304
|
+
* Validate the package-map section without dropping future or misspelled keys.
|
|
1305
|
+
*
|
|
1306
|
+
* @param value - Untrusted package-map value
|
|
1307
|
+
*
|
|
1308
|
+
* @returns Whether the section is outside the pnpm 12.13 schema
|
|
1309
|
+
*/
|
|
1310
|
+
function hasInvalidNodePackageMap(value) {
|
|
1311
|
+
if (value === null) return false;
|
|
1312
|
+
if (!value || typeof value !== "object" || isArray(value)) return true;
|
|
1313
|
+
return Object.entries(value).some(([field, entry]) => {
|
|
1314
|
+
let values = [];
|
|
1315
|
+
if (field === "mode") values = [
|
|
1316
|
+
"off",
|
|
1317
|
+
"write-only",
|
|
1318
|
+
"load"
|
|
1319
|
+
];
|
|
1320
|
+
else if (field === "type") values = ["standard", "loose"];
|
|
1321
|
+
return values.length === 0 || entry !== null && (!isString(entry) || !entry.includes("${") && !values.includes(entry));
|
|
1322
|
+
});
|
|
1323
|
+
}
|
|
1324
|
+
/**
|
|
1325
|
+
* Validate cache exclusions and retain their whole section on older targets.
|
|
1326
|
+
*
|
|
1327
|
+
* @param value - Untrusted cache section
|
|
1328
|
+
* @param target - Confirmed release controlling exclusion support
|
|
1329
|
+
*
|
|
1330
|
+
* @returns Whether a section containing exclusions cannot migrate
|
|
1331
|
+
*/
|
|
1332
|
+
function hasInvalidCacheExclusions(value, target) {
|
|
1333
|
+
if (!Object.hasOwn(value, "exclude")) return false;
|
|
1334
|
+
if (!supportsMinimumVersion(target.version, PNPM_V12_14_MINIMUM_VERSION)) return true;
|
|
1335
|
+
return Object.entries(value).some(([field, entry]) => {
|
|
1336
|
+
if (field === "exclude") return entry !== null && (!isArray(entry) || !entry.every(isString));
|
|
1337
|
+
if (field === "read" || field === "write") return entry !== null && !isBoolean(entry) && !(isString(entry) && entry.includes("${"));
|
|
1338
|
+
if (field !== "remote") return true;
|
|
1339
|
+
if (entry === null) return false;
|
|
1340
|
+
if (!entry || typeof entry !== "object" || isArray(entry)) return true;
|
|
1341
|
+
return Object.entries(entry).some(([key, setting]) => {
|
|
1342
|
+
if (key === "org" || key === "organization") return !isString(setting);
|
|
1343
|
+
return key !== "packages" || !isArray(setting) || !setting.every(isString);
|
|
1344
|
+
});
|
|
1345
|
+
});
|
|
1346
|
+
}
|
|
1347
|
+
/**
|
|
1348
|
+
* Check the value expansions introduced by pnpm 12.13 and 12.14.
|
|
1349
|
+
*
|
|
1350
|
+
* @param key - Canonical setting name
|
|
1351
|
+
* @param value - Untrusted setting value
|
|
1352
|
+
* @param target - Confirmed target release
|
|
1353
|
+
*
|
|
1354
|
+
* @returns Whether a package-map, store-index, or cache value is incompatible
|
|
1355
|
+
*/
|
|
1356
|
+
function hasIncompatiblePackageMapOrCache(key, value, target) {
|
|
1357
|
+
if (key === "nodePackageMap") return !supportsMinimumVersion(target.version, PNPM_V12_13_MINIMUM_VERSION) || hasInvalidNodePackageMap(value);
|
|
1358
|
+
if (key === "storeIndexFormat") return !supportsMinimumVersion(target.version, PNPM_V12_13_MINIMUM_VERSION) || value !== null && (!isString(value) || !value.includes("${") && !["sqlite", "files"].includes(value));
|
|
1359
|
+
if (key === "sideEffectsCache" && value && typeof value === "object") return hasInvalidCacheExclusions(value, target);
|
|
1360
|
+
return false;
|
|
1361
|
+
}
|
|
1362
|
+
/**
|
|
1155
1363
|
* Check a loaded linker object without discarding unsupported options.
|
|
1156
1364
|
*
|
|
1157
1365
|
* @param value - Untrusted nodeLinker object
|
|
@@ -1241,6 +1449,7 @@ function hasInvalidLinkerOrLockfileScalarValue(key, value) {
|
|
|
1241
1449
|
* @returns Whether the setting cannot be used by the target release
|
|
1242
1450
|
*/
|
|
1243
1451
|
function hasIncompatibleV12SettingValue(key, value, target) {
|
|
1452
|
+
if (hasIncompatiblePackageMapOrCache(key, value, target)) return true;
|
|
1244
1453
|
const supportsV12_10 = supportsMinimumVersion(target.version, PNPM_V12_10_MINIMUM_VERSION);
|
|
1245
1454
|
if (key === "nodeLinker" && value === "loaded") return !supportsV12_10;
|
|
1246
1455
|
if (key === "failIfNoMatch" && target.compatibility === "v12") return value !== null && !isBoolean(value);
|
|
@@ -1263,6 +1472,22 @@ function hasIncompatibleV12SettingValue(key, value, target) {
|
|
|
1263
1472
|
return false;
|
|
1264
1473
|
}
|
|
1265
1474
|
//#endregion
|
|
1475
|
+
//#region src/features/settings/remote-cache.ts
|
|
1476
|
+
/**
|
|
1477
|
+
* Accept only the project-owned remote cache organization.
|
|
1478
|
+
* Keep machine configuration and unknown fields intact in their source.
|
|
1479
|
+
*
|
|
1480
|
+
* @param key - Canonical workspace setting name
|
|
1481
|
+
* @param value - Untrusted incoming or existing workspace value
|
|
1482
|
+
*
|
|
1483
|
+
* @returns Whether the remote cache setting cannot migrate to project YAML
|
|
1484
|
+
*/
|
|
1485
|
+
function hasInvalidRemoteCacheValue(key, value) {
|
|
1486
|
+
if (key !== "remoteCache" || value === null) return false;
|
|
1487
|
+
if (!value || typeof value !== "object" || isArray(value)) return true;
|
|
1488
|
+
return Object.entries(value).some(([field, entry]) => field !== "org" || entry !== null && !isString(entry));
|
|
1489
|
+
}
|
|
1490
|
+
//#endregion
|
|
1266
1491
|
//#region src/features/settings/versioned-values.ts
|
|
1267
1492
|
/**
|
|
1268
1493
|
* Bounds of the signed 32-bit integer used by pnpm's task priority.
|
|
@@ -1279,7 +1504,10 @@ const MAX_TASK_PRIORITY = 2147483647;
|
|
|
1279
1504
|
* @returns Whether pnpm rejects the value after migration
|
|
1280
1505
|
*/
|
|
1281
1506
|
function hasInvalidV11PatchValue(key, value, target) {
|
|
1282
|
-
if (["httpProxy", "httpsProxy"].includes(key) && supportsMinimumVersion(target.version, PNPM_V11_28_5_MINIMUM_VERSION))
|
|
1507
|
+
if (["httpProxy", "httpsProxy"].includes(key) && supportsMinimumVersion(target.version, PNPM_V11_28_5_MINIMUM_VERSION)) {
|
|
1508
|
+
if (value === null && supportsMinimumVersion(target.version, PNPM_V11_28_6_MINIMUM_VERSION)) return false;
|
|
1509
|
+
return !isString(value);
|
|
1510
|
+
}
|
|
1283
1511
|
if (value === null || value === void 0) return false;
|
|
1284
1512
|
if (key === "allowBuilds" && supportsMinimumVersion(target.version, PNPM_V11_28_3_MINIMUM_VERSION)) return typeof value !== "object" || isArray(value) || Object.values(value).some((entry) => !isBoolean(entry) && !isString(entry));
|
|
1285
1513
|
if (supportsMinimumVersion(target.version, PNPM_V11_28_4_MINIMUM_VERSION)) {
|
|
@@ -1311,7 +1539,7 @@ function hasInvalidScalarValue(key, value, target) {
|
|
|
1311
1539
|
"ndjson",
|
|
1312
1540
|
"silent"
|
|
1313
1541
|
].includes(value);
|
|
1314
|
-
if (key === "provenance") return value !== null && !isBoolean(value);
|
|
1542
|
+
if (key === "provenance" || key === "addMissingPeerTypes") return value !== null && !isBoolean(value);
|
|
1315
1543
|
if (key === "progress") return !isBoolean(value);
|
|
1316
1544
|
if (key === "tagVersionPrefix") return !isString(value);
|
|
1317
1545
|
if (key === "loglevel") return !isString(value) || !value.includes("${") && ![
|
|
@@ -1341,6 +1569,17 @@ function hasInvalidReleaseValue(key, value, target) {
|
|
|
1341
1569
|
return false;
|
|
1342
1570
|
}
|
|
1343
1571
|
/**
|
|
1572
|
+
* Check release-specific reserved aliases without broadening case sensitivity.
|
|
1573
|
+
*
|
|
1574
|
+
* @param prefix - Registry alias as declared
|
|
1575
|
+
* @param target - Confirmed target version
|
|
1576
|
+
*
|
|
1577
|
+
* @returns Whether the alias shadows a protocol introduced by these releases
|
|
1578
|
+
*/
|
|
1579
|
+
function isReservedRegistryPrefix(prefix, target) {
|
|
1580
|
+
return prefix.toLowerCase() === "pkg" && supportsMinimumVersion(target.version, PNPM_V12_5_MINIMUM_VERSION) || prefix === "tool" && supportsMinimumVersion(target.version, PNPM_V12_12_1_MINIMUM_VERSION);
|
|
1581
|
+
}
|
|
1582
|
+
/**
|
|
1344
1583
|
* Check value shapes that changed within a pnpm major release.
|
|
1345
1584
|
*
|
|
1346
1585
|
* Reject the whole setting so cleanup cannot discard unsupported nested values.
|
|
@@ -1352,7 +1591,7 @@ function hasInvalidReleaseValue(key, value, target) {
|
|
|
1352
1591
|
* @returns Whether the value uses a shape unsupported by the selected release
|
|
1353
1592
|
*/
|
|
1354
1593
|
function hasIncompatibleSettingValue(key, value, target) {
|
|
1355
|
-
if (hasInvalidPermissionSettings(key, value) || hasInvalidReleaseValue(key, value, target) || hasInvalidV11PatchValue(key, value, target) || hasIncompatibleV12SettingValue(key, value, target)) return true;
|
|
1594
|
+
if (hasInvalidPermissionSettings(key, value) || hasInvalidRemoteCacheValue(key, value) || hasInvalidReleaseValue(key, value, target) || hasInvalidV11PatchValue(key, value, target) || hasIncompatibleV12SettingValue(key, value, target)) return true;
|
|
1356
1595
|
if (hasInvalidScalarValue(key, value, target)) return true;
|
|
1357
1596
|
const supportsV12_5 = supportsMinimumVersion(target.version, PNPM_V12_5_MINIMUM_VERSION);
|
|
1358
1597
|
if (key === "supportedArchitectures" && isArray(value)) return !supportsV12_5;
|
|
@@ -1370,19 +1609,42 @@ function hasIncompatibleSettingValue(key, value, target) {
|
|
|
1370
1609
|
"constraints"
|
|
1371
1610
|
].some((field) => Object.hasOwn(value, field));
|
|
1372
1611
|
}
|
|
1373
|
-
if (key === "namedRegistries"
|
|
1612
|
+
if (key === "namedRegistries") return Object.keys(value).some((prefix) => isReservedRegistryPrefix(prefix, target));
|
|
1374
1613
|
if (key === "registries") return Object.values(value).some((entry) => {
|
|
1375
1614
|
if (!entry || typeof entry !== "object") return false;
|
|
1376
1615
|
if (Object.hasOwn(entry, "ecosystem") && !supportsV12_5) return true;
|
|
1377
1616
|
if (Object.hasOwn(entry, "packages") && !supportsMinimumVersion(target.version, PNPM_V12_5_1_MINIMUM_VERSION)) return true;
|
|
1378
1617
|
const prefix = Reflect.get(entry, "prefix");
|
|
1379
|
-
return
|
|
1618
|
+
return isString(prefix) && isReservedRegistryPrefix(prefix, target);
|
|
1380
1619
|
});
|
|
1381
1620
|
return false;
|
|
1382
1621
|
}
|
|
1383
1622
|
//#endregion
|
|
1384
1623
|
//#region src/features/settings/schema.ts
|
|
1385
1624
|
/**
|
|
1625
|
+
* Detect legacy runtime fields without a destination in pnpm v11 and newer.
|
|
1626
|
+
*
|
|
1627
|
+
* @param key - Canonical setting name
|
|
1628
|
+
* @param value - Untrusted legacy runtime declaration
|
|
1629
|
+
* @param compatibility - Target major controlling runtime conversion
|
|
1630
|
+
*
|
|
1631
|
+
* @returns Whether converting the runtime would discard sibling fields
|
|
1632
|
+
*/
|
|
1633
|
+
function hasUnmigratableRuntimeFields(key, value, compatibility) {
|
|
1634
|
+
return Boolean(key === "executionEnv" && compatibility !== "v10" && value && typeof value === "object" && Object.keys(value).some((field) => field !== "nodeVersion"));
|
|
1635
|
+
}
|
|
1636
|
+
/**
|
|
1637
|
+
* Check task declarations against the fields consumed by the target release.
|
|
1638
|
+
*
|
|
1639
|
+
* @param value - Untrusted task declarations
|
|
1640
|
+
* @param taskSettings - Supported task field names
|
|
1641
|
+
*
|
|
1642
|
+
* @returns Whether any declared task field is unsupported
|
|
1643
|
+
*/
|
|
1644
|
+
function hasUnsupportedTaskFields(value, taskSettings) {
|
|
1645
|
+
return Boolean(value && typeof value === "object" && Object.values(value).some((task) => task && typeof task === "object" && Object.keys(task).some((field) => !taskSettings.has(field))));
|
|
1646
|
+
}
|
|
1647
|
+
/**
|
|
1386
1648
|
* Create an empty collection of schema selection issues.
|
|
1387
1649
|
*
|
|
1388
1650
|
* @returns Empty issue lists for every supported issue category
|
|
@@ -1487,8 +1749,8 @@ function resolveSettingIssue({ allowedFields, target, key, npmrc, value }) {
|
|
|
1487
1749
|
if (compatibility !== "v10" && PROJECT_REFUSED_SETTINGS.has(key)) return "refused";
|
|
1488
1750
|
if (REGISTRY_SETTINGS.has(key) && containsUnsafeRegistryValue(value)) return "unsafe";
|
|
1489
1751
|
if (PROXY_SETTINGS.has(key) && isString(value) && value.includes("${")) return "unsafe";
|
|
1490
|
-
if (hasIncompatibleSettingValue(key, value, target)) return "incompatible";
|
|
1491
|
-
if (key === "tasks" &&
|
|
1752
|
+
if (hasInvalidRegistryLocations(key, value) || hasUnmigratableRuntimeFields(key, value, compatibility) || hasIncompatibleSettingValue(key, value, target)) return "incompatible";
|
|
1753
|
+
if (key === "tasks" && hasUnsupportedTaskFields(value, taskSettings)) return "incompatible";
|
|
1492
1754
|
if (!workspaceSettings.has(key)) return isSettingFromAnotherVersion(key, compatibility) ? "incompatible" : "unknown";
|
|
1493
1755
|
if (allowedFields && !allowedFields.has(key)) return "unsupported";
|
|
1494
1756
|
}
|
|
@@ -1897,8 +2159,17 @@ function getNpmrcLineKey(line) {
|
|
|
1897
2159
|
const trimmed = line.trim();
|
|
1898
2160
|
if (!trimmed || trimmed.startsWith(";") || trimmed.startsWith("#")) return;
|
|
1899
2161
|
const index = trimmed.indexOf("=");
|
|
1900
|
-
if (index
|
|
1901
|
-
|
|
2162
|
+
if (index === 0) return;
|
|
2163
|
+
let key = (index < 0 ? trimmed : trimmed.slice(0, index)).trim();
|
|
2164
|
+
if (key.startsWith("\"") && key.endsWith("\"") || key.startsWith("'") && key.endsWith("'")) {
|
|
2165
|
+
if (key.startsWith("'")) key = key.slice(1, -1);
|
|
2166
|
+
try {
|
|
2167
|
+
const parsed = JSON.parse(key);
|
|
2168
|
+
if (!isString(parsed)) return;
|
|
2169
|
+
key = parsed;
|
|
2170
|
+
} catch {}
|
|
2171
|
+
} else key = key.replace(/\\(?<escaped>[\\;#])|[;#].*$/gu, (_, escaped) => escaped ?? "");
|
|
2172
|
+
return normalizeNpmrcKey(key);
|
|
1902
2173
|
}
|
|
1903
2174
|
/**
|
|
1904
2175
|
* Check whether a `.npmrc` key is auth/registry-related and should stay in
|
|
@@ -1925,7 +2196,7 @@ function isNpmrcAuthOrRegistryKey(key) {
|
|
|
1925
2196
|
* @param compatibility - Concrete pnpm compatibility target
|
|
1926
2197
|
* @param migratedKeys - Original `.npmrc` keys that were migrated
|
|
1927
2198
|
*
|
|
1928
|
-
* @returns
|
|
2199
|
+
* @returns Whether the file was changed or removed
|
|
1929
2200
|
*
|
|
1930
2201
|
* @throws {Error} When file read/write operations fail
|
|
1931
2202
|
*
|
|
@@ -1938,18 +2209,25 @@ async function pruneNpmrc(path, compatibility, migratedKeys) {
|
|
|
1938
2209
|
const migratedKeySet = new Set(migratedKeys.map(normalizeNpmrcKey));
|
|
1939
2210
|
const content = await fsReadFile(path);
|
|
1940
2211
|
let inSection = false;
|
|
1941
|
-
|
|
2212
|
+
let removed = false;
|
|
2213
|
+
const lines = content.split(/\r\n|[\r\n]/u).filter((line) => {
|
|
1942
2214
|
if (/^\[[^\]]*\]\s*$/u.test(line.replace(/^\uFEFF/u, ""))) inSection = true;
|
|
1943
2215
|
if (inSection) return true;
|
|
1944
2216
|
const key = getNpmrcLineKey(line);
|
|
1945
2217
|
if (!key) return true;
|
|
1946
|
-
|
|
1947
|
-
|
|
2218
|
+
if (migratedKeySet.has(key)) {
|
|
2219
|
+
removed = true;
|
|
2220
|
+
return false;
|
|
2221
|
+
}
|
|
2222
|
+
return true;
|
|
2223
|
+
});
|
|
2224
|
+
if (!removed) return false;
|
|
2225
|
+
const updatedContent = lines.join("\n").trimEnd();
|
|
1948
2226
|
if (compatibility !== "v10" && !updatedContent.trim()) {
|
|
1949
2227
|
await fsRemoveFile(path);
|
|
1950
|
-
return;
|
|
2228
|
+
return true;
|
|
1951
2229
|
}
|
|
1952
|
-
|
|
2230
|
+
return fsWriteFileIfChanged(path, updatedContent);
|
|
1953
2231
|
}
|
|
1954
2232
|
/**
|
|
1955
2233
|
* Read `.npmrc` and return settings that should be migrated into workspace config.
|
|
@@ -2488,10 +2766,10 @@ async function persistMigration(options) {
|
|
|
2488
2766
|
if (!cleanNpmrc) return result;
|
|
2489
2767
|
const pruneTasks = projectNpmrcs.projects.flatMap((project) => {
|
|
2490
2768
|
const appliedKeys = selectAppliedProjectKeys(project, finalSettings);
|
|
2491
|
-
return appliedKeys.length ? [pruneNpmrc(project.npmrcPath, compatibility, appliedKeys).then(() => project.npmrcPath)] : [];
|
|
2769
|
+
return appliedKeys.length ? [pruneNpmrc(project.npmrcPath, compatibility, appliedKeys).then((changed) => changed ? project.npmrcPath : void 0)] : [];
|
|
2492
2770
|
});
|
|
2493
|
-
if (npmrcExists && appliedNpmrcKeys.length) pruneTasks.push(pruneNpmrc(npmrcPath, compatibility, appliedNpmrcKeys).then(() => npmrcPath));
|
|
2494
|
-
const cleanedNpmrcPaths = await Promise.all(pruneTasks);
|
|
2771
|
+
if (npmrcExists && appliedNpmrcKeys.length) pruneTasks.push(pruneNpmrc(npmrcPath, compatibility, appliedNpmrcKeys).then((changed) => changed ? npmrcPath : void 0));
|
|
2772
|
+
const cleanedNpmrcPaths = (await Promise.all(pruneTasks)).filter(isString);
|
|
2495
2773
|
result.changedFiles.push(...cleanedNpmrcPaths);
|
|
2496
2774
|
result.sourceSettingsCleaned ||= cleanedNpmrcPaths.length > 0;
|
|
2497
2775
|
return result;
|
|
@@ -2587,9 +2865,9 @@ function normalizeSideEffectsCache(settings) {
|
|
|
2587
2865
|
* @returns Nothing; the URL's declaration is created or updated in place
|
|
2588
2866
|
*/
|
|
2589
2867
|
function addRegistryScope(declarations, url, scope) {
|
|
2590
|
-
declarations
|
|
2591
|
-
const declaration = declarations[url];
|
|
2868
|
+
const declaration = declarations.get(url) ?? {};
|
|
2592
2869
|
declaration.scopes = unique([...declaration.scopes ?? [], scope]);
|
|
2870
|
+
declarations.set(url, declaration);
|
|
2593
2871
|
}
|
|
2594
2872
|
/**
|
|
2595
2873
|
* Find source URLs with multiple active aliases across the merged settings.
|
|
@@ -2618,9 +2896,9 @@ function findRegistryAliasConflicts(prefixesByUrl, namedRegistries, canonicalPre
|
|
|
2618
2896
|
function normalizeNamedRegistries(settings, warnings, canonicalSettings) {
|
|
2619
2897
|
const { namedRegistries } = settings;
|
|
2620
2898
|
if (!namedRegistries) return;
|
|
2621
|
-
const declarations =
|
|
2899
|
+
const declarations = /* @__PURE__ */ new Map();
|
|
2622
2900
|
for (const [key, value] of Object.entries(settings.registries ?? {})) if (isString(value)) addRegistryScope(declarations, value, key === "default" ? "@" : key);
|
|
2623
|
-
else declarations
|
|
2901
|
+
else declarations.set(key, { ...value });
|
|
2624
2902
|
const canonicalPrefixes = new Set(Object.values(canonicalSettings.registries ?? {}).flatMap((value) => !isString(value) && isString(value?.prefix) ? [value.prefix] : []));
|
|
2625
2903
|
const prefixesByUrl = /* @__PURE__ */ new Map();
|
|
2626
2904
|
const registryAliases = Object.entries(namedRegistries).filter(([prefix]) => !canonicalPrefixes.has(prefix));
|
|
@@ -2635,17 +2913,17 @@ function normalizeNamedRegistries(settings, warnings, canonicalSettings) {
|
|
|
2635
2913
|
return;
|
|
2636
2914
|
}
|
|
2637
2915
|
for (const [url, [prefix]] of prefixesByUrl) {
|
|
2638
|
-
declarations
|
|
2639
|
-
const
|
|
2640
|
-
const canonicalDeclaration = canonicalSettings.registries?.[url];
|
|
2916
|
+
const declaration = declarations.get(url) ?? {};
|
|
2917
|
+
const canonicalDeclaration = canonicalSettings.registries && Object.hasOwn(canonicalSettings.registries, url) ? canonicalSettings.registries[url] : void 0;
|
|
2641
2918
|
const declaredPrefix = (isString(canonicalDeclaration) ? void 0 : canonicalDeclaration?.prefix) ?? declaration.prefix;
|
|
2642
2919
|
if (isString(declaredPrefix) && declaredPrefix !== prefix) {
|
|
2643
2920
|
warnings.push(`namedRegistries was kept because ${url} already declares prefix ${declaredPrefix}.`);
|
|
2644
2921
|
return;
|
|
2645
2922
|
}
|
|
2646
2923
|
declaration.prefix = prefix;
|
|
2924
|
+
declarations.set(url, declaration);
|
|
2647
2925
|
}
|
|
2648
|
-
settings.registries = declarations;
|
|
2926
|
+
settings.registries = Object.fromEntries(declarations);
|
|
2649
2927
|
Reflect.deleteProperty(settings, "namedRegistries");
|
|
2650
2928
|
}
|
|
2651
2929
|
/**
|
|
@@ -3207,7 +3485,7 @@ const bold = getColor("bold");
|
|
|
3207
3485
|
//#endregion
|
|
3208
3486
|
//#region package.json
|
|
3209
3487
|
var name = "pnpm-settings-migrator";
|
|
3210
|
-
var version = "0.
|
|
3488
|
+
var version = "0.18.0";
|
|
3211
3489
|
//#endregion
|
|
3212
3490
|
//#region src/cli/options.ts
|
|
3213
3491
|
/**
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Document } from "yaml";
|
|
2
|
-
import { PnpmSettings, RegistryDeclaration } from "@pnpm/types";
|
|
2
|
+
import { PnpmSettings, RegistryDeclaration, SideEffectsCacheSettings } from "@pnpm/types";
|
|
3
3
|
//#region src/types/options.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* Compatibility target used by the migrator.
|
|
@@ -259,6 +259,27 @@ interface PnpmSkillsSettings {
|
|
|
259
259
|
* Settings supported by pnpm v12, subject to the resolved version capabilities.
|
|
260
260
|
*/
|
|
261
261
|
interface PnpmSettingsV12 {
|
|
262
|
+
/**
|
|
263
|
+
* Package-map generation and loading options (pnpm 12.13+).
|
|
264
|
+
*/
|
|
265
|
+
nodePackageMap?: PnpmNodePackageMapSettings | null;
|
|
266
|
+
/**
|
|
267
|
+
* Store package index backend; files supports network shares (pnpm 12.13+).
|
|
268
|
+
*/
|
|
269
|
+
storeIndexFormat?: 'sqlite' | 'files' | null;
|
|
270
|
+
/**
|
|
271
|
+
* Build cache policy, including exclusions from pnpm 12.14 onward.
|
|
272
|
+
*/
|
|
273
|
+
sideEffectsCache?: boolean | PnpmSideEffectsCacheSettings;
|
|
274
|
+
/**
|
|
275
|
+
* Link available companion @types packages as optional peers (pnpm 12.12+).
|
|
276
|
+
* Null leaves pnpm's default unchanged.
|
|
277
|
+
*/
|
|
278
|
+
addMissingPeerTypes?: boolean | null;
|
|
279
|
+
/**
|
|
280
|
+
* Project-owned remote cache configuration (pnpm 12.12+).
|
|
281
|
+
*/
|
|
282
|
+
remoteCache?: PnpmRemoteCacheSettings | null;
|
|
262
283
|
/**
|
|
263
284
|
* Per-dependency build and agent skill permissions (pnpm 12.11+).
|
|
264
285
|
*/
|
|
@@ -353,6 +374,41 @@ interface PnpmSettingsV12 {
|
|
|
353
374
|
*/
|
|
354
375
|
tasks?: Record<string, PnpmTaskSettings>;
|
|
355
376
|
}
|
|
377
|
+
/**
|
|
378
|
+
* Package-map section introduced in pnpm 12.13. Explicit fields take precedence
|
|
379
|
+
* over nodeExperimentalPackageMap and nodePackageMapType in the same source.
|
|
380
|
+
*/
|
|
381
|
+
interface PnpmNodePackageMapSettings {
|
|
382
|
+
/**
|
|
383
|
+
* Disable the map, write it for tools, or also load it in Node.js scripts.
|
|
384
|
+
* Null leaves the legacy setting or pnpm's default effective.
|
|
385
|
+
*/
|
|
386
|
+
mode?: 'off' | 'write-only' | 'load' | null;
|
|
387
|
+
/**
|
|
388
|
+
* Map format; null leaves the legacy setting or default effective.
|
|
389
|
+
*/
|
|
390
|
+
type?: 'standard' | 'loose' | null;
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Structured build cache options with pnpm 12.14 package exclusions.
|
|
394
|
+
*/
|
|
395
|
+
interface PnpmSideEffectsCacheSettings extends SideEffectsCacheSettings {
|
|
396
|
+
/**
|
|
397
|
+
* Package name, scope, or name@version patterns whose builds are neither
|
|
398
|
+
* restored nor saved; null leaves exclusions unset.
|
|
399
|
+
*/
|
|
400
|
+
exclude?: string[] | null;
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Remote cache options allowed in project YAML. Server and signing settings
|
|
404
|
+
* belong in trusted global configuration or environment variables.
|
|
405
|
+
*/
|
|
406
|
+
interface PnpmRemoteCacheSettings {
|
|
407
|
+
/**
|
|
408
|
+
* Organization owning shared artifacts; null leaves it unset.
|
|
409
|
+
*/
|
|
410
|
+
org?: string | null;
|
|
411
|
+
}
|
|
356
412
|
//#endregion
|
|
357
413
|
//#region src/types/workspace.d.ts
|
|
358
414
|
/**
|
|
@@ -473,6 +529,14 @@ interface PnpmSettingsDeprecated {
|
|
|
473
529
|
* Settings introduced in pnpm v11.
|
|
474
530
|
*/
|
|
475
531
|
interface PnpmSettingsV11 {
|
|
532
|
+
/**
|
|
533
|
+
* HTTP proxy; null leaves the proxy unset except in pnpm 11.28.5.
|
|
534
|
+
*/
|
|
535
|
+
httpProxy?: string | null;
|
|
536
|
+
/**
|
|
537
|
+
* HTTPS proxy; null leaves the proxy unset except in pnpm 11.28.5.
|
|
538
|
+
*/
|
|
539
|
+
httpsProxy?: string | null;
|
|
476
540
|
/**
|
|
477
541
|
* Publish provenance preference. Consumed from workspace configuration by
|
|
478
542
|
* pnpm v11 and pnpm v12 starting with 12.11. Null leaves it unset.
|
|
@@ -522,7 +586,7 @@ interface PnpmSettingsV11 {
|
|
|
522
586
|
/**
|
|
523
587
|
* `pnpm-workspace.yaml` types.
|
|
524
588
|
*/
|
|
525
|
-
type PnpmWorkspace = Omit<PnpmSettings, 'lockfile' | 'nodeLinker' | 'provenance' | 'registries' | 'supportedArchitectures' | 'tasks'> & PnpmSettingsDeprecated & PnpmSettingsV11 & PnpmSettingsV12 & PnpmWorkspaceLegacy;
|
|
589
|
+
type PnpmWorkspace = Omit<PnpmSettings, 'httpProxy' | 'httpsProxy' | 'lockfile' | 'nodeLinker' | 'provenance' | 'registries' | 'sideEffectsCache' | 'supportedArchitectures' | 'tasks'> & PnpmSettingsDeprecated & PnpmSettingsV11 & PnpmSettingsV12 & PnpmWorkspaceLegacy;
|
|
526
590
|
//#endregion
|
|
527
591
|
//#region src/types/package-json.d.ts
|
|
528
592
|
/**
|
|
@@ -730,4 +794,4 @@ export declare function migratePnpmSettings(rawOptions?: Options): Promise<Migra
|
|
|
730
794
|
*/
|
|
731
795
|
export declare function resolveOptions(options?: Options): ResolvedOptions;
|
|
732
796
|
//#endregion
|
|
733
|
-
export type { CompatibilityTarget, GlobalShimPolicy, MergeStrategy, MigrationResult, NpmRC, Options, PackageJson, PackageManagerEngine, PnpmCargoSettings, PnpmLoadedNodeLinkerSettings, PnpmLockfileSettings, PnpmPackagePermissions, PnpmPythonSettings, PnpmRegistryDeclaration, PnpmSettingsDeprecated, PnpmSettingsV11, PnpmSettingsV12, PnpmSkillsSettings, PnpmTaskSettings, PnpmWorkspace, PnpmWorkspaceLegacy, ResolvedOptions, RuntimeEngine, SettingsChange };
|
|
797
|
+
export type { CompatibilityTarget, GlobalShimPolicy, MergeStrategy, MigrationResult, NpmRC, Options, PackageJson, PackageManagerEngine, PnpmCargoSettings, PnpmLoadedNodeLinkerSettings, PnpmLockfileSettings, PnpmNodePackageMapSettings, PnpmPackagePermissions, PnpmPythonSettings, PnpmRegistryDeclaration, PnpmRemoteCacheSettings, PnpmSettingsDeprecated, PnpmSettingsV11, PnpmSettingsV12, PnpmSideEffectsCacheSettings, PnpmSkillsSettings, PnpmTaskSettings, PnpmWorkspace, PnpmWorkspaceLegacy, ResolvedOptions, RuntimeEngine, SettingsChange };
|
package/dist/index.mjs
CHANGED
|
@@ -585,6 +585,14 @@ const PNPM_V11_28_5_MINIMUM_VERSION = [
|
|
|
585
585
|
5
|
|
586
586
|
];
|
|
587
587
|
/**
|
|
588
|
+
* Release restoring null proxy values as unset configuration.
|
|
589
|
+
*/
|
|
590
|
+
const PNPM_V11_28_6_MINIMUM_VERSION = [
|
|
591
|
+
11,
|
|
592
|
+
28,
|
|
593
|
+
6
|
|
594
|
+
];
|
|
595
|
+
/**
|
|
588
596
|
* Release adding force, publishing, and reporter settings.
|
|
589
597
|
*/
|
|
590
598
|
const PNPM_V12_7_MINIMUM_VERSION = [
|
|
@@ -625,6 +633,38 @@ const PNPM_V12_11_MINIMUM_VERSION = [
|
|
|
625
633
|
0
|
|
626
634
|
];
|
|
627
635
|
/**
|
|
636
|
+
* Release introducing remote cache configuration and missing peer types.
|
|
637
|
+
*/
|
|
638
|
+
const PNPM_V12_12_MINIMUM_VERSION = [
|
|
639
|
+
12,
|
|
640
|
+
12,
|
|
641
|
+
0
|
|
642
|
+
];
|
|
643
|
+
/**
|
|
644
|
+
* Release reserving the tool registry prefix.
|
|
645
|
+
*/
|
|
646
|
+
const PNPM_V12_12_1_MINIMUM_VERSION = [
|
|
647
|
+
12,
|
|
648
|
+
12,
|
|
649
|
+
1
|
|
650
|
+
];
|
|
651
|
+
/**
|
|
652
|
+
* Release introducing package map options and file-based store indexes.
|
|
653
|
+
*/
|
|
654
|
+
const PNPM_V12_13_MINIMUM_VERSION = [
|
|
655
|
+
12,
|
|
656
|
+
13,
|
|
657
|
+
0
|
|
658
|
+
];
|
|
659
|
+
/**
|
|
660
|
+
* Release introducing side-effects cache exclusions.
|
|
661
|
+
*/
|
|
662
|
+
const PNPM_V12_14_MINIMUM_VERSION = [
|
|
663
|
+
12,
|
|
664
|
+
14,
|
|
665
|
+
0
|
|
666
|
+
];
|
|
667
|
+
/**
|
|
628
668
|
* Workspace settings first consumed by pnpm v12 in 12.11.0.
|
|
629
669
|
*/
|
|
630
670
|
const PNPM_V12_11_WORKSPACE_SETTINGS = [
|
|
@@ -650,6 +690,16 @@ const PNPM_V12_7_WORKSPACE_SETTINGS = [
|
|
|
650
690
|
* @see https://github.com/pnpm/pnpm/blob/v12.4.0/pnpm/crates/config/src/workspace_yaml.rs
|
|
651
691
|
*/
|
|
652
692
|
const PNPM_SETTINGS_CAPABILITIES = [
|
|
693
|
+
{
|
|
694
|
+
minimumVersion: PNPM_V12_13_MINIMUM_VERSION,
|
|
695
|
+
workspaceFields: ["nodePackageMap", "storeIndexFormat"],
|
|
696
|
+
taskFields: []
|
|
697
|
+
},
|
|
698
|
+
{
|
|
699
|
+
minimumVersion: PNPM_V12_12_MINIMUM_VERSION,
|
|
700
|
+
workspaceFields: ["addMissingPeerTypes", "remoteCache"],
|
|
701
|
+
taskFields: []
|
|
702
|
+
},
|
|
653
703
|
{
|
|
654
704
|
minimumVersion: PNPM_V12_11_MINIMUM_VERSION,
|
|
655
705
|
workspaceFields: PNPM_V12_11_WORKSPACE_SETTINGS,
|
|
@@ -731,9 +781,82 @@ const PNPM_V12_ONLY_WORKSPACE_SETTINGS = [
|
|
|
731
781
|
"globalShims"
|
|
732
782
|
];
|
|
733
783
|
/**
|
|
784
|
+
* Legacy fields absent from pnpm v12's workspace reader through 12.14.0.
|
|
785
|
+
* Removed aliases with explicit migration replacements are kept separately.
|
|
786
|
+
*
|
|
787
|
+
* @see https://github.com/pnpm/pnpm/blob/v12.14.0/pnpm/crates/config/src/workspace_yaml/settings.rs
|
|
788
|
+
*/
|
|
789
|
+
const PNPM_V12_IGNORED_WORKSPACE_SETTINGS = [
|
|
790
|
+
"access",
|
|
791
|
+
"allowNew",
|
|
792
|
+
"allowSameVersion",
|
|
793
|
+
"auditIgnorePrune",
|
|
794
|
+
"autoConfirmAllPrompts",
|
|
795
|
+
"binLinks",
|
|
796
|
+
"commitHooks",
|
|
797
|
+
"cpu",
|
|
798
|
+
"depth",
|
|
799
|
+
"description",
|
|
800
|
+
"dev",
|
|
801
|
+
"dryRun",
|
|
802
|
+
"enablePnp",
|
|
803
|
+
"extraBinPaths",
|
|
804
|
+
"extraEnv",
|
|
805
|
+
"fetchingConcurrency",
|
|
806
|
+
"force",
|
|
807
|
+
"git",
|
|
808
|
+
"gitTagVersion",
|
|
809
|
+
"global",
|
|
810
|
+
"globalPath",
|
|
811
|
+
"globalPrefix",
|
|
812
|
+
"ignoreCurrentSpecifiers",
|
|
813
|
+
"json",
|
|
814
|
+
"libc",
|
|
815
|
+
"localAddress",
|
|
816
|
+
"lockfileOnly",
|
|
817
|
+
"long",
|
|
818
|
+
"message",
|
|
819
|
+
"npmPath",
|
|
820
|
+
"only",
|
|
821
|
+
"os",
|
|
822
|
+
"packDestination",
|
|
823
|
+
"packGzipLevel",
|
|
824
|
+
"parseable",
|
|
825
|
+
"pnpmExecPath",
|
|
826
|
+
"preserveAbsolutePaths",
|
|
827
|
+
"production",
|
|
828
|
+
"publishBranch",
|
|
829
|
+
"recursive",
|
|
830
|
+
"registriesByPrefix",
|
|
831
|
+
"registriesByScope",
|
|
832
|
+
"registryOptionsByUrl",
|
|
833
|
+
"requiredScripts",
|
|
834
|
+
"runtime",
|
|
835
|
+
"save",
|
|
836
|
+
"saveDev",
|
|
837
|
+
"saveOptional",
|
|
838
|
+
"saveProd",
|
|
839
|
+
"sideEffectsCacheRead",
|
|
840
|
+
"sideEffectsCacheWrite",
|
|
841
|
+
"signGitTag",
|
|
842
|
+
"strictSsl",
|
|
843
|
+
"tag",
|
|
844
|
+
"tryLoadDefaultPnpmfile",
|
|
845
|
+
"umask",
|
|
846
|
+
"useGitBranchLockfile",
|
|
847
|
+
"useLockfile",
|
|
848
|
+
"useRunningStoreServer",
|
|
849
|
+
"version",
|
|
850
|
+
"workspacePackagePatterns",
|
|
851
|
+
"workspacePackages",
|
|
852
|
+
"workspacePrefix",
|
|
853
|
+
"workspaceRoot",
|
|
854
|
+
"yes"
|
|
855
|
+
];
|
|
856
|
+
/**
|
|
734
857
|
* Lookup used to remove v11-only fields from the v12 workspace allowlist.
|
|
735
858
|
*/
|
|
736
|
-
const v11OnlyWorkspaceSettings = new Set(PNPM_V11_ONLY_WORKSPACE_SETTINGS);
|
|
859
|
+
const v11OnlyWorkspaceSettings = /* @__PURE__ */ new Set([...PNPM_V11_ONLY_WORKSPACE_SETTINGS, ...PNPM_V12_IGNORED_WORKSPACE_SETTINGS]);
|
|
737
860
|
/**
|
|
738
861
|
* Fields accepted by registry declarations with pnpm 12.9 concurrency limits.
|
|
739
862
|
*/
|
|
@@ -768,7 +891,7 @@ const PROJECT_REFUSED_SETTINGS = new Set(PNPM_PROJECT_REFUSED_SETTINGS);
|
|
|
768
891
|
/**
|
|
769
892
|
* Fields reported as incompatible when selecting settings for v12.
|
|
770
893
|
*/
|
|
771
|
-
const PNPM_V11_ONLY_SETTINGS = new Set(PNPM_V11_ONLY_WORKSPACE_SETTINGS);
|
|
894
|
+
const PNPM_V11_ONLY_SETTINGS = /* @__PURE__ */ new Set([...PNPM_V11_ONLY_WORKSPACE_SETTINGS, ...PNPM_V12_IGNORED_WORKSPACE_SETTINGS]);
|
|
772
895
|
/**
|
|
773
896
|
* Allowed workspace fields for the v11 target.
|
|
774
897
|
*/
|
|
@@ -1112,6 +1235,32 @@ function formatSettingsIssues(options) {
|
|
|
1112
1235
|
return warnings;
|
|
1113
1236
|
}
|
|
1114
1237
|
//#endregion
|
|
1238
|
+
//#region src/features/settings/registries.ts
|
|
1239
|
+
/**
|
|
1240
|
+
* Check that a registry location is a URL rather than an object property name.
|
|
1241
|
+
*
|
|
1242
|
+
* @param value - Untrusted registry location
|
|
1243
|
+
*
|
|
1244
|
+
* @returns Whether the location is an absolute or scheme-relative URL
|
|
1245
|
+
*/
|
|
1246
|
+
function isRegistryUrl(value) {
|
|
1247
|
+
return isString(value) && URL.canParse(value.startsWith("//") ? `https:${value}` : value);
|
|
1248
|
+
}
|
|
1249
|
+
/**
|
|
1250
|
+
* Validate locations in legacy registry maps and URL-keyed declarations.
|
|
1251
|
+
*
|
|
1252
|
+
* @param key - Canonical workspace setting name
|
|
1253
|
+
* @param value - Untrusted registry configuration
|
|
1254
|
+
*
|
|
1255
|
+
* @returns Whether the complete setting must be retained in its source
|
|
1256
|
+
*/
|
|
1257
|
+
function hasInvalidRegistryLocations(key, value) {
|
|
1258
|
+
if (!["namedRegistries", "registries"].includes(key) || value === null) return false;
|
|
1259
|
+
if (!isPlainObject(value)) return true;
|
|
1260
|
+
if (key === "namedRegistries") return Object.values(value).some((url) => !isRegistryUrl(url));
|
|
1261
|
+
return Object.entries(value).some(([url, entry]) => isString(entry) ? !isRegistryUrl(entry) : !isRegistryUrl(url));
|
|
1262
|
+
}
|
|
1263
|
+
//#endregion
|
|
1115
1264
|
//#region src/features/settings/permissions.ts
|
|
1116
1265
|
/**
|
|
1117
1266
|
* Validate a dependency's capability map without dropping unknown permissions.
|
|
@@ -1145,6 +1294,65 @@ function hasInvalidPermissionSettings(key, value) {
|
|
|
1145
1294
|
//#endregion
|
|
1146
1295
|
//#region src/features/settings/pnpm-v12-values.ts
|
|
1147
1296
|
/**
|
|
1297
|
+
* Validate the package-map section without dropping future or misspelled keys.
|
|
1298
|
+
*
|
|
1299
|
+
* @param value - Untrusted package-map value
|
|
1300
|
+
*
|
|
1301
|
+
* @returns Whether the section is outside the pnpm 12.13 schema
|
|
1302
|
+
*/
|
|
1303
|
+
function hasInvalidNodePackageMap(value) {
|
|
1304
|
+
if (value === null) return false;
|
|
1305
|
+
if (!value || typeof value !== "object" || isArray(value)) return true;
|
|
1306
|
+
return Object.entries(value).some(([field, entry]) => {
|
|
1307
|
+
let values = [];
|
|
1308
|
+
if (field === "mode") values = [
|
|
1309
|
+
"off",
|
|
1310
|
+
"write-only",
|
|
1311
|
+
"load"
|
|
1312
|
+
];
|
|
1313
|
+
else if (field === "type") values = ["standard", "loose"];
|
|
1314
|
+
return values.length === 0 || entry !== null && (!isString(entry) || !entry.includes("${") && !values.includes(entry));
|
|
1315
|
+
});
|
|
1316
|
+
}
|
|
1317
|
+
/**
|
|
1318
|
+
* Validate cache exclusions and retain their whole section on older targets.
|
|
1319
|
+
*
|
|
1320
|
+
* @param value - Untrusted cache section
|
|
1321
|
+
* @param target - Confirmed release controlling exclusion support
|
|
1322
|
+
*
|
|
1323
|
+
* @returns Whether a section containing exclusions cannot migrate
|
|
1324
|
+
*/
|
|
1325
|
+
function hasInvalidCacheExclusions(value, target) {
|
|
1326
|
+
if (!Object.hasOwn(value, "exclude")) return false;
|
|
1327
|
+
if (!supportsMinimumVersion(target.version, PNPM_V12_14_MINIMUM_VERSION)) return true;
|
|
1328
|
+
return Object.entries(value).some(([field, entry]) => {
|
|
1329
|
+
if (field === "exclude") return entry !== null && (!isArray(entry) || !entry.every(isString));
|
|
1330
|
+
if (field === "read" || field === "write") return entry !== null && !isBoolean(entry) && !(isString(entry) && entry.includes("${"));
|
|
1331
|
+
if (field !== "remote") return true;
|
|
1332
|
+
if (entry === null) return false;
|
|
1333
|
+
if (!entry || typeof entry !== "object" || isArray(entry)) return true;
|
|
1334
|
+
return Object.entries(entry).some(([key, setting]) => {
|
|
1335
|
+
if (key === "org" || key === "organization") return !isString(setting);
|
|
1336
|
+
return key !== "packages" || !isArray(setting) || !setting.every(isString);
|
|
1337
|
+
});
|
|
1338
|
+
});
|
|
1339
|
+
}
|
|
1340
|
+
/**
|
|
1341
|
+
* Check the value expansions introduced by pnpm 12.13 and 12.14.
|
|
1342
|
+
*
|
|
1343
|
+
* @param key - Canonical setting name
|
|
1344
|
+
* @param value - Untrusted setting value
|
|
1345
|
+
* @param target - Confirmed target release
|
|
1346
|
+
*
|
|
1347
|
+
* @returns Whether a package-map, store-index, or cache value is incompatible
|
|
1348
|
+
*/
|
|
1349
|
+
function hasIncompatiblePackageMapOrCache(key, value, target) {
|
|
1350
|
+
if (key === "nodePackageMap") return !supportsMinimumVersion(target.version, PNPM_V12_13_MINIMUM_VERSION) || hasInvalidNodePackageMap(value);
|
|
1351
|
+
if (key === "storeIndexFormat") return !supportsMinimumVersion(target.version, PNPM_V12_13_MINIMUM_VERSION) || value !== null && (!isString(value) || !value.includes("${") && !["sqlite", "files"].includes(value));
|
|
1352
|
+
if (key === "sideEffectsCache" && value && typeof value === "object") return hasInvalidCacheExclusions(value, target);
|
|
1353
|
+
return false;
|
|
1354
|
+
}
|
|
1355
|
+
/**
|
|
1148
1356
|
* Check a loaded linker object without discarding unsupported options.
|
|
1149
1357
|
*
|
|
1150
1358
|
* @param value - Untrusted nodeLinker object
|
|
@@ -1234,6 +1442,7 @@ function hasInvalidLinkerOrLockfileScalarValue(key, value) {
|
|
|
1234
1442
|
* @returns Whether the setting cannot be used by the target release
|
|
1235
1443
|
*/
|
|
1236
1444
|
function hasIncompatibleV12SettingValue(key, value, target) {
|
|
1445
|
+
if (hasIncompatiblePackageMapOrCache(key, value, target)) return true;
|
|
1237
1446
|
const supportsV12_10 = supportsMinimumVersion(target.version, PNPM_V12_10_MINIMUM_VERSION);
|
|
1238
1447
|
if (key === "nodeLinker" && value === "loaded") return !supportsV12_10;
|
|
1239
1448
|
if (key === "failIfNoMatch" && target.compatibility === "v12") return value !== null && !isBoolean(value);
|
|
@@ -1256,6 +1465,22 @@ function hasIncompatibleV12SettingValue(key, value, target) {
|
|
|
1256
1465
|
return false;
|
|
1257
1466
|
}
|
|
1258
1467
|
//#endregion
|
|
1468
|
+
//#region src/features/settings/remote-cache.ts
|
|
1469
|
+
/**
|
|
1470
|
+
* Accept only the project-owned remote cache organization.
|
|
1471
|
+
* Keep machine configuration and unknown fields intact in their source.
|
|
1472
|
+
*
|
|
1473
|
+
* @param key - Canonical workspace setting name
|
|
1474
|
+
* @param value - Untrusted incoming or existing workspace value
|
|
1475
|
+
*
|
|
1476
|
+
* @returns Whether the remote cache setting cannot migrate to project YAML
|
|
1477
|
+
*/
|
|
1478
|
+
function hasInvalidRemoteCacheValue(key, value) {
|
|
1479
|
+
if (key !== "remoteCache" || value === null) return false;
|
|
1480
|
+
if (!value || typeof value !== "object" || isArray(value)) return true;
|
|
1481
|
+
return Object.entries(value).some(([field, entry]) => field !== "org" || entry !== null && !isString(entry));
|
|
1482
|
+
}
|
|
1483
|
+
//#endregion
|
|
1259
1484
|
//#region src/features/settings/versioned-values.ts
|
|
1260
1485
|
/**
|
|
1261
1486
|
* Bounds of the signed 32-bit integer used by pnpm's task priority.
|
|
@@ -1272,7 +1497,10 @@ const MAX_TASK_PRIORITY = 2147483647;
|
|
|
1272
1497
|
* @returns Whether pnpm rejects the value after migration
|
|
1273
1498
|
*/
|
|
1274
1499
|
function hasInvalidV11PatchValue(key, value, target) {
|
|
1275
|
-
if (["httpProxy", "httpsProxy"].includes(key) && supportsMinimumVersion(target.version, PNPM_V11_28_5_MINIMUM_VERSION))
|
|
1500
|
+
if (["httpProxy", "httpsProxy"].includes(key) && supportsMinimumVersion(target.version, PNPM_V11_28_5_MINIMUM_VERSION)) {
|
|
1501
|
+
if (value === null && supportsMinimumVersion(target.version, PNPM_V11_28_6_MINIMUM_VERSION)) return false;
|
|
1502
|
+
return !isString(value);
|
|
1503
|
+
}
|
|
1276
1504
|
if (value === null || value === void 0) return false;
|
|
1277
1505
|
if (key === "allowBuilds" && supportsMinimumVersion(target.version, PNPM_V11_28_3_MINIMUM_VERSION)) return typeof value !== "object" || isArray(value) || Object.values(value).some((entry) => !isBoolean(entry) && !isString(entry));
|
|
1278
1506
|
if (supportsMinimumVersion(target.version, PNPM_V11_28_4_MINIMUM_VERSION)) {
|
|
@@ -1304,7 +1532,7 @@ function hasInvalidScalarValue(key, value, target) {
|
|
|
1304
1532
|
"ndjson",
|
|
1305
1533
|
"silent"
|
|
1306
1534
|
].includes(value);
|
|
1307
|
-
if (key === "provenance") return value !== null && !isBoolean(value);
|
|
1535
|
+
if (key === "provenance" || key === "addMissingPeerTypes") return value !== null && !isBoolean(value);
|
|
1308
1536
|
if (key === "progress") return !isBoolean(value);
|
|
1309
1537
|
if (key === "tagVersionPrefix") return !isString(value);
|
|
1310
1538
|
if (key === "loglevel") return !isString(value) || !value.includes("${") && ![
|
|
@@ -1334,6 +1562,17 @@ function hasInvalidReleaseValue(key, value, target) {
|
|
|
1334
1562
|
return false;
|
|
1335
1563
|
}
|
|
1336
1564
|
/**
|
|
1565
|
+
* Check release-specific reserved aliases without broadening case sensitivity.
|
|
1566
|
+
*
|
|
1567
|
+
* @param prefix - Registry alias as declared
|
|
1568
|
+
* @param target - Confirmed target version
|
|
1569
|
+
*
|
|
1570
|
+
* @returns Whether the alias shadows a protocol introduced by these releases
|
|
1571
|
+
*/
|
|
1572
|
+
function isReservedRegistryPrefix(prefix, target) {
|
|
1573
|
+
return prefix.toLowerCase() === "pkg" && supportsMinimumVersion(target.version, PNPM_V12_5_MINIMUM_VERSION) || prefix === "tool" && supportsMinimumVersion(target.version, PNPM_V12_12_1_MINIMUM_VERSION);
|
|
1574
|
+
}
|
|
1575
|
+
/**
|
|
1337
1576
|
* Check value shapes that changed within a pnpm major release.
|
|
1338
1577
|
*
|
|
1339
1578
|
* Reject the whole setting so cleanup cannot discard unsupported nested values.
|
|
@@ -1345,7 +1584,7 @@ function hasInvalidReleaseValue(key, value, target) {
|
|
|
1345
1584
|
* @returns Whether the value uses a shape unsupported by the selected release
|
|
1346
1585
|
*/
|
|
1347
1586
|
function hasIncompatibleSettingValue(key, value, target) {
|
|
1348
|
-
if (hasInvalidPermissionSettings(key, value) || hasInvalidReleaseValue(key, value, target) || hasInvalidV11PatchValue(key, value, target) || hasIncompatibleV12SettingValue(key, value, target)) return true;
|
|
1587
|
+
if (hasInvalidPermissionSettings(key, value) || hasInvalidRemoteCacheValue(key, value) || hasInvalidReleaseValue(key, value, target) || hasInvalidV11PatchValue(key, value, target) || hasIncompatibleV12SettingValue(key, value, target)) return true;
|
|
1349
1588
|
if (hasInvalidScalarValue(key, value, target)) return true;
|
|
1350
1589
|
const supportsV12_5 = supportsMinimumVersion(target.version, PNPM_V12_5_MINIMUM_VERSION);
|
|
1351
1590
|
if (key === "supportedArchitectures" && isArray(value)) return !supportsV12_5;
|
|
@@ -1363,19 +1602,42 @@ function hasIncompatibleSettingValue(key, value, target) {
|
|
|
1363
1602
|
"constraints"
|
|
1364
1603
|
].some((field) => Object.hasOwn(value, field));
|
|
1365
1604
|
}
|
|
1366
|
-
if (key === "namedRegistries"
|
|
1605
|
+
if (key === "namedRegistries") return Object.keys(value).some((prefix) => isReservedRegistryPrefix(prefix, target));
|
|
1367
1606
|
if (key === "registries") return Object.values(value).some((entry) => {
|
|
1368
1607
|
if (!entry || typeof entry !== "object") return false;
|
|
1369
1608
|
if (Object.hasOwn(entry, "ecosystem") && !supportsV12_5) return true;
|
|
1370
1609
|
if (Object.hasOwn(entry, "packages") && !supportsMinimumVersion(target.version, PNPM_V12_5_1_MINIMUM_VERSION)) return true;
|
|
1371
1610
|
const prefix = Reflect.get(entry, "prefix");
|
|
1372
|
-
return
|
|
1611
|
+
return isString(prefix) && isReservedRegistryPrefix(prefix, target);
|
|
1373
1612
|
});
|
|
1374
1613
|
return false;
|
|
1375
1614
|
}
|
|
1376
1615
|
//#endregion
|
|
1377
1616
|
//#region src/features/settings/schema.ts
|
|
1378
1617
|
/**
|
|
1618
|
+
* Detect legacy runtime fields without a destination in pnpm v11 and newer.
|
|
1619
|
+
*
|
|
1620
|
+
* @param key - Canonical setting name
|
|
1621
|
+
* @param value - Untrusted legacy runtime declaration
|
|
1622
|
+
* @param compatibility - Target major controlling runtime conversion
|
|
1623
|
+
*
|
|
1624
|
+
* @returns Whether converting the runtime would discard sibling fields
|
|
1625
|
+
*/
|
|
1626
|
+
function hasUnmigratableRuntimeFields(key, value, compatibility) {
|
|
1627
|
+
return Boolean(key === "executionEnv" && compatibility !== "v10" && value && typeof value === "object" && Object.keys(value).some((field) => field !== "nodeVersion"));
|
|
1628
|
+
}
|
|
1629
|
+
/**
|
|
1630
|
+
* Check task declarations against the fields consumed by the target release.
|
|
1631
|
+
*
|
|
1632
|
+
* @param value - Untrusted task declarations
|
|
1633
|
+
* @param taskSettings - Supported task field names
|
|
1634
|
+
*
|
|
1635
|
+
* @returns Whether any declared task field is unsupported
|
|
1636
|
+
*/
|
|
1637
|
+
function hasUnsupportedTaskFields(value, taskSettings) {
|
|
1638
|
+
return Boolean(value && typeof value === "object" && Object.values(value).some((task) => task && typeof task === "object" && Object.keys(task).some((field) => !taskSettings.has(field))));
|
|
1639
|
+
}
|
|
1640
|
+
/**
|
|
1379
1641
|
* Create an empty collection of schema selection issues.
|
|
1380
1642
|
*
|
|
1381
1643
|
* @returns Empty issue lists for every supported issue category
|
|
@@ -1480,8 +1742,8 @@ function resolveSettingIssue({ allowedFields, target, key, npmrc, value }) {
|
|
|
1480
1742
|
if (compatibility !== "v10" && PROJECT_REFUSED_SETTINGS.has(key)) return "refused";
|
|
1481
1743
|
if (REGISTRY_SETTINGS.has(key) && containsUnsafeRegistryValue(value)) return "unsafe";
|
|
1482
1744
|
if (PROXY_SETTINGS.has(key) && isString(value) && value.includes("${")) return "unsafe";
|
|
1483
|
-
if (hasIncompatibleSettingValue(key, value, target)) return "incompatible";
|
|
1484
|
-
if (key === "tasks" &&
|
|
1745
|
+
if (hasInvalidRegistryLocations(key, value) || hasUnmigratableRuntimeFields(key, value, compatibility) || hasIncompatibleSettingValue(key, value, target)) return "incompatible";
|
|
1746
|
+
if (key === "tasks" && hasUnsupportedTaskFields(value, taskSettings)) return "incompatible";
|
|
1485
1747
|
if (!workspaceSettings.has(key)) return isSettingFromAnotherVersion(key, compatibility) ? "incompatible" : "unknown";
|
|
1486
1748
|
if (allowedFields && !allowedFields.has(key)) return "unsupported";
|
|
1487
1749
|
}
|
|
@@ -1890,8 +2152,17 @@ function getNpmrcLineKey(line) {
|
|
|
1890
2152
|
const trimmed = line.trim();
|
|
1891
2153
|
if (!trimmed || trimmed.startsWith(";") || trimmed.startsWith("#")) return;
|
|
1892
2154
|
const index = trimmed.indexOf("=");
|
|
1893
|
-
if (index
|
|
1894
|
-
|
|
2155
|
+
if (index === 0) return;
|
|
2156
|
+
let key = (index < 0 ? trimmed : trimmed.slice(0, index)).trim();
|
|
2157
|
+
if (key.startsWith("\"") && key.endsWith("\"") || key.startsWith("'") && key.endsWith("'")) {
|
|
2158
|
+
if (key.startsWith("'")) key = key.slice(1, -1);
|
|
2159
|
+
try {
|
|
2160
|
+
const parsed = JSON.parse(key);
|
|
2161
|
+
if (!isString(parsed)) return;
|
|
2162
|
+
key = parsed;
|
|
2163
|
+
} catch {}
|
|
2164
|
+
} else key = key.replace(/\\(?<escaped>[\\;#])|[;#].*$/gu, (_, escaped) => escaped ?? "");
|
|
2165
|
+
return normalizeNpmrcKey(key);
|
|
1895
2166
|
}
|
|
1896
2167
|
/**
|
|
1897
2168
|
* Check whether a `.npmrc` key is auth/registry-related and should stay in
|
|
@@ -1918,7 +2189,7 @@ function isNpmrcAuthOrRegistryKey(key) {
|
|
|
1918
2189
|
* @param compatibility - Concrete pnpm compatibility target
|
|
1919
2190
|
* @param migratedKeys - Original `.npmrc` keys that were migrated
|
|
1920
2191
|
*
|
|
1921
|
-
* @returns
|
|
2192
|
+
* @returns Whether the file was changed or removed
|
|
1922
2193
|
*
|
|
1923
2194
|
* @throws {Error} When file read/write operations fail
|
|
1924
2195
|
*
|
|
@@ -1931,18 +2202,25 @@ async function pruneNpmrc(path, compatibility, migratedKeys) {
|
|
|
1931
2202
|
const migratedKeySet = new Set(migratedKeys.map(normalizeNpmrcKey));
|
|
1932
2203
|
const content = await fsReadFile(path);
|
|
1933
2204
|
let inSection = false;
|
|
1934
|
-
|
|
2205
|
+
let removed = false;
|
|
2206
|
+
const lines = content.split(/\r\n|[\r\n]/u).filter((line) => {
|
|
1935
2207
|
if (/^\[[^\]]*\]\s*$/u.test(line.replace(/^\uFEFF/u, ""))) inSection = true;
|
|
1936
2208
|
if (inSection) return true;
|
|
1937
2209
|
const key = getNpmrcLineKey(line);
|
|
1938
2210
|
if (!key) return true;
|
|
1939
|
-
|
|
1940
|
-
|
|
2211
|
+
if (migratedKeySet.has(key)) {
|
|
2212
|
+
removed = true;
|
|
2213
|
+
return false;
|
|
2214
|
+
}
|
|
2215
|
+
return true;
|
|
2216
|
+
});
|
|
2217
|
+
if (!removed) return false;
|
|
2218
|
+
const updatedContent = lines.join("\n").trimEnd();
|
|
1941
2219
|
if (compatibility !== "v10" && !updatedContent.trim()) {
|
|
1942
2220
|
await fsRemoveFile(path);
|
|
1943
|
-
return;
|
|
2221
|
+
return true;
|
|
1944
2222
|
}
|
|
1945
|
-
|
|
2223
|
+
return fsWriteFileIfChanged(path, updatedContent);
|
|
1946
2224
|
}
|
|
1947
2225
|
/**
|
|
1948
2226
|
* Read `.npmrc` and return settings that should be migrated into workspace config.
|
|
@@ -2481,10 +2759,10 @@ async function persistMigration(options) {
|
|
|
2481
2759
|
if (!cleanNpmrc) return result;
|
|
2482
2760
|
const pruneTasks = projectNpmrcs.projects.flatMap((project) => {
|
|
2483
2761
|
const appliedKeys = selectAppliedProjectKeys(project, finalSettings);
|
|
2484
|
-
return appliedKeys.length ? [pruneNpmrc(project.npmrcPath, compatibility, appliedKeys).then(() => project.npmrcPath)] : [];
|
|
2762
|
+
return appliedKeys.length ? [pruneNpmrc(project.npmrcPath, compatibility, appliedKeys).then((changed) => changed ? project.npmrcPath : void 0)] : [];
|
|
2485
2763
|
});
|
|
2486
|
-
if (npmrcExists && appliedNpmrcKeys.length) pruneTasks.push(pruneNpmrc(npmrcPath, compatibility, appliedNpmrcKeys).then(() => npmrcPath));
|
|
2487
|
-
const cleanedNpmrcPaths = await Promise.all(pruneTasks);
|
|
2764
|
+
if (npmrcExists && appliedNpmrcKeys.length) pruneTasks.push(pruneNpmrc(npmrcPath, compatibility, appliedNpmrcKeys).then((changed) => changed ? npmrcPath : void 0));
|
|
2765
|
+
const cleanedNpmrcPaths = (await Promise.all(pruneTasks)).filter(isString);
|
|
2488
2766
|
result.changedFiles.push(...cleanedNpmrcPaths);
|
|
2489
2767
|
result.sourceSettingsCleaned ||= cleanedNpmrcPaths.length > 0;
|
|
2490
2768
|
return result;
|
|
@@ -2580,9 +2858,9 @@ function normalizeSideEffectsCache(settings) {
|
|
|
2580
2858
|
* @returns Nothing; the URL's declaration is created or updated in place
|
|
2581
2859
|
*/
|
|
2582
2860
|
function addRegistryScope(declarations, url, scope) {
|
|
2583
|
-
declarations
|
|
2584
|
-
const declaration = declarations[url];
|
|
2861
|
+
const declaration = declarations.get(url) ?? {};
|
|
2585
2862
|
declaration.scopes = unique([...declaration.scopes ?? [], scope]);
|
|
2863
|
+
declarations.set(url, declaration);
|
|
2586
2864
|
}
|
|
2587
2865
|
/**
|
|
2588
2866
|
* Find source URLs with multiple active aliases across the merged settings.
|
|
@@ -2611,9 +2889,9 @@ function findRegistryAliasConflicts(prefixesByUrl, namedRegistries, canonicalPre
|
|
|
2611
2889
|
function normalizeNamedRegistries(settings, warnings, canonicalSettings) {
|
|
2612
2890
|
const { namedRegistries } = settings;
|
|
2613
2891
|
if (!namedRegistries) return;
|
|
2614
|
-
const declarations =
|
|
2892
|
+
const declarations = /* @__PURE__ */ new Map();
|
|
2615
2893
|
for (const [key, value] of Object.entries(settings.registries ?? {})) if (isString(value)) addRegistryScope(declarations, value, key === "default" ? "@" : key);
|
|
2616
|
-
else declarations
|
|
2894
|
+
else declarations.set(key, { ...value });
|
|
2617
2895
|
const canonicalPrefixes = new Set(Object.values(canonicalSettings.registries ?? {}).flatMap((value) => !isString(value) && isString(value?.prefix) ? [value.prefix] : []));
|
|
2618
2896
|
const prefixesByUrl = /* @__PURE__ */ new Map();
|
|
2619
2897
|
const registryAliases = Object.entries(namedRegistries).filter(([prefix]) => !canonicalPrefixes.has(prefix));
|
|
@@ -2628,17 +2906,17 @@ function normalizeNamedRegistries(settings, warnings, canonicalSettings) {
|
|
|
2628
2906
|
return;
|
|
2629
2907
|
}
|
|
2630
2908
|
for (const [url, [prefix]] of prefixesByUrl) {
|
|
2631
|
-
declarations
|
|
2632
|
-
const
|
|
2633
|
-
const canonicalDeclaration = canonicalSettings.registries?.[url];
|
|
2909
|
+
const declaration = declarations.get(url) ?? {};
|
|
2910
|
+
const canonicalDeclaration = canonicalSettings.registries && Object.hasOwn(canonicalSettings.registries, url) ? canonicalSettings.registries[url] : void 0;
|
|
2634
2911
|
const declaredPrefix = (isString(canonicalDeclaration) ? void 0 : canonicalDeclaration?.prefix) ?? declaration.prefix;
|
|
2635
2912
|
if (isString(declaredPrefix) && declaredPrefix !== prefix) {
|
|
2636
2913
|
warnings.push(`namedRegistries was kept because ${url} already declares prefix ${declaredPrefix}.`);
|
|
2637
2914
|
return;
|
|
2638
2915
|
}
|
|
2639
2916
|
declaration.prefix = prefix;
|
|
2917
|
+
declarations.set(url, declaration);
|
|
2640
2918
|
}
|
|
2641
|
-
settings.registries = declarations;
|
|
2919
|
+
settings.registries = Object.fromEntries(declarations);
|
|
2642
2920
|
Reflect.deleteProperty(settings, "namedRegistries");
|
|
2643
2921
|
}
|
|
2644
2922
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pnpm-settings-migrator",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.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",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@ntnyq/utils": "^0.23.0",
|
|
46
46
|
"@pnpm/types": "^1102.1.1",
|
|
47
|
-
"cac": "^7.
|
|
47
|
+
"cac": "^7.1.0",
|
|
48
48
|
"camelcase-keys": "^10.0.3",
|
|
49
49
|
"consola": "^3.4.2",
|
|
50
50
|
"defu": "^6.1.7",
|
|
@@ -56,17 +56,17 @@
|
|
|
56
56
|
"yaml": "^2.9.1"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
|
-
"@types/node": "^26.6.
|
|
59
|
+
"@types/node": "^26.6.5",
|
|
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.
|
|
66
|
-
"tsdown": "^0.23.
|
|
64
|
+
"oxfmt": "^0.73.0",
|
|
65
|
+
"oxlint": "^1.88.0",
|
|
66
|
+
"tsdown": "^0.23.1",
|
|
67
67
|
"tsx": "^4.23.15",
|
|
68
68
|
"typescript": "^7.0.2",
|
|
69
|
-
"vitest": "^5.0.
|
|
69
|
+
"vitest": "^5.0.4"
|
|
70
70
|
},
|
|
71
71
|
"nano-staged": {
|
|
72
72
|
"*.{js,ts,mjs,tsx}": "oxlint --fix",
|