pnpm-settings-migrator 0.16.0 → 0.17.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 +41 -6
- package/dist/cli.mjs +214 -28
- package/dist/index.d.mts +20 -1
- package/dist/index.mjs +213 -27
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -232,11 +232,37 @@ For example, targeting 12.11.0 migrates
|
|
|
232
232
|
to equivalent workspace YAML. Targeting 12.10.1 retains these settings with
|
|
233
233
|
diagnostics.
|
|
234
234
|
|
|
235
|
+
For pnpm 12.12.0 and later stable v12 releases, `addMissingPeerTypes` accepts
|
|
236
|
+
boolean or null values and `remoteCache` accepts the project organization:
|
|
237
|
+
`{ org: 'acme' }`. Empty and null remote-cache sections are preserved. Server,
|
|
238
|
+
team, signing, and publication fields belong in trusted global configuration
|
|
239
|
+
or environment variables. Objects containing those fields, unknown fields,
|
|
240
|
+
or invalid values remain intact in their source with diagnostics; existing
|
|
241
|
+
incompatible workspace values block migration before writes.
|
|
242
|
+
|
|
243
|
+
`remoteCache` and legacy `sideEffectsCache.remote` are kept independently so
|
|
244
|
+
pnpm applies its field-level precedence. The migrator does not insert defaults
|
|
245
|
+
or rewrite `allowBuilds`: pnpm 12.12 itself now honors explicit build denials
|
|
246
|
+
when `dangerouslyAllowAllBuilds` is true.
|
|
247
|
+
|
|
248
|
+
From 12.12.1, the registry prefix `tool` is reserved. Both `namedRegistries`
|
|
249
|
+
and URL-keyed `registries` using that exact prefix remain in their sources;
|
|
250
|
+
existing workspace declarations block migration. This restriction is
|
|
251
|
+
case-sensitive (`ToOl` remains valid), unlike the existing `pkg` restriction.
|
|
252
|
+
|
|
235
253
|
pnpm v12 reads `filter` and `filterProd` from CLI arguments or `updateConfig`
|
|
236
254
|
hooks, not workspace YAML. These settings remain in their source with
|
|
237
255
|
diagnostics for v12 targets; existing YAML containing them blocks migration.
|
|
238
256
|
The 12.11.2 hook fix does not add workspace support.
|
|
239
257
|
|
|
258
|
+
Other legacy fields ignored by the v12 workspace reader also stay in their
|
|
259
|
+
sources with diagnostics. These include `production`, `dev`, `recursive`,
|
|
260
|
+
`lockfileOnly`, `force`, `depth`, `saveDev`, `fetchingConcurrency`,
|
|
261
|
+
`requiredScripts`, `extraBinPaths`, `extraEnv`, and `workspacePackagePatterns`.
|
|
262
|
+
Existing YAML containing ignored fields blocks migration before writes.
|
|
263
|
+
For example, `production=true` cannot be moved into v12 YAML; use
|
|
264
|
+
`pnpm install --prod` to select production dependencies.
|
|
265
|
+
|
|
240
266
|
</details>
|
|
241
267
|
|
|
242
268
|
<details>
|
|
@@ -273,6 +299,9 @@ Notes:
|
|
|
273
299
|
such as `globalDir`, `stateDir`, `configDir`, and `scope` stay in `.npmrc`.
|
|
274
300
|
- Registry declarations containing embedded credentials or dynamic `${...}`
|
|
275
301
|
URLs stay in their source with a warning.
|
|
302
|
+
- Invalid registry locations, including relative paths and object property names
|
|
303
|
+
such as `__proto__`, remain in their sources with diagnostics. Registry
|
|
304
|
+
conversion never uses inherited object properties as declarations.
|
|
276
305
|
- Dynamic `${...}` values in `httpProxy`, `httpsProxy`, `noProxy`, `proxy`, and
|
|
277
306
|
`noproxy` stay in their source for every target. Project YAML does not expand
|
|
278
307
|
these placeholders; configure them globally in trusted configuration or use
|
|
@@ -297,10 +326,16 @@ Notes:
|
|
|
297
326
|
`package.json#pnpm` child keys remain in `package.json`.
|
|
298
327
|
INI sections in `.npmrc` are retained conservatively; their child names are
|
|
299
328
|
never treated as root keys during cleanup.
|
|
329
|
+
Bare boolean flags such as `save-exact` and quoted keys such as
|
|
330
|
+
`"save-exact"=true` are cleaned when migrated. Retained sections alone do not
|
|
331
|
+
cause repeated cleanup reports or rewrites.
|
|
300
332
|
- Runtime migration writes `devEngines.runtime` before removing a workspace
|
|
301
333
|
runtime declaration. A failed write preserves source settings for retry.
|
|
302
334
|
If an existing runtime declaration conflicts with a workspace runtime, migration
|
|
303
335
|
stops before writing any files. An identical Node.js declaration is reused.
|
|
336
|
+
- An `executionEnv` object containing fields besides `nodeVersion` remains
|
|
337
|
+
intact in its source with a warning. Existing workspace declarations with
|
|
338
|
+
such fields block migration before writes, so unmigrated values cannot be lost.
|
|
304
339
|
- YAML aliases remain when their anchors are unchanged and precede them.
|
|
305
340
|
If replacement, removal, or sorting would invalidate an alias, its original
|
|
306
341
|
value is written explicitly so unrelated settings keep their values.
|
|
@@ -311,17 +346,17 @@ Notes:
|
|
|
311
346
|
- The migrator does not update the `packageManager` version, CI environment variables,
|
|
312
347
|
shell setup, or pnpm commands in scripts. When `packageManager` still pins pnpm 10,
|
|
313
348
|
pass `--compatibility v11` explicitly and update the pin separately.
|
|
314
|
-
- Compatibility checks cover pnpm 11.28.5 and 12.
|
|
349
|
+
- Compatibility checks cover pnpm 11.28.5 and 12.12.1, including 11.28.0,
|
|
315
350
|
11.28.1, 11.28.2, 11.28.3, 11.28.4, 12.7.0, 12.8.0, 12.8.1, 12.8.2,
|
|
316
|
-
12.9.0, 12.9.1, 12.10.0, 12.10.1, 12.11.0,
|
|
351
|
+
12.9.0, 12.9.1, 12.10.0, 12.10.1, 12.11.0, 12.11.1, 12.11.2, and 12.12.0.
|
|
317
352
|
Earlier regressions remain at 11.25.0,
|
|
318
353
|
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` is 12.
|
|
354
|
+
As [audited on 2026-10-13](docs/research/pnpm-settings-bump-2026-10-13.md),
|
|
355
|
+
npm `latest` is 12.11.2; the highest confirmed stable release is 12.12.1 on
|
|
321
356
|
`next-12`. The v11 target is 11.28.5 on `next-11`, while `latest-11` is
|
|
322
|
-
11.28.2. The project is pinned to 12.
|
|
357
|
+
11.28.2. The project is pinned to 12.11.2. These checks verify the documented
|
|
323
358
|
fixtures, not every setting in every release. v10 has unit regressions and
|
|
324
|
-
source review through 10.34.
|
|
359
|
+
source review through 10.34.7; real v10 consumption remains unverified.
|
|
325
360
|
The removed `pnpm install --resolution-only` CLI flag is outside this settings
|
|
326
361
|
migrator's scope; replace it with `pnpm peers check` in scripts before upgrading.
|
|
327
362
|
|
package/dist/cli.mjs
CHANGED
|
@@ -627,6 +627,22 @@ const PNPM_V12_11_MINIMUM_VERSION = [
|
|
|
627
627
|
0
|
|
628
628
|
];
|
|
629
629
|
/**
|
|
630
|
+
* Release introducing remote cache configuration and missing peer types.
|
|
631
|
+
*/
|
|
632
|
+
const PNPM_V12_12_MINIMUM_VERSION = [
|
|
633
|
+
12,
|
|
634
|
+
12,
|
|
635
|
+
0
|
|
636
|
+
];
|
|
637
|
+
/**
|
|
638
|
+
* Release reserving the tool registry prefix.
|
|
639
|
+
*/
|
|
640
|
+
const PNPM_V12_12_1_MINIMUM_VERSION = [
|
|
641
|
+
12,
|
|
642
|
+
12,
|
|
643
|
+
1
|
|
644
|
+
];
|
|
645
|
+
/**
|
|
630
646
|
* Workspace settings first consumed by pnpm v12 in 12.11.0.
|
|
631
647
|
*/
|
|
632
648
|
const PNPM_V12_11_WORKSPACE_SETTINGS = [
|
|
@@ -652,6 +668,11 @@ const PNPM_V12_7_WORKSPACE_SETTINGS = [
|
|
|
652
668
|
* @see https://github.com/pnpm/pnpm/blob/v12.4.0/pnpm/crates/config/src/workspace_yaml.rs
|
|
653
669
|
*/
|
|
654
670
|
const PNPM_SETTINGS_CAPABILITIES = [
|
|
671
|
+
{
|
|
672
|
+
minimumVersion: PNPM_V12_12_MINIMUM_VERSION,
|
|
673
|
+
workspaceFields: ["addMissingPeerTypes", "remoteCache"],
|
|
674
|
+
taskFields: []
|
|
675
|
+
},
|
|
655
676
|
{
|
|
656
677
|
minimumVersion: PNPM_V12_11_MINIMUM_VERSION,
|
|
657
678
|
workspaceFields: PNPM_V12_11_WORKSPACE_SETTINGS,
|
|
@@ -733,9 +754,82 @@ const PNPM_V12_ONLY_WORKSPACE_SETTINGS = [
|
|
|
733
754
|
"globalShims"
|
|
734
755
|
];
|
|
735
756
|
/**
|
|
757
|
+
* Legacy fields absent from pnpm v12's workspace reader through 12.12.1.
|
|
758
|
+
* Removed aliases with explicit migration replacements are kept separately.
|
|
759
|
+
*
|
|
760
|
+
* @see https://github.com/pnpm/pnpm/blob/v12.12.1/pnpm/crates/config/src/workspace_yaml/settings.rs
|
|
761
|
+
*/
|
|
762
|
+
const PNPM_V12_IGNORED_WORKSPACE_SETTINGS = [
|
|
763
|
+
"access",
|
|
764
|
+
"allowNew",
|
|
765
|
+
"allowSameVersion",
|
|
766
|
+
"auditIgnorePrune",
|
|
767
|
+
"autoConfirmAllPrompts",
|
|
768
|
+
"binLinks",
|
|
769
|
+
"commitHooks",
|
|
770
|
+
"cpu",
|
|
771
|
+
"depth",
|
|
772
|
+
"description",
|
|
773
|
+
"dev",
|
|
774
|
+
"dryRun",
|
|
775
|
+
"enablePnp",
|
|
776
|
+
"extraBinPaths",
|
|
777
|
+
"extraEnv",
|
|
778
|
+
"fetchingConcurrency",
|
|
779
|
+
"force",
|
|
780
|
+
"git",
|
|
781
|
+
"gitTagVersion",
|
|
782
|
+
"global",
|
|
783
|
+
"globalPath",
|
|
784
|
+
"globalPrefix",
|
|
785
|
+
"ignoreCurrentSpecifiers",
|
|
786
|
+
"json",
|
|
787
|
+
"libc",
|
|
788
|
+
"localAddress",
|
|
789
|
+
"lockfileOnly",
|
|
790
|
+
"long",
|
|
791
|
+
"message",
|
|
792
|
+
"npmPath",
|
|
793
|
+
"only",
|
|
794
|
+
"os",
|
|
795
|
+
"packDestination",
|
|
796
|
+
"packGzipLevel",
|
|
797
|
+
"parseable",
|
|
798
|
+
"pnpmExecPath",
|
|
799
|
+
"preserveAbsolutePaths",
|
|
800
|
+
"production",
|
|
801
|
+
"publishBranch",
|
|
802
|
+
"recursive",
|
|
803
|
+
"registriesByPrefix",
|
|
804
|
+
"registriesByScope",
|
|
805
|
+
"registryOptionsByUrl",
|
|
806
|
+
"requiredScripts",
|
|
807
|
+
"runtime",
|
|
808
|
+
"save",
|
|
809
|
+
"saveDev",
|
|
810
|
+
"saveOptional",
|
|
811
|
+
"saveProd",
|
|
812
|
+
"sideEffectsCacheRead",
|
|
813
|
+
"sideEffectsCacheWrite",
|
|
814
|
+
"signGitTag",
|
|
815
|
+
"strictSsl",
|
|
816
|
+
"tag",
|
|
817
|
+
"tryLoadDefaultPnpmfile",
|
|
818
|
+
"umask",
|
|
819
|
+
"useGitBranchLockfile",
|
|
820
|
+
"useLockfile",
|
|
821
|
+
"useRunningStoreServer",
|
|
822
|
+
"version",
|
|
823
|
+
"workspacePackagePatterns",
|
|
824
|
+
"workspacePackages",
|
|
825
|
+
"workspacePrefix",
|
|
826
|
+
"workspaceRoot",
|
|
827
|
+
"yes"
|
|
828
|
+
];
|
|
829
|
+
/**
|
|
736
830
|
* Lookup used to remove v11-only fields from the v12 workspace allowlist.
|
|
737
831
|
*/
|
|
738
|
-
const v11OnlyWorkspaceSettings = new Set(PNPM_V11_ONLY_WORKSPACE_SETTINGS);
|
|
832
|
+
const v11OnlyWorkspaceSettings = /* @__PURE__ */ new Set([...PNPM_V11_ONLY_WORKSPACE_SETTINGS, ...PNPM_V12_IGNORED_WORKSPACE_SETTINGS]);
|
|
739
833
|
/**
|
|
740
834
|
* Fields accepted by registry declarations with pnpm 12.9 concurrency limits.
|
|
741
835
|
*/
|
|
@@ -770,7 +864,7 @@ const PROJECT_REFUSED_SETTINGS = new Set(PNPM_PROJECT_REFUSED_SETTINGS);
|
|
|
770
864
|
/**
|
|
771
865
|
* Fields reported as incompatible when selecting settings for v12.
|
|
772
866
|
*/
|
|
773
|
-
const PNPM_V11_ONLY_SETTINGS = new Set(PNPM_V11_ONLY_WORKSPACE_SETTINGS);
|
|
867
|
+
const PNPM_V11_ONLY_SETTINGS = /* @__PURE__ */ new Set([...PNPM_V11_ONLY_WORKSPACE_SETTINGS, ...PNPM_V12_IGNORED_WORKSPACE_SETTINGS]);
|
|
774
868
|
/**
|
|
775
869
|
* Allowed workspace fields for the v11 target.
|
|
776
870
|
*/
|
|
@@ -1119,6 +1213,32 @@ function formatSettingsIssues(options) {
|
|
|
1119
1213
|
return warnings;
|
|
1120
1214
|
}
|
|
1121
1215
|
//#endregion
|
|
1216
|
+
//#region src/features/settings/registries.ts
|
|
1217
|
+
/**
|
|
1218
|
+
* Check that a registry location is a URL rather than an object property name.
|
|
1219
|
+
*
|
|
1220
|
+
* @param value - Untrusted registry location
|
|
1221
|
+
*
|
|
1222
|
+
* @returns Whether the location is an absolute or scheme-relative URL
|
|
1223
|
+
*/
|
|
1224
|
+
function isRegistryUrl(value) {
|
|
1225
|
+
return isString(value) && URL.canParse(value.startsWith("//") ? `https:${value}` : value);
|
|
1226
|
+
}
|
|
1227
|
+
/**
|
|
1228
|
+
* Validate locations in legacy registry maps and URL-keyed declarations.
|
|
1229
|
+
*
|
|
1230
|
+
* @param key - Canonical workspace setting name
|
|
1231
|
+
* @param value - Untrusted registry configuration
|
|
1232
|
+
*
|
|
1233
|
+
* @returns Whether the complete setting must be retained in its source
|
|
1234
|
+
*/
|
|
1235
|
+
function hasInvalidRegistryLocations(key, value) {
|
|
1236
|
+
if (!["namedRegistries", "registries"].includes(key) || value === null) return false;
|
|
1237
|
+
if (!isPlainObject(value)) return true;
|
|
1238
|
+
if (key === "namedRegistries") return Object.values(value).some((url) => !isRegistryUrl(url));
|
|
1239
|
+
return Object.entries(value).some(([url, entry]) => isString(entry) ? !isRegistryUrl(entry) : !isRegistryUrl(url));
|
|
1240
|
+
}
|
|
1241
|
+
//#endregion
|
|
1122
1242
|
//#region src/features/settings/permissions.ts
|
|
1123
1243
|
/**
|
|
1124
1244
|
* Validate a dependency's capability map without dropping unknown permissions.
|
|
@@ -1263,6 +1383,22 @@ function hasIncompatibleV12SettingValue(key, value, target) {
|
|
|
1263
1383
|
return false;
|
|
1264
1384
|
}
|
|
1265
1385
|
//#endregion
|
|
1386
|
+
//#region src/features/settings/remote-cache.ts
|
|
1387
|
+
/**
|
|
1388
|
+
* Accept only the project-owned remote cache organization.
|
|
1389
|
+
* Keep machine configuration and unknown fields intact in their source.
|
|
1390
|
+
*
|
|
1391
|
+
* @param key - Canonical workspace setting name
|
|
1392
|
+
* @param value - Untrusted incoming or existing workspace value
|
|
1393
|
+
*
|
|
1394
|
+
* @returns Whether the remote cache setting cannot migrate to project YAML
|
|
1395
|
+
*/
|
|
1396
|
+
function hasInvalidRemoteCacheValue(key, value) {
|
|
1397
|
+
if (key !== "remoteCache" || value === null) return false;
|
|
1398
|
+
if (!value || typeof value !== "object" || isArray(value)) return true;
|
|
1399
|
+
return Object.entries(value).some(([field, entry]) => field !== "org" || entry !== null && !isString(entry));
|
|
1400
|
+
}
|
|
1401
|
+
//#endregion
|
|
1266
1402
|
//#region src/features/settings/versioned-values.ts
|
|
1267
1403
|
/**
|
|
1268
1404
|
* Bounds of the signed 32-bit integer used by pnpm's task priority.
|
|
@@ -1311,7 +1447,7 @@ function hasInvalidScalarValue(key, value, target) {
|
|
|
1311
1447
|
"ndjson",
|
|
1312
1448
|
"silent"
|
|
1313
1449
|
].includes(value);
|
|
1314
|
-
if (key === "provenance") return value !== null && !isBoolean(value);
|
|
1450
|
+
if (key === "provenance" || key === "addMissingPeerTypes") return value !== null && !isBoolean(value);
|
|
1315
1451
|
if (key === "progress") return !isBoolean(value);
|
|
1316
1452
|
if (key === "tagVersionPrefix") return !isString(value);
|
|
1317
1453
|
if (key === "loglevel") return !isString(value) || !value.includes("${") && ![
|
|
@@ -1341,6 +1477,17 @@ function hasInvalidReleaseValue(key, value, target) {
|
|
|
1341
1477
|
return false;
|
|
1342
1478
|
}
|
|
1343
1479
|
/**
|
|
1480
|
+
* Check release-specific reserved aliases without broadening case sensitivity.
|
|
1481
|
+
*
|
|
1482
|
+
* @param prefix - Registry alias as declared
|
|
1483
|
+
* @param target - Confirmed target version
|
|
1484
|
+
*
|
|
1485
|
+
* @returns Whether the alias shadows a protocol introduced by these releases
|
|
1486
|
+
*/
|
|
1487
|
+
function isReservedRegistryPrefix(prefix, target) {
|
|
1488
|
+
return prefix.toLowerCase() === "pkg" && supportsMinimumVersion(target.version, PNPM_V12_5_MINIMUM_VERSION) || prefix === "tool" && supportsMinimumVersion(target.version, PNPM_V12_12_1_MINIMUM_VERSION);
|
|
1489
|
+
}
|
|
1490
|
+
/**
|
|
1344
1491
|
* Check value shapes that changed within a pnpm major release.
|
|
1345
1492
|
*
|
|
1346
1493
|
* Reject the whole setting so cleanup cannot discard unsupported nested values.
|
|
@@ -1352,7 +1499,7 @@ function hasInvalidReleaseValue(key, value, target) {
|
|
|
1352
1499
|
* @returns Whether the value uses a shape unsupported by the selected release
|
|
1353
1500
|
*/
|
|
1354
1501
|
function hasIncompatibleSettingValue(key, value, target) {
|
|
1355
|
-
if (hasInvalidPermissionSettings(key, value) || hasInvalidReleaseValue(key, value, target) || hasInvalidV11PatchValue(key, value, target) || hasIncompatibleV12SettingValue(key, value, target)) return true;
|
|
1502
|
+
if (hasInvalidPermissionSettings(key, value) || hasInvalidRemoteCacheValue(key, value) || hasInvalidReleaseValue(key, value, target) || hasInvalidV11PatchValue(key, value, target) || hasIncompatibleV12SettingValue(key, value, target)) return true;
|
|
1356
1503
|
if (hasInvalidScalarValue(key, value, target)) return true;
|
|
1357
1504
|
const supportsV12_5 = supportsMinimumVersion(target.version, PNPM_V12_5_MINIMUM_VERSION);
|
|
1358
1505
|
if (key === "supportedArchitectures" && isArray(value)) return !supportsV12_5;
|
|
@@ -1370,19 +1517,42 @@ function hasIncompatibleSettingValue(key, value, target) {
|
|
|
1370
1517
|
"constraints"
|
|
1371
1518
|
].some((field) => Object.hasOwn(value, field));
|
|
1372
1519
|
}
|
|
1373
|
-
if (key === "namedRegistries"
|
|
1520
|
+
if (key === "namedRegistries") return Object.keys(value).some((prefix) => isReservedRegistryPrefix(prefix, target));
|
|
1374
1521
|
if (key === "registries") return Object.values(value).some((entry) => {
|
|
1375
1522
|
if (!entry || typeof entry !== "object") return false;
|
|
1376
1523
|
if (Object.hasOwn(entry, "ecosystem") && !supportsV12_5) return true;
|
|
1377
1524
|
if (Object.hasOwn(entry, "packages") && !supportsMinimumVersion(target.version, PNPM_V12_5_1_MINIMUM_VERSION)) return true;
|
|
1378
1525
|
const prefix = Reflect.get(entry, "prefix");
|
|
1379
|
-
return
|
|
1526
|
+
return isString(prefix) && isReservedRegistryPrefix(prefix, target);
|
|
1380
1527
|
});
|
|
1381
1528
|
return false;
|
|
1382
1529
|
}
|
|
1383
1530
|
//#endregion
|
|
1384
1531
|
//#region src/features/settings/schema.ts
|
|
1385
1532
|
/**
|
|
1533
|
+
* Detect legacy runtime fields without a destination in pnpm v11 and newer.
|
|
1534
|
+
*
|
|
1535
|
+
* @param key - Canonical setting name
|
|
1536
|
+
* @param value - Untrusted legacy runtime declaration
|
|
1537
|
+
* @param compatibility - Target major controlling runtime conversion
|
|
1538
|
+
*
|
|
1539
|
+
* @returns Whether converting the runtime would discard sibling fields
|
|
1540
|
+
*/
|
|
1541
|
+
function hasUnmigratableRuntimeFields(key, value, compatibility) {
|
|
1542
|
+
return Boolean(key === "executionEnv" && compatibility !== "v10" && value && typeof value === "object" && Object.keys(value).some((field) => field !== "nodeVersion"));
|
|
1543
|
+
}
|
|
1544
|
+
/**
|
|
1545
|
+
* Check task declarations against the fields consumed by the target release.
|
|
1546
|
+
*
|
|
1547
|
+
* @param value - Untrusted task declarations
|
|
1548
|
+
* @param taskSettings - Supported task field names
|
|
1549
|
+
*
|
|
1550
|
+
* @returns Whether any declared task field is unsupported
|
|
1551
|
+
*/
|
|
1552
|
+
function hasUnsupportedTaskFields(value, taskSettings) {
|
|
1553
|
+
return Boolean(value && typeof value === "object" && Object.values(value).some((task) => task && typeof task === "object" && Object.keys(task).some((field) => !taskSettings.has(field))));
|
|
1554
|
+
}
|
|
1555
|
+
/**
|
|
1386
1556
|
* Create an empty collection of schema selection issues.
|
|
1387
1557
|
*
|
|
1388
1558
|
* @returns Empty issue lists for every supported issue category
|
|
@@ -1487,8 +1657,8 @@ function resolveSettingIssue({ allowedFields, target, key, npmrc, value }) {
|
|
|
1487
1657
|
if (compatibility !== "v10" && PROJECT_REFUSED_SETTINGS.has(key)) return "refused";
|
|
1488
1658
|
if (REGISTRY_SETTINGS.has(key) && containsUnsafeRegistryValue(value)) return "unsafe";
|
|
1489
1659
|
if (PROXY_SETTINGS.has(key) && isString(value) && value.includes("${")) return "unsafe";
|
|
1490
|
-
if (hasIncompatibleSettingValue(key, value, target)) return "incompatible";
|
|
1491
|
-
if (key === "tasks" &&
|
|
1660
|
+
if (hasInvalidRegistryLocations(key, value) || hasUnmigratableRuntimeFields(key, value, compatibility) || hasIncompatibleSettingValue(key, value, target)) return "incompatible";
|
|
1661
|
+
if (key === "tasks" && hasUnsupportedTaskFields(value, taskSettings)) return "incompatible";
|
|
1492
1662
|
if (!workspaceSettings.has(key)) return isSettingFromAnotherVersion(key, compatibility) ? "incompatible" : "unknown";
|
|
1493
1663
|
if (allowedFields && !allowedFields.has(key)) return "unsupported";
|
|
1494
1664
|
}
|
|
@@ -1897,8 +2067,17 @@ function getNpmrcLineKey(line) {
|
|
|
1897
2067
|
const trimmed = line.trim();
|
|
1898
2068
|
if (!trimmed || trimmed.startsWith(";") || trimmed.startsWith("#")) return;
|
|
1899
2069
|
const index = trimmed.indexOf("=");
|
|
1900
|
-
if (index
|
|
1901
|
-
|
|
2070
|
+
if (index === 0) return;
|
|
2071
|
+
let key = (index < 0 ? trimmed : trimmed.slice(0, index)).trim();
|
|
2072
|
+
if (key.startsWith("\"") && key.endsWith("\"") || key.startsWith("'") && key.endsWith("'")) {
|
|
2073
|
+
if (key.startsWith("'")) key = key.slice(1, -1);
|
|
2074
|
+
try {
|
|
2075
|
+
const parsed = JSON.parse(key);
|
|
2076
|
+
if (!isString(parsed)) return;
|
|
2077
|
+
key = parsed;
|
|
2078
|
+
} catch {}
|
|
2079
|
+
} else key = key.replace(/\\(?<escaped>[\\;#])|[;#].*$/gu, (_, escaped) => escaped ?? "");
|
|
2080
|
+
return normalizeNpmrcKey(key);
|
|
1902
2081
|
}
|
|
1903
2082
|
/**
|
|
1904
2083
|
* Check whether a `.npmrc` key is auth/registry-related and should stay in
|
|
@@ -1925,7 +2104,7 @@ function isNpmrcAuthOrRegistryKey(key) {
|
|
|
1925
2104
|
* @param compatibility - Concrete pnpm compatibility target
|
|
1926
2105
|
* @param migratedKeys - Original `.npmrc` keys that were migrated
|
|
1927
2106
|
*
|
|
1928
|
-
* @returns
|
|
2107
|
+
* @returns Whether the file was changed or removed
|
|
1929
2108
|
*
|
|
1930
2109
|
* @throws {Error} When file read/write operations fail
|
|
1931
2110
|
*
|
|
@@ -1938,18 +2117,25 @@ async function pruneNpmrc(path, compatibility, migratedKeys) {
|
|
|
1938
2117
|
const migratedKeySet = new Set(migratedKeys.map(normalizeNpmrcKey));
|
|
1939
2118
|
const content = await fsReadFile(path);
|
|
1940
2119
|
let inSection = false;
|
|
1941
|
-
|
|
2120
|
+
let removed = false;
|
|
2121
|
+
const lines = content.split(/\r\n|[\r\n]/u).filter((line) => {
|
|
1942
2122
|
if (/^\[[^\]]*\]\s*$/u.test(line.replace(/^\uFEFF/u, ""))) inSection = true;
|
|
1943
2123
|
if (inSection) return true;
|
|
1944
2124
|
const key = getNpmrcLineKey(line);
|
|
1945
2125
|
if (!key) return true;
|
|
1946
|
-
|
|
1947
|
-
|
|
2126
|
+
if (migratedKeySet.has(key)) {
|
|
2127
|
+
removed = true;
|
|
2128
|
+
return false;
|
|
2129
|
+
}
|
|
2130
|
+
return true;
|
|
2131
|
+
});
|
|
2132
|
+
if (!removed) return false;
|
|
2133
|
+
const updatedContent = lines.join("\n").trimEnd();
|
|
1948
2134
|
if (compatibility !== "v10" && !updatedContent.trim()) {
|
|
1949
2135
|
await fsRemoveFile(path);
|
|
1950
|
-
return;
|
|
2136
|
+
return true;
|
|
1951
2137
|
}
|
|
1952
|
-
|
|
2138
|
+
return fsWriteFileIfChanged(path, updatedContent);
|
|
1953
2139
|
}
|
|
1954
2140
|
/**
|
|
1955
2141
|
* Read `.npmrc` and return settings that should be migrated into workspace config.
|
|
@@ -2488,10 +2674,10 @@ async function persistMigration(options) {
|
|
|
2488
2674
|
if (!cleanNpmrc) return result;
|
|
2489
2675
|
const pruneTasks = projectNpmrcs.projects.flatMap((project) => {
|
|
2490
2676
|
const appliedKeys = selectAppliedProjectKeys(project, finalSettings);
|
|
2491
|
-
return appliedKeys.length ? [pruneNpmrc(project.npmrcPath, compatibility, appliedKeys).then(() => project.npmrcPath)] : [];
|
|
2677
|
+
return appliedKeys.length ? [pruneNpmrc(project.npmrcPath, compatibility, appliedKeys).then((changed) => changed ? project.npmrcPath : void 0)] : [];
|
|
2492
2678
|
});
|
|
2493
|
-
if (npmrcExists && appliedNpmrcKeys.length) pruneTasks.push(pruneNpmrc(npmrcPath, compatibility, appliedNpmrcKeys).then(() => npmrcPath));
|
|
2494
|
-
const cleanedNpmrcPaths = await Promise.all(pruneTasks);
|
|
2679
|
+
if (npmrcExists && appliedNpmrcKeys.length) pruneTasks.push(pruneNpmrc(npmrcPath, compatibility, appliedNpmrcKeys).then((changed) => changed ? npmrcPath : void 0));
|
|
2680
|
+
const cleanedNpmrcPaths = (await Promise.all(pruneTasks)).filter(isString);
|
|
2495
2681
|
result.changedFiles.push(...cleanedNpmrcPaths);
|
|
2496
2682
|
result.sourceSettingsCleaned ||= cleanedNpmrcPaths.length > 0;
|
|
2497
2683
|
return result;
|
|
@@ -2587,9 +2773,9 @@ function normalizeSideEffectsCache(settings) {
|
|
|
2587
2773
|
* @returns Nothing; the URL's declaration is created or updated in place
|
|
2588
2774
|
*/
|
|
2589
2775
|
function addRegistryScope(declarations, url, scope) {
|
|
2590
|
-
declarations
|
|
2591
|
-
const declaration = declarations[url];
|
|
2776
|
+
const declaration = declarations.get(url) ?? {};
|
|
2592
2777
|
declaration.scopes = unique([...declaration.scopes ?? [], scope]);
|
|
2778
|
+
declarations.set(url, declaration);
|
|
2593
2779
|
}
|
|
2594
2780
|
/**
|
|
2595
2781
|
* Find source URLs with multiple active aliases across the merged settings.
|
|
@@ -2618,9 +2804,9 @@ function findRegistryAliasConflicts(prefixesByUrl, namedRegistries, canonicalPre
|
|
|
2618
2804
|
function normalizeNamedRegistries(settings, warnings, canonicalSettings) {
|
|
2619
2805
|
const { namedRegistries } = settings;
|
|
2620
2806
|
if (!namedRegistries) return;
|
|
2621
|
-
const declarations =
|
|
2807
|
+
const declarations = /* @__PURE__ */ new Map();
|
|
2622
2808
|
for (const [key, value] of Object.entries(settings.registries ?? {})) if (isString(value)) addRegistryScope(declarations, value, key === "default" ? "@" : key);
|
|
2623
|
-
else declarations
|
|
2809
|
+
else declarations.set(key, { ...value });
|
|
2624
2810
|
const canonicalPrefixes = new Set(Object.values(canonicalSettings.registries ?? {}).flatMap((value) => !isString(value) && isString(value?.prefix) ? [value.prefix] : []));
|
|
2625
2811
|
const prefixesByUrl = /* @__PURE__ */ new Map();
|
|
2626
2812
|
const registryAliases = Object.entries(namedRegistries).filter(([prefix]) => !canonicalPrefixes.has(prefix));
|
|
@@ -2635,17 +2821,17 @@ function normalizeNamedRegistries(settings, warnings, canonicalSettings) {
|
|
|
2635
2821
|
return;
|
|
2636
2822
|
}
|
|
2637
2823
|
for (const [url, [prefix]] of prefixesByUrl) {
|
|
2638
|
-
declarations
|
|
2639
|
-
const
|
|
2640
|
-
const canonicalDeclaration = canonicalSettings.registries?.[url];
|
|
2824
|
+
const declaration = declarations.get(url) ?? {};
|
|
2825
|
+
const canonicalDeclaration = canonicalSettings.registries && Object.hasOwn(canonicalSettings.registries, url) ? canonicalSettings.registries[url] : void 0;
|
|
2641
2826
|
const declaredPrefix = (isString(canonicalDeclaration) ? void 0 : canonicalDeclaration?.prefix) ?? declaration.prefix;
|
|
2642
2827
|
if (isString(declaredPrefix) && declaredPrefix !== prefix) {
|
|
2643
2828
|
warnings.push(`namedRegistries was kept because ${url} already declares prefix ${declaredPrefix}.`);
|
|
2644
2829
|
return;
|
|
2645
2830
|
}
|
|
2646
2831
|
declaration.prefix = prefix;
|
|
2832
|
+
declarations.set(url, declaration);
|
|
2647
2833
|
}
|
|
2648
|
-
settings.registries = declarations;
|
|
2834
|
+
settings.registries = Object.fromEntries(declarations);
|
|
2649
2835
|
Reflect.deleteProperty(settings, "namedRegistries");
|
|
2650
2836
|
}
|
|
2651
2837
|
/**
|
|
@@ -3207,7 +3393,7 @@ const bold = getColor("bold");
|
|
|
3207
3393
|
//#endregion
|
|
3208
3394
|
//#region package.json
|
|
3209
3395
|
var name = "pnpm-settings-migrator";
|
|
3210
|
-
var version = "0.
|
|
3396
|
+
var version = "0.17.0";
|
|
3211
3397
|
//#endregion
|
|
3212
3398
|
//#region src/cli/options.ts
|
|
3213
3399
|
/**
|
package/dist/index.d.mts
CHANGED
|
@@ -259,6 +259,15 @@ interface PnpmSkillsSettings {
|
|
|
259
259
|
* Settings supported by pnpm v12, subject to the resolved version capabilities.
|
|
260
260
|
*/
|
|
261
261
|
interface PnpmSettingsV12 {
|
|
262
|
+
/**
|
|
263
|
+
* Link available companion @types packages as optional peers (pnpm 12.12+).
|
|
264
|
+
* Null leaves pnpm's default unchanged.
|
|
265
|
+
*/
|
|
266
|
+
addMissingPeerTypes?: boolean | null;
|
|
267
|
+
/**
|
|
268
|
+
* Project-owned remote cache configuration (pnpm 12.12+).
|
|
269
|
+
*/
|
|
270
|
+
remoteCache?: PnpmRemoteCacheSettings | null;
|
|
262
271
|
/**
|
|
263
272
|
* Per-dependency build and agent skill permissions (pnpm 12.11+).
|
|
264
273
|
*/
|
|
@@ -353,6 +362,16 @@ interface PnpmSettingsV12 {
|
|
|
353
362
|
*/
|
|
354
363
|
tasks?: Record<string, PnpmTaskSettings>;
|
|
355
364
|
}
|
|
365
|
+
/**
|
|
366
|
+
* Remote cache options allowed in project YAML. Server and signing settings
|
|
367
|
+
* belong in trusted global configuration or environment variables.
|
|
368
|
+
*/
|
|
369
|
+
interface PnpmRemoteCacheSettings {
|
|
370
|
+
/**
|
|
371
|
+
* Organization owning shared artifacts; null leaves it unset.
|
|
372
|
+
*/
|
|
373
|
+
org?: string | null;
|
|
374
|
+
}
|
|
356
375
|
//#endregion
|
|
357
376
|
//#region src/types/workspace.d.ts
|
|
358
377
|
/**
|
|
@@ -730,4 +749,4 @@ export declare function migratePnpmSettings(rawOptions?: Options): Promise<Migra
|
|
|
730
749
|
*/
|
|
731
750
|
export declare function resolveOptions(options?: Options): ResolvedOptions;
|
|
732
751
|
//#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 };
|
|
752
|
+
export type { CompatibilityTarget, GlobalShimPolicy, MergeStrategy, MigrationResult, NpmRC, Options, PackageJson, PackageManagerEngine, PnpmCargoSettings, PnpmLoadedNodeLinkerSettings, PnpmLockfileSettings, PnpmPackagePermissions, PnpmPythonSettings, PnpmRegistryDeclaration, PnpmRemoteCacheSettings, PnpmSettingsDeprecated, PnpmSettingsV11, PnpmSettingsV12, PnpmSkillsSettings, PnpmTaskSettings, PnpmWorkspace, PnpmWorkspaceLegacy, ResolvedOptions, RuntimeEngine, SettingsChange };
|
package/dist/index.mjs
CHANGED
|
@@ -625,6 +625,22 @@ const PNPM_V12_11_MINIMUM_VERSION = [
|
|
|
625
625
|
0
|
|
626
626
|
];
|
|
627
627
|
/**
|
|
628
|
+
* Release introducing remote cache configuration and missing peer types.
|
|
629
|
+
*/
|
|
630
|
+
const PNPM_V12_12_MINIMUM_VERSION = [
|
|
631
|
+
12,
|
|
632
|
+
12,
|
|
633
|
+
0
|
|
634
|
+
];
|
|
635
|
+
/**
|
|
636
|
+
* Release reserving the tool registry prefix.
|
|
637
|
+
*/
|
|
638
|
+
const PNPM_V12_12_1_MINIMUM_VERSION = [
|
|
639
|
+
12,
|
|
640
|
+
12,
|
|
641
|
+
1
|
|
642
|
+
];
|
|
643
|
+
/**
|
|
628
644
|
* Workspace settings first consumed by pnpm v12 in 12.11.0.
|
|
629
645
|
*/
|
|
630
646
|
const PNPM_V12_11_WORKSPACE_SETTINGS = [
|
|
@@ -650,6 +666,11 @@ const PNPM_V12_7_WORKSPACE_SETTINGS = [
|
|
|
650
666
|
* @see https://github.com/pnpm/pnpm/blob/v12.4.0/pnpm/crates/config/src/workspace_yaml.rs
|
|
651
667
|
*/
|
|
652
668
|
const PNPM_SETTINGS_CAPABILITIES = [
|
|
669
|
+
{
|
|
670
|
+
minimumVersion: PNPM_V12_12_MINIMUM_VERSION,
|
|
671
|
+
workspaceFields: ["addMissingPeerTypes", "remoteCache"],
|
|
672
|
+
taskFields: []
|
|
673
|
+
},
|
|
653
674
|
{
|
|
654
675
|
minimumVersion: PNPM_V12_11_MINIMUM_VERSION,
|
|
655
676
|
workspaceFields: PNPM_V12_11_WORKSPACE_SETTINGS,
|
|
@@ -731,9 +752,82 @@ const PNPM_V12_ONLY_WORKSPACE_SETTINGS = [
|
|
|
731
752
|
"globalShims"
|
|
732
753
|
];
|
|
733
754
|
/**
|
|
755
|
+
* Legacy fields absent from pnpm v12's workspace reader through 12.12.1.
|
|
756
|
+
* Removed aliases with explicit migration replacements are kept separately.
|
|
757
|
+
*
|
|
758
|
+
* @see https://github.com/pnpm/pnpm/blob/v12.12.1/pnpm/crates/config/src/workspace_yaml/settings.rs
|
|
759
|
+
*/
|
|
760
|
+
const PNPM_V12_IGNORED_WORKSPACE_SETTINGS = [
|
|
761
|
+
"access",
|
|
762
|
+
"allowNew",
|
|
763
|
+
"allowSameVersion",
|
|
764
|
+
"auditIgnorePrune",
|
|
765
|
+
"autoConfirmAllPrompts",
|
|
766
|
+
"binLinks",
|
|
767
|
+
"commitHooks",
|
|
768
|
+
"cpu",
|
|
769
|
+
"depth",
|
|
770
|
+
"description",
|
|
771
|
+
"dev",
|
|
772
|
+
"dryRun",
|
|
773
|
+
"enablePnp",
|
|
774
|
+
"extraBinPaths",
|
|
775
|
+
"extraEnv",
|
|
776
|
+
"fetchingConcurrency",
|
|
777
|
+
"force",
|
|
778
|
+
"git",
|
|
779
|
+
"gitTagVersion",
|
|
780
|
+
"global",
|
|
781
|
+
"globalPath",
|
|
782
|
+
"globalPrefix",
|
|
783
|
+
"ignoreCurrentSpecifiers",
|
|
784
|
+
"json",
|
|
785
|
+
"libc",
|
|
786
|
+
"localAddress",
|
|
787
|
+
"lockfileOnly",
|
|
788
|
+
"long",
|
|
789
|
+
"message",
|
|
790
|
+
"npmPath",
|
|
791
|
+
"only",
|
|
792
|
+
"os",
|
|
793
|
+
"packDestination",
|
|
794
|
+
"packGzipLevel",
|
|
795
|
+
"parseable",
|
|
796
|
+
"pnpmExecPath",
|
|
797
|
+
"preserveAbsolutePaths",
|
|
798
|
+
"production",
|
|
799
|
+
"publishBranch",
|
|
800
|
+
"recursive",
|
|
801
|
+
"registriesByPrefix",
|
|
802
|
+
"registriesByScope",
|
|
803
|
+
"registryOptionsByUrl",
|
|
804
|
+
"requiredScripts",
|
|
805
|
+
"runtime",
|
|
806
|
+
"save",
|
|
807
|
+
"saveDev",
|
|
808
|
+
"saveOptional",
|
|
809
|
+
"saveProd",
|
|
810
|
+
"sideEffectsCacheRead",
|
|
811
|
+
"sideEffectsCacheWrite",
|
|
812
|
+
"signGitTag",
|
|
813
|
+
"strictSsl",
|
|
814
|
+
"tag",
|
|
815
|
+
"tryLoadDefaultPnpmfile",
|
|
816
|
+
"umask",
|
|
817
|
+
"useGitBranchLockfile",
|
|
818
|
+
"useLockfile",
|
|
819
|
+
"useRunningStoreServer",
|
|
820
|
+
"version",
|
|
821
|
+
"workspacePackagePatterns",
|
|
822
|
+
"workspacePackages",
|
|
823
|
+
"workspacePrefix",
|
|
824
|
+
"workspaceRoot",
|
|
825
|
+
"yes"
|
|
826
|
+
];
|
|
827
|
+
/**
|
|
734
828
|
* Lookup used to remove v11-only fields from the v12 workspace allowlist.
|
|
735
829
|
*/
|
|
736
|
-
const v11OnlyWorkspaceSettings = new Set(PNPM_V11_ONLY_WORKSPACE_SETTINGS);
|
|
830
|
+
const v11OnlyWorkspaceSettings = /* @__PURE__ */ new Set([...PNPM_V11_ONLY_WORKSPACE_SETTINGS, ...PNPM_V12_IGNORED_WORKSPACE_SETTINGS]);
|
|
737
831
|
/**
|
|
738
832
|
* Fields accepted by registry declarations with pnpm 12.9 concurrency limits.
|
|
739
833
|
*/
|
|
@@ -768,7 +862,7 @@ const PROJECT_REFUSED_SETTINGS = new Set(PNPM_PROJECT_REFUSED_SETTINGS);
|
|
|
768
862
|
/**
|
|
769
863
|
* Fields reported as incompatible when selecting settings for v12.
|
|
770
864
|
*/
|
|
771
|
-
const PNPM_V11_ONLY_SETTINGS = new Set(PNPM_V11_ONLY_WORKSPACE_SETTINGS);
|
|
865
|
+
const PNPM_V11_ONLY_SETTINGS = /* @__PURE__ */ new Set([...PNPM_V11_ONLY_WORKSPACE_SETTINGS, ...PNPM_V12_IGNORED_WORKSPACE_SETTINGS]);
|
|
772
866
|
/**
|
|
773
867
|
* Allowed workspace fields for the v11 target.
|
|
774
868
|
*/
|
|
@@ -1112,6 +1206,32 @@ function formatSettingsIssues(options) {
|
|
|
1112
1206
|
return warnings;
|
|
1113
1207
|
}
|
|
1114
1208
|
//#endregion
|
|
1209
|
+
//#region src/features/settings/registries.ts
|
|
1210
|
+
/**
|
|
1211
|
+
* Check that a registry location is a URL rather than an object property name.
|
|
1212
|
+
*
|
|
1213
|
+
* @param value - Untrusted registry location
|
|
1214
|
+
*
|
|
1215
|
+
* @returns Whether the location is an absolute or scheme-relative URL
|
|
1216
|
+
*/
|
|
1217
|
+
function isRegistryUrl(value) {
|
|
1218
|
+
return isString(value) && URL.canParse(value.startsWith("//") ? `https:${value}` : value);
|
|
1219
|
+
}
|
|
1220
|
+
/**
|
|
1221
|
+
* Validate locations in legacy registry maps and URL-keyed declarations.
|
|
1222
|
+
*
|
|
1223
|
+
* @param key - Canonical workspace setting name
|
|
1224
|
+
* @param value - Untrusted registry configuration
|
|
1225
|
+
*
|
|
1226
|
+
* @returns Whether the complete setting must be retained in its source
|
|
1227
|
+
*/
|
|
1228
|
+
function hasInvalidRegistryLocations(key, value) {
|
|
1229
|
+
if (!["namedRegistries", "registries"].includes(key) || value === null) return false;
|
|
1230
|
+
if (!isPlainObject(value)) return true;
|
|
1231
|
+
if (key === "namedRegistries") return Object.values(value).some((url) => !isRegistryUrl(url));
|
|
1232
|
+
return Object.entries(value).some(([url, entry]) => isString(entry) ? !isRegistryUrl(entry) : !isRegistryUrl(url));
|
|
1233
|
+
}
|
|
1234
|
+
//#endregion
|
|
1115
1235
|
//#region src/features/settings/permissions.ts
|
|
1116
1236
|
/**
|
|
1117
1237
|
* Validate a dependency's capability map without dropping unknown permissions.
|
|
@@ -1256,6 +1376,22 @@ function hasIncompatibleV12SettingValue(key, value, target) {
|
|
|
1256
1376
|
return false;
|
|
1257
1377
|
}
|
|
1258
1378
|
//#endregion
|
|
1379
|
+
//#region src/features/settings/remote-cache.ts
|
|
1380
|
+
/**
|
|
1381
|
+
* Accept only the project-owned remote cache organization.
|
|
1382
|
+
* Keep machine configuration and unknown fields intact in their source.
|
|
1383
|
+
*
|
|
1384
|
+
* @param key - Canonical workspace setting name
|
|
1385
|
+
* @param value - Untrusted incoming or existing workspace value
|
|
1386
|
+
*
|
|
1387
|
+
* @returns Whether the remote cache setting cannot migrate to project YAML
|
|
1388
|
+
*/
|
|
1389
|
+
function hasInvalidRemoteCacheValue(key, value) {
|
|
1390
|
+
if (key !== "remoteCache" || value === null) return false;
|
|
1391
|
+
if (!value || typeof value !== "object" || isArray(value)) return true;
|
|
1392
|
+
return Object.entries(value).some(([field, entry]) => field !== "org" || entry !== null && !isString(entry));
|
|
1393
|
+
}
|
|
1394
|
+
//#endregion
|
|
1259
1395
|
//#region src/features/settings/versioned-values.ts
|
|
1260
1396
|
/**
|
|
1261
1397
|
* Bounds of the signed 32-bit integer used by pnpm's task priority.
|
|
@@ -1304,7 +1440,7 @@ function hasInvalidScalarValue(key, value, target) {
|
|
|
1304
1440
|
"ndjson",
|
|
1305
1441
|
"silent"
|
|
1306
1442
|
].includes(value);
|
|
1307
|
-
if (key === "provenance") return value !== null && !isBoolean(value);
|
|
1443
|
+
if (key === "provenance" || key === "addMissingPeerTypes") return value !== null && !isBoolean(value);
|
|
1308
1444
|
if (key === "progress") return !isBoolean(value);
|
|
1309
1445
|
if (key === "tagVersionPrefix") return !isString(value);
|
|
1310
1446
|
if (key === "loglevel") return !isString(value) || !value.includes("${") && ![
|
|
@@ -1334,6 +1470,17 @@ function hasInvalidReleaseValue(key, value, target) {
|
|
|
1334
1470
|
return false;
|
|
1335
1471
|
}
|
|
1336
1472
|
/**
|
|
1473
|
+
* Check release-specific reserved aliases without broadening case sensitivity.
|
|
1474
|
+
*
|
|
1475
|
+
* @param prefix - Registry alias as declared
|
|
1476
|
+
* @param target - Confirmed target version
|
|
1477
|
+
*
|
|
1478
|
+
* @returns Whether the alias shadows a protocol introduced by these releases
|
|
1479
|
+
*/
|
|
1480
|
+
function isReservedRegistryPrefix(prefix, target) {
|
|
1481
|
+
return prefix.toLowerCase() === "pkg" && supportsMinimumVersion(target.version, PNPM_V12_5_MINIMUM_VERSION) || prefix === "tool" && supportsMinimumVersion(target.version, PNPM_V12_12_1_MINIMUM_VERSION);
|
|
1482
|
+
}
|
|
1483
|
+
/**
|
|
1337
1484
|
* Check value shapes that changed within a pnpm major release.
|
|
1338
1485
|
*
|
|
1339
1486
|
* Reject the whole setting so cleanup cannot discard unsupported nested values.
|
|
@@ -1345,7 +1492,7 @@ function hasInvalidReleaseValue(key, value, target) {
|
|
|
1345
1492
|
* @returns Whether the value uses a shape unsupported by the selected release
|
|
1346
1493
|
*/
|
|
1347
1494
|
function hasIncompatibleSettingValue(key, value, target) {
|
|
1348
|
-
if (hasInvalidPermissionSettings(key, value) || hasInvalidReleaseValue(key, value, target) || hasInvalidV11PatchValue(key, value, target) || hasIncompatibleV12SettingValue(key, value, target)) return true;
|
|
1495
|
+
if (hasInvalidPermissionSettings(key, value) || hasInvalidRemoteCacheValue(key, value) || hasInvalidReleaseValue(key, value, target) || hasInvalidV11PatchValue(key, value, target) || hasIncompatibleV12SettingValue(key, value, target)) return true;
|
|
1349
1496
|
if (hasInvalidScalarValue(key, value, target)) return true;
|
|
1350
1497
|
const supportsV12_5 = supportsMinimumVersion(target.version, PNPM_V12_5_MINIMUM_VERSION);
|
|
1351
1498
|
if (key === "supportedArchitectures" && isArray(value)) return !supportsV12_5;
|
|
@@ -1363,19 +1510,42 @@ function hasIncompatibleSettingValue(key, value, target) {
|
|
|
1363
1510
|
"constraints"
|
|
1364
1511
|
].some((field) => Object.hasOwn(value, field));
|
|
1365
1512
|
}
|
|
1366
|
-
if (key === "namedRegistries"
|
|
1513
|
+
if (key === "namedRegistries") return Object.keys(value).some((prefix) => isReservedRegistryPrefix(prefix, target));
|
|
1367
1514
|
if (key === "registries") return Object.values(value).some((entry) => {
|
|
1368
1515
|
if (!entry || typeof entry !== "object") return false;
|
|
1369
1516
|
if (Object.hasOwn(entry, "ecosystem") && !supportsV12_5) return true;
|
|
1370
1517
|
if (Object.hasOwn(entry, "packages") && !supportsMinimumVersion(target.version, PNPM_V12_5_1_MINIMUM_VERSION)) return true;
|
|
1371
1518
|
const prefix = Reflect.get(entry, "prefix");
|
|
1372
|
-
return
|
|
1519
|
+
return isString(prefix) && isReservedRegistryPrefix(prefix, target);
|
|
1373
1520
|
});
|
|
1374
1521
|
return false;
|
|
1375
1522
|
}
|
|
1376
1523
|
//#endregion
|
|
1377
1524
|
//#region src/features/settings/schema.ts
|
|
1378
1525
|
/**
|
|
1526
|
+
* Detect legacy runtime fields without a destination in pnpm v11 and newer.
|
|
1527
|
+
*
|
|
1528
|
+
* @param key - Canonical setting name
|
|
1529
|
+
* @param value - Untrusted legacy runtime declaration
|
|
1530
|
+
* @param compatibility - Target major controlling runtime conversion
|
|
1531
|
+
*
|
|
1532
|
+
* @returns Whether converting the runtime would discard sibling fields
|
|
1533
|
+
*/
|
|
1534
|
+
function hasUnmigratableRuntimeFields(key, value, compatibility) {
|
|
1535
|
+
return Boolean(key === "executionEnv" && compatibility !== "v10" && value && typeof value === "object" && Object.keys(value).some((field) => field !== "nodeVersion"));
|
|
1536
|
+
}
|
|
1537
|
+
/**
|
|
1538
|
+
* Check task declarations against the fields consumed by the target release.
|
|
1539
|
+
*
|
|
1540
|
+
* @param value - Untrusted task declarations
|
|
1541
|
+
* @param taskSettings - Supported task field names
|
|
1542
|
+
*
|
|
1543
|
+
* @returns Whether any declared task field is unsupported
|
|
1544
|
+
*/
|
|
1545
|
+
function hasUnsupportedTaskFields(value, taskSettings) {
|
|
1546
|
+
return Boolean(value && typeof value === "object" && Object.values(value).some((task) => task && typeof task === "object" && Object.keys(task).some((field) => !taskSettings.has(field))));
|
|
1547
|
+
}
|
|
1548
|
+
/**
|
|
1379
1549
|
* Create an empty collection of schema selection issues.
|
|
1380
1550
|
*
|
|
1381
1551
|
* @returns Empty issue lists for every supported issue category
|
|
@@ -1480,8 +1650,8 @@ function resolveSettingIssue({ allowedFields, target, key, npmrc, value }) {
|
|
|
1480
1650
|
if (compatibility !== "v10" && PROJECT_REFUSED_SETTINGS.has(key)) return "refused";
|
|
1481
1651
|
if (REGISTRY_SETTINGS.has(key) && containsUnsafeRegistryValue(value)) return "unsafe";
|
|
1482
1652
|
if (PROXY_SETTINGS.has(key) && isString(value) && value.includes("${")) return "unsafe";
|
|
1483
|
-
if (hasIncompatibleSettingValue(key, value, target)) return "incompatible";
|
|
1484
|
-
if (key === "tasks" &&
|
|
1653
|
+
if (hasInvalidRegistryLocations(key, value) || hasUnmigratableRuntimeFields(key, value, compatibility) || hasIncompatibleSettingValue(key, value, target)) return "incompatible";
|
|
1654
|
+
if (key === "tasks" && hasUnsupportedTaskFields(value, taskSettings)) return "incompatible";
|
|
1485
1655
|
if (!workspaceSettings.has(key)) return isSettingFromAnotherVersion(key, compatibility) ? "incompatible" : "unknown";
|
|
1486
1656
|
if (allowedFields && !allowedFields.has(key)) return "unsupported";
|
|
1487
1657
|
}
|
|
@@ -1890,8 +2060,17 @@ function getNpmrcLineKey(line) {
|
|
|
1890
2060
|
const trimmed = line.trim();
|
|
1891
2061
|
if (!trimmed || trimmed.startsWith(";") || trimmed.startsWith("#")) return;
|
|
1892
2062
|
const index = trimmed.indexOf("=");
|
|
1893
|
-
if (index
|
|
1894
|
-
|
|
2063
|
+
if (index === 0) return;
|
|
2064
|
+
let key = (index < 0 ? trimmed : trimmed.slice(0, index)).trim();
|
|
2065
|
+
if (key.startsWith("\"") && key.endsWith("\"") || key.startsWith("'") && key.endsWith("'")) {
|
|
2066
|
+
if (key.startsWith("'")) key = key.slice(1, -1);
|
|
2067
|
+
try {
|
|
2068
|
+
const parsed = JSON.parse(key);
|
|
2069
|
+
if (!isString(parsed)) return;
|
|
2070
|
+
key = parsed;
|
|
2071
|
+
} catch {}
|
|
2072
|
+
} else key = key.replace(/\\(?<escaped>[\\;#])|[;#].*$/gu, (_, escaped) => escaped ?? "");
|
|
2073
|
+
return normalizeNpmrcKey(key);
|
|
1895
2074
|
}
|
|
1896
2075
|
/**
|
|
1897
2076
|
* Check whether a `.npmrc` key is auth/registry-related and should stay in
|
|
@@ -1918,7 +2097,7 @@ function isNpmrcAuthOrRegistryKey(key) {
|
|
|
1918
2097
|
* @param compatibility - Concrete pnpm compatibility target
|
|
1919
2098
|
* @param migratedKeys - Original `.npmrc` keys that were migrated
|
|
1920
2099
|
*
|
|
1921
|
-
* @returns
|
|
2100
|
+
* @returns Whether the file was changed or removed
|
|
1922
2101
|
*
|
|
1923
2102
|
* @throws {Error} When file read/write operations fail
|
|
1924
2103
|
*
|
|
@@ -1931,18 +2110,25 @@ async function pruneNpmrc(path, compatibility, migratedKeys) {
|
|
|
1931
2110
|
const migratedKeySet = new Set(migratedKeys.map(normalizeNpmrcKey));
|
|
1932
2111
|
const content = await fsReadFile(path);
|
|
1933
2112
|
let inSection = false;
|
|
1934
|
-
|
|
2113
|
+
let removed = false;
|
|
2114
|
+
const lines = content.split(/\r\n|[\r\n]/u).filter((line) => {
|
|
1935
2115
|
if (/^\[[^\]]*\]\s*$/u.test(line.replace(/^\uFEFF/u, ""))) inSection = true;
|
|
1936
2116
|
if (inSection) return true;
|
|
1937
2117
|
const key = getNpmrcLineKey(line);
|
|
1938
2118
|
if (!key) return true;
|
|
1939
|
-
|
|
1940
|
-
|
|
2119
|
+
if (migratedKeySet.has(key)) {
|
|
2120
|
+
removed = true;
|
|
2121
|
+
return false;
|
|
2122
|
+
}
|
|
2123
|
+
return true;
|
|
2124
|
+
});
|
|
2125
|
+
if (!removed) return false;
|
|
2126
|
+
const updatedContent = lines.join("\n").trimEnd();
|
|
1941
2127
|
if (compatibility !== "v10" && !updatedContent.trim()) {
|
|
1942
2128
|
await fsRemoveFile(path);
|
|
1943
|
-
return;
|
|
2129
|
+
return true;
|
|
1944
2130
|
}
|
|
1945
|
-
|
|
2131
|
+
return fsWriteFileIfChanged(path, updatedContent);
|
|
1946
2132
|
}
|
|
1947
2133
|
/**
|
|
1948
2134
|
* Read `.npmrc` and return settings that should be migrated into workspace config.
|
|
@@ -2481,10 +2667,10 @@ async function persistMigration(options) {
|
|
|
2481
2667
|
if (!cleanNpmrc) return result;
|
|
2482
2668
|
const pruneTasks = projectNpmrcs.projects.flatMap((project) => {
|
|
2483
2669
|
const appliedKeys = selectAppliedProjectKeys(project, finalSettings);
|
|
2484
|
-
return appliedKeys.length ? [pruneNpmrc(project.npmrcPath, compatibility, appliedKeys).then(() => project.npmrcPath)] : [];
|
|
2670
|
+
return appliedKeys.length ? [pruneNpmrc(project.npmrcPath, compatibility, appliedKeys).then((changed) => changed ? project.npmrcPath : void 0)] : [];
|
|
2485
2671
|
});
|
|
2486
|
-
if (npmrcExists && appliedNpmrcKeys.length) pruneTasks.push(pruneNpmrc(npmrcPath, compatibility, appliedNpmrcKeys).then(() => npmrcPath));
|
|
2487
|
-
const cleanedNpmrcPaths = await Promise.all(pruneTasks);
|
|
2672
|
+
if (npmrcExists && appliedNpmrcKeys.length) pruneTasks.push(pruneNpmrc(npmrcPath, compatibility, appliedNpmrcKeys).then((changed) => changed ? npmrcPath : void 0));
|
|
2673
|
+
const cleanedNpmrcPaths = (await Promise.all(pruneTasks)).filter(isString);
|
|
2488
2674
|
result.changedFiles.push(...cleanedNpmrcPaths);
|
|
2489
2675
|
result.sourceSettingsCleaned ||= cleanedNpmrcPaths.length > 0;
|
|
2490
2676
|
return result;
|
|
@@ -2580,9 +2766,9 @@ function normalizeSideEffectsCache(settings) {
|
|
|
2580
2766
|
* @returns Nothing; the URL's declaration is created or updated in place
|
|
2581
2767
|
*/
|
|
2582
2768
|
function addRegistryScope(declarations, url, scope) {
|
|
2583
|
-
declarations
|
|
2584
|
-
const declaration = declarations[url];
|
|
2769
|
+
const declaration = declarations.get(url) ?? {};
|
|
2585
2770
|
declaration.scopes = unique([...declaration.scopes ?? [], scope]);
|
|
2771
|
+
declarations.set(url, declaration);
|
|
2586
2772
|
}
|
|
2587
2773
|
/**
|
|
2588
2774
|
* Find source URLs with multiple active aliases across the merged settings.
|
|
@@ -2611,9 +2797,9 @@ function findRegistryAliasConflicts(prefixesByUrl, namedRegistries, canonicalPre
|
|
|
2611
2797
|
function normalizeNamedRegistries(settings, warnings, canonicalSettings) {
|
|
2612
2798
|
const { namedRegistries } = settings;
|
|
2613
2799
|
if (!namedRegistries) return;
|
|
2614
|
-
const declarations =
|
|
2800
|
+
const declarations = /* @__PURE__ */ new Map();
|
|
2615
2801
|
for (const [key, value] of Object.entries(settings.registries ?? {})) if (isString(value)) addRegistryScope(declarations, value, key === "default" ? "@" : key);
|
|
2616
|
-
else declarations
|
|
2802
|
+
else declarations.set(key, { ...value });
|
|
2617
2803
|
const canonicalPrefixes = new Set(Object.values(canonicalSettings.registries ?? {}).flatMap((value) => !isString(value) && isString(value?.prefix) ? [value.prefix] : []));
|
|
2618
2804
|
const prefixesByUrl = /* @__PURE__ */ new Map();
|
|
2619
2805
|
const registryAliases = Object.entries(namedRegistries).filter(([prefix]) => !canonicalPrefixes.has(prefix));
|
|
@@ -2628,17 +2814,17 @@ function normalizeNamedRegistries(settings, warnings, canonicalSettings) {
|
|
|
2628
2814
|
return;
|
|
2629
2815
|
}
|
|
2630
2816
|
for (const [url, [prefix]] of prefixesByUrl) {
|
|
2631
|
-
declarations
|
|
2632
|
-
const
|
|
2633
|
-
const canonicalDeclaration = canonicalSettings.registries?.[url];
|
|
2817
|
+
const declaration = declarations.get(url) ?? {};
|
|
2818
|
+
const canonicalDeclaration = canonicalSettings.registries && Object.hasOwn(canonicalSettings.registries, url) ? canonicalSettings.registries[url] : void 0;
|
|
2634
2819
|
const declaredPrefix = (isString(canonicalDeclaration) ? void 0 : canonicalDeclaration?.prefix) ?? declaration.prefix;
|
|
2635
2820
|
if (isString(declaredPrefix) && declaredPrefix !== prefix) {
|
|
2636
2821
|
warnings.push(`namedRegistries was kept because ${url} already declares prefix ${declaredPrefix}.`);
|
|
2637
2822
|
return;
|
|
2638
2823
|
}
|
|
2639
2824
|
declaration.prefix = prefix;
|
|
2825
|
+
declarations.set(url, declaration);
|
|
2640
2826
|
}
|
|
2641
|
-
settings.registries = declarations;
|
|
2827
|
+
settings.registries = Object.fromEntries(declarations);
|
|
2642
2828
|
Reflect.deleteProperty(settings, "namedRegistries");
|
|
2643
2829
|
}
|
|
2644
2830
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pnpm-settings-migrator",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.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",
|