pnpm-settings-migrator 0.7.0 → 0.8.1

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/dist/cli.mjs CHANGED
@@ -1,86 +1,133 @@
1
1
  import process from "node:process";
2
- import { cac } from "cac";
3
2
  import consola$1, { consola } from "consola";
4
- import { resolve } from "pathe";
5
- import { Document, parse } from "yaml";
6
- import { access, readFile, rm, writeFile } from "node:fs/promises";
3
+ import { cac } from "cac";
4
+ import { basename, dirname, join, resolve } from "pathe";
5
+ import { randomUUID } from "node:crypto";
6
+ import { access, chmod, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
7
+ import { fileURLToPath } from "node:url";
7
8
  import { getColor } from "consola/utils";
8
- import { isPlainObject, isUndefined, pick } from "@ntnyq/utils";
9
+ import { isPlainObject, isUndefined } from "@ntnyq/utils";
9
10
  import { defu } from "defu";
10
11
  import camelcaseKeys from "camelcase-keys";
11
12
  import { readIniFile } from "read-ini-file";
12
13
  import { kebabCase } from "uncase";
14
+ import { isDeepStrictEqual } from "node:util";
15
+ import { Document, isMap, isNode, isScalar, isSeq, parseDocument, stringify } from "yaml";
13
16
  import detectIndent from "detect-indent";
14
17
  //#region package.json
15
18
  var name = "pnpm-settings-migrator";
16
- var version = "0.7.0";
19
+ var version = "0.8.1";
20
+ //#endregion
21
+ //#region src/cli-options.ts
22
+ /**
23
+ * Create the command-line parser shared by the executable and parser tests.
24
+ */
25
+ function createCli() {
26
+ const cli = cac(name).version(version).option("--cwd <cwd>", "Current working directory").option("--sort-keys", "Sort keys when write pnpm-workspace.yaml").option("--compatibility <compatibility>", "Compatibility target (auto, v10, v11, v12)").option("--replace-deprecated", "Replace deprecated pnpm settings with new ones and remove old keys").option("--strategy <strategy>", "Strategy to handle conflicts (discard, merge, overwrite)").option("--no-yarn-resolutions", "Disable migrating resolutions field in package.json").option("--no-newline-between", "Disable adding newlines between each root keys").option("--no-show-changes", "Disable showing settings changes").option("--no-clean-npmrc", "Disable removing pnpm settings in .npmrc file").option("--no-clean-package-json", "Disable removing pnpm fields in package.json");
27
+ cli.help((sections) => {
28
+ if (!(cli.commands.length > 0 && cli.commands.every((command) => command.isDefaultCommand))) return sections;
29
+ return sections.filter((section) => section.title !== "Commands" && !section.title?.startsWith("For more info"));
30
+ });
31
+ return cli;
32
+ }
17
33
  //#endregion
18
34
  //#region src/constants.ts
19
35
  const NPMRC = ".npmrc";
20
36
  const PACKAGE_JSON = "package.json";
21
37
  const PNPM_WORKSPACE_YAML = "pnpm-workspace.yaml";
22
38
  /**
23
- * @see {@link https://github.com/pnpm/pnpm/blob/main/packages/types/src/package.ts}
24
- * Schema-aligned pnpm config fields that can be migrated from `.npmrc`.
39
+ * @see {@link https://pnpm.io/10.x/settings}
40
+ * Documented pnpm v10 config fields that can be migrated from `.npmrc`.
25
41
  *
26
42
  * Intentionally excluded workspace manifest-only fields:
27
43
  * - `catalog`
28
- * - `catalogMode`
29
44
  * - `catalogs`
30
- * - `cleanupUnusedCatalogs`
31
45
  * - `packages`
32
46
  */
33
- const PNPM_SETTINGS_FIELDS = [
47
+ const PNPM_V10_NPMRC_SETTINGS_FIELDS = [
34
48
  "allowBuilds",
35
49
  "allowedDeprecatedVersions",
36
50
  "allowNonAppliedPatches",
37
51
  "allowUnusedPatches",
38
52
  "auditConfig",
53
+ "auditLevel",
54
+ "autoInstallPeers",
55
+ "blockExoticSubdeps",
56
+ "cacheDir",
57
+ "catalogMode",
58
+ "childConcurrency",
59
+ "ci",
60
+ "cleanupUnusedCatalogs",
61
+ "color",
39
62
  "configDependencies",
63
+ "dangerouslyAllowAllBuilds",
40
64
  "dedupeDirectDeps",
41
65
  "dedupeInjectedDeps",
42
66
  "dedupePeerDependents",
43
67
  "deployAllFiles",
68
+ "disallowWorkspaceCycles",
69
+ "dlxCacheMaxAge",
70
+ "enableGlobalVirtualStore",
71
+ "enableModulesDir",
72
+ "enablePrePostScripts",
44
73
  "engineStrict",
45
74
  "executionEnv",
75
+ "extendNodePath",
76
+ "failIfNoMatch",
46
77
  "fetchMinSpeedKiBps",
78
+ "fetchRetries",
47
79
  "fetchRetryFactor",
48
80
  "fetchRetryMaxtimeout",
49
81
  "fetchRetryMintimeout",
50
- "fetchRetryTimeout",
82
+ "fetchTimeout",
51
83
  "fetchWarnTimeoutMs",
52
84
  "forceLegacyDeploy",
85
+ "frozenLockfile",
86
+ "gitBranchLockfile",
53
87
  "gitChecks",
54
88
  "gitShallowHosts",
55
- "globalVirtualStore",
89
+ "globalBinDir",
90
+ "globalDir",
91
+ "globalPnpmfile",
56
92
  "globalVirtualStoreDir",
57
93
  "hoist",
58
94
  "hoistPattern",
59
95
  "hoistWorkspacePackages",
60
96
  "httpProxy",
61
97
  "httpsProxy",
98
+ "ignoreCompatibilityDb",
62
99
  "ignoredBuiltDependencies",
63
100
  "ignoreDepScripts",
64
- "ignoreDeps",
65
101
  "ignoredOptionalDependencies",
66
102
  "ignorePatchFailures",
103
+ "ignorePnpmfile",
104
+ "ignoreScripts",
67
105
  "ignoreWorkspaceCycles",
106
+ "ignoreWorkspaceRootCheck",
107
+ "includeWorkspaceRoot",
68
108
  "injectWorkspacePackages",
69
109
  "linkWorkspacePackages",
110
+ "localAddress",
70
111
  "lockfile",
71
112
  "lockfileDir",
113
+ "lockfileIncludeTarballUrl",
114
+ "loglevel",
72
115
  "managePackageManagerVersions",
116
+ "maxsockets",
73
117
  "mergeGitBranchLockfiles",
74
118
  "mergeGitBranchLockfilesBranchPattern",
119
+ "minimumReleaseAge",
120
+ "minimumReleaseAgeExclude",
75
121
  "modulesCacheMaxAge",
122
+ "modulesDir",
76
123
  "networkConcurrency",
77
124
  "neverBuiltDependencies",
78
- "nodeDownloadMirrors",
79
125
  "nodeLinker",
80
- "noproxy",
126
+ "nodeOptions",
127
+ "nodeVersion",
81
128
  "noProxy",
129
+ "noproxy",
82
130
  "npmPath",
83
- "npmrcAuthFile",
84
131
  "onlyBuiltDependencies",
85
132
  "onlyBuiltDependenciesFile",
86
133
  "optimisticRepeatInstall",
@@ -94,38 +141,46 @@ const PNPM_SETTINGS_FIELDS = [
94
141
  "peerDependencyRules",
95
142
  "peersSuffixMaxLength",
96
143
  "pnpmfile",
97
- "pnpmfileIgnorePkgManifest",
98
- "pmOnFail",
99
144
  "preferFrozenLockfile",
145
+ "preferSymlinkedExecutables",
100
146
  "preferWorkspacePackages",
101
147
  "production",
102
148
  "proxy",
103
149
  "publicHoistPattern",
104
150
  "recursiveInstall",
105
- "registries",
151
+ "registrySupportsTimeField",
106
152
  "requiredScripts",
107
153
  "resolutionMode",
108
154
  "resolvePeersFromWorkspaceRoot",
109
155
  "saveExact",
110
156
  "savePrefix",
157
+ "saveWorkspaceProtocol",
111
158
  "scriptShell",
112
159
  "shamefullyHoist",
113
160
  "sharedWorkspaceLockfile",
114
161
  "shellEmulator",
115
162
  "sideEffectsCache",
116
163
  "sideEffectsCacheReadonly",
164
+ "stateDir",
117
165
  "storeDir",
118
166
  "strictDepBuilds",
119
167
  "strictPeerDependencies",
120
168
  "strictSsl",
169
+ "strictStorePkgContentCheck",
121
170
  "supportedArchitectures",
171
+ "symlink",
122
172
  "syncInjectedDepsAfterScripts",
123
173
  "tag",
124
- "targetDependenciesField",
125
- "timeout",
174
+ "trustPolicy",
175
+ "trustPolicyExclude",
176
+ "trustPolicyIgnoreAfter",
177
+ "unsafePerm",
126
178
  "updateConfig",
179
+ "updateNotifier",
127
180
  "useBetaCli",
128
181
  "useNodeVersion",
182
+ "useRunningStoreServer",
183
+ "useStderr",
129
184
  "verifyDepsBeforeRun",
130
185
  "verifyStoreIntegrity",
131
186
  "virtualStoreDir",
@@ -155,9 +210,9 @@ const DEFAULT_OPTIONS = {
155
210
  cleanNpmrc: true,
156
211
  cleanPackageJson: true,
157
212
  compatibility: "auto",
158
- cwd: process.cwd(),
159
213
  newlineBetween: true,
160
214
  replaceDeprecated: false,
215
+ showChanges: true,
161
216
  sortKeys: false,
162
217
  strategy: "merge",
163
218
  yarnResolutions: true
@@ -208,8 +263,9 @@ function resolveOptions(options = {}) {
208
263
  return {
209
264
  cleanNpmrc: options.cleanNpmrc ?? DEFAULT_OPTIONS.cleanNpmrc,
210
265
  compatibility: resolveCompatibility(options.compatibility),
211
- cwd: options.cwd ?? DEFAULT_OPTIONS.cwd,
266
+ cwd: options.cwd ?? process.cwd(),
212
267
  newlineBetween: options.newlineBetween ?? DEFAULT_OPTIONS.newlineBetween,
268
+ showChanges: options.showChanges ?? DEFAULT_OPTIONS.showChanges,
213
269
  sortKeys: options.sortKeys ?? DEFAULT_OPTIONS.sortKeys,
214
270
  strategy: resolveStrategy(options.strategy),
215
271
  yarnResolutions: options.yarnResolutions ?? DEFAULT_OPTIONS.yarnResolutions,
@@ -220,52 +276,104 @@ function resolveOptions(options = {}) {
220
276
  //#endregion
221
277
  //#region src/utils/fs.ts
222
278
  /**
223
- * Check if a path exists
224
- * @param path - given path
225
- * @returns `true` if exists, false otherwise
279
+ * Modulus that isolates Unix permission bits from a stat mode.
280
+ */
281
+ const FILE_PERMISSION_MODULUS = 512;
282
+ /**
283
+ * Resolve a filesystem path to a string suitable for a sibling temp file.
284
+ */
285
+ function resolvePathString(path) {
286
+ if (path instanceof URL) return fileURLToPath(path);
287
+ return typeof path === "string" ? path : path.toString();
288
+ }
289
+ /**
290
+ * Read an existing file's permission bits, if the file exists.
291
+ */
292
+ async function resolveExistingMode(path) {
293
+ try {
294
+ return (await stat(path)).mode % FILE_PERMISSION_MODULUS;
295
+ } catch (error) {
296
+ if (error.code === "ENOENT") return;
297
+ throw error;
298
+ }
299
+ }
300
+ /**
301
+ * Check whether a filesystem path exists.
302
+ *
303
+ * @param path - Filesystem path to check
304
+ *
305
+ * @returns `true` when the path is accessible, otherwise `false`
226
306
  */
227
307
  async function fsExists(path) {
228
308
  return access(path).then(() => true).catch(() => false);
229
309
  }
230
310
  /**
231
- * Read file content
232
- * @param path - file path
233
- * @returns content of given file
311
+ * Read a UTF-8 text file.
312
+ *
313
+ * @param path - Filesystem path to read
314
+ *
315
+ * @returns Text content of the file
234
316
  */
235
317
  async function fsReadFile(path) {
236
318
  return await readFile(path, "utf-8");
237
319
  }
238
320
  /**
239
321
  * Remove a file if it exists.
240
- * @param path - file path
322
+ *
323
+ * @param path - Filesystem path to remove
324
+ *
325
+ * @returns A promise that resolves after the file is removed
241
326
  */
242
327
  async function fsRemoveFile(path) {
243
328
  await rm(path, { force: true });
244
329
  }
245
330
  /**
246
- * Write file
247
- * @param path - file path
248
- * @param content - file content
331
+ * Write a normalized UTF-8 text file with one trailing newline.
332
+ *
333
+ * @param path - Filesystem path to write
334
+ * @param content - Text content to write
335
+ *
336
+ * @returns A promise that resolves after the file is written
249
337
  */
250
338
  async function fsWriteFile(path, content) {
251
- await writeFile(path, `${content.trimEnd()}\n`, "utf-8");
339
+ const pathString = resolvePathString(path);
340
+ const temporaryPath = join(dirname(pathString), `.${basename(pathString)}.${process.pid}.${randomUUID()}.tmp`);
341
+ const mode = await resolveExistingMode(path);
342
+ try {
343
+ await writeFile(temporaryPath, `${content.trimEnd()}\n`, {
344
+ encoding: "utf-8",
345
+ mode
346
+ });
347
+ if (mode !== void 0) await chmod(temporaryPath, mode);
348
+ await rename(temporaryPath, pathString);
349
+ } finally {
350
+ await rm(temporaryPath, { force: true });
351
+ }
252
352
  }
253
- getColor("cyan");
254
- getColor("yellow");
353
+ //#endregion
354
+ //#region src/utils/color.ts
355
+ /**
356
+ * Apply dim terminal styling to a value.
357
+ */
255
358
  const dim = getColor("dim");
359
+ /**
360
+ * Apply green terminal styling to a value.
361
+ */
256
362
  const green = getColor("green");
363
+ /**
364
+ * Apply red terminal styling to a value.
365
+ */
257
366
  const red = getColor("red");
367
+ /**
368
+ * Apply bold terminal styling to a value.
369
+ */
258
370
  const bold = getColor("bold");
371
+ /**
372
+ * Apply magenta terminal styling to a value.
373
+ */
259
374
  const magenta = getColor("magenta");
260
375
  //#endregion
261
- //#region src/utils/compatibility.ts
262
- const PNPM_REPLACEABLE_IN_V10_SETTINGS = [
263
- "allowNonAppliedPatches",
264
- "ignoredBuiltDependencies",
265
- "neverBuiltDependencies",
266
- "onlyBuiltDependencies",
267
- "onlyBuiltDependenciesFile"
268
- ];
376
+ //#region src/utils/legacy-build-settings.ts
269
377
  /**
270
378
  * Read packages listed by the legacy `onlyBuiltDependenciesFile` setting.
271
379
  */
@@ -277,18 +385,50 @@ async function readOnlyBuiltDependenciesFile(cwd, file) {
277
385
  return value;
278
386
  }
279
387
  /**
388
+ * Read and validate one legacy build dependency list. Configuration files are
389
+ * external input, so their runtime shape can differ from its declared type.
390
+ */
391
+ function readLegacyBuildDependencyList(settings, key) {
392
+ const value = settings[key];
393
+ if (value === void 0) return [];
394
+ if (!Array.isArray(value) || !value.every((item) => typeof item === "string")) {
395
+ const npmrcKey = key.replace(/[A-Z]/gu, (character) => `-${character.toLowerCase()}`);
396
+ throw new TypeError(`Invalid ${key}: expected an array of package names. In .npmrc, use ${npmrcKey}[]=<package>.`);
397
+ }
398
+ return value;
399
+ }
400
+ /**
280
401
  * Build an `allowBuilds` map from legacy build-script settings.
281
402
  */
282
403
  async function collectAllowBuildsFromLegacy(incomingSettings, cwd) {
283
404
  const allowBuilds = {};
405
+ const allowed = readLegacyBuildDependencyList(incomingSettings, "onlyBuiltDependencies");
406
+ const ignored = readLegacyBuildDependencyList(incomingSettings, "ignoredBuiltDependencies");
407
+ const neverBuilt = readLegacyBuildDependencyList(incomingSettings, "neverBuiltDependencies");
284
408
  const allowedFromFile = await readOnlyBuiltDependenciesFile(cwd, incomingSettings.onlyBuiltDependenciesFile);
285
- for (const name of [...incomingSettings.onlyBuiltDependencies || [], ...allowedFromFile]) allowBuilds[name] = true;
286
- for (const name of incomingSettings.ignoredBuiltDependencies || []) allowBuilds[name] = false;
287
- for (const name of incomingSettings.neverBuiltDependencies || []) allowBuilds[name] = false;
409
+ for (const name of [...allowed, ...allowedFromFile]) allowBuilds[name] = true;
410
+ for (const name of ignored) allowBuilds[name] = false;
411
+ for (const name of neverBuilt) allowBuilds[name] = false;
288
412
  return Object.keys(allowBuilds).length ? allowBuilds : void 0;
289
413
  }
414
+ //#endregion
415
+ //#region src/utils/compatibility.ts
416
+ /**
417
+ * Legacy build settings that can be replaced for pnpm v10.
418
+ */
419
+ const PNPM_REPLACEABLE_IN_V10_SETTINGS = [
420
+ "allowNonAppliedPatches",
421
+ "ignoredBuiltDependencies",
422
+ "neverBuiltDependencies",
423
+ "onlyBuiltDependencies",
424
+ "onlyBuiltDependenciesFile"
425
+ ];
290
426
  /**
291
427
  * Resolve the pnpm v11 replacement for legacy package-manager settings.
428
+ *
429
+ * @param settings - Settings containing legacy package-manager declarations
430
+ *
431
+ * @returns Resolved `pmOnFail` behavior, or `undefined` when not configured
292
432
  */
293
433
  function resolvePmOnFail(settings) {
294
434
  if (settings.pmOnFail) return settings.pmOnFail;
@@ -299,6 +439,11 @@ function resolvePmOnFail(settings) {
299
439
  }
300
440
  /**
301
441
  * Rename legacy audit settings while preserving values for manual ID updates.
442
+ *
443
+ * @param settings - Settings whose audit configuration may be normalized
444
+ * @param warnings - Warning collection updated with manual follow-up guidance
445
+ *
446
+ * @returns Nothing; the settings and warning collection are mutated in place
302
447
  */
303
448
  function normalizeAuditConfig(settings, warnings) {
304
449
  const { auditConfig } = settings;
@@ -310,6 +455,11 @@ function normalizeAuditConfig(settings, warnings) {
310
455
  }
311
456
  /**
312
457
  * Resolve a removed Node.js runtime setting for migration to `package.json`.
458
+ *
459
+ * @param settings - Settings containing legacy runtime declarations
460
+ * @param warnings - Warning collection updated when declarations conflict
461
+ *
462
+ * @returns Runtime version to migrate, or `undefined` when not configured
313
463
  */
314
464
  function resolveRuntimeVersion(settings, warnings) {
315
465
  const { useNodeVersion } = settings;
@@ -319,6 +469,11 @@ function resolveRuntimeVersion(settings, warnings) {
319
469
  }
320
470
  /**
321
471
  * Normalize settings according to the selected compatibility target.
472
+ *
473
+ * @param incomingSettings - Settings to normalize in place
474
+ * @param options - Compatibility target and normalization context
475
+ *
476
+ * @returns Normalization result, extracted runtime version, and warnings
322
477
  */
323
478
  async function normalizeIncomingSettings(incomingSettings, options) {
324
479
  const { compatibility, cwd, replaceDeprecated } = options;
@@ -354,8 +509,15 @@ async function normalizeIncomingSettings(incomingSettings, options) {
354
509
  warnings
355
510
  };
356
511
  }
512
+ //#endregion
513
+ //#region src/utils/compatibility-runtime.ts
357
514
  /**
358
515
  * Move a removed Node.js runtime setting to `package.json#devEngines.runtime`.
516
+ *
517
+ * @param packageJson - Package manifest to update in place
518
+ * @param runtimeVersion - Node.js runtime version extracted from pnpm settings
519
+ *
520
+ * @returns Migration result indicating whether the manifest changed
359
521
  */
360
522
  function migrateRuntimeToPackageJson(packageJson, runtimeVersion) {
361
523
  if (!runtimeVersion) return { changed: false };
@@ -374,6 +536,12 @@ function migrateRuntimeToPackageJson(packageJson, runtimeVersion) {
374
536
  }
375
537
  /**
376
538
  * Select the runtime setting that wins under the configured merge strategy.
539
+ *
540
+ * @param existingVersion - Runtime version from existing workspace settings
541
+ * @param incomingVersion - Runtime version from incoming settings
542
+ * @param strategy - Merge strategy controlling value precedence
543
+ *
544
+ * @returns Selected runtime version, or `undefined` when neither source has one
377
545
  */
378
546
  function resolveRuntimeVersionByStrategy(existingVersion, incomingVersion, strategy) {
379
547
  if (strategy === "overwrite") return incomingVersion ?? existingVersion;
@@ -381,17 +549,44 @@ function resolveRuntimeVersionByStrategy(existingVersion, incomingVersion, strat
381
549
  }
382
550
  //#endregion
383
551
  //#region src/utils/compatibility-target.ts
552
+ /**
553
+ * pnpm major version that introduced the v11 settings schema.
554
+ */
384
555
  const PNPM_V11_MAJOR = 11;
556
+ /**
557
+ * pnpm major version that introduced the v12 compatibility target.
558
+ */
385
559
  const PNPM_V12_MAJOR = 12;
386
560
  /**
561
+ * Resolve a pnpm major from a package manager declaration.
562
+ */
563
+ function resolvePnpmMajor(hint) {
564
+ const match = hint?.match(/^pnpm@[\s<=>^~]*(?<major>\d+)(?:\.|\s|$)/u);
565
+ return match?.groups?.major ? Number(match.groups.major) : void 0;
566
+ }
567
+ /**
568
+ * Normalize the single-or-array devEngines declaration.
569
+ */
570
+ function normalizePackageManagers(value) {
571
+ if (Array.isArray(value)) return value;
572
+ return value ? [value] : [];
573
+ }
574
+ /**
387
575
  * Resolve final compatibility target from user option and package manager hint.
576
+ *
577
+ * @param compatibility - Explicit or automatically detected compatibility target
578
+ * @param packageManager - `packageManager` declaration from `package.json`
579
+ * @param devPackageManager - Package manager declaration from `devEngines`
580
+ *
581
+ * @returns Resolved concrete compatibility target
388
582
  */
389
583
  function resolveCompatibilityTarget(compatibility, packageManager, devPackageManager) {
390
584
  if (compatibility !== "auto") return compatibility;
391
- const match = (packageManager || (devPackageManager?.name === "pnpm" ? `pnpm@${devPackageManager.version}` : void 0))?.match(/^pnpm@[\s<=>^~]*(?<major>\d+)(?:\.|\s|$)/u);
392
- const major = Number(match?.groups?.major);
393
- if (major >= PNPM_V12_MAJOR) return "v12";
394
- return major >= PNPM_V11_MAJOR ? "v11" : "v10";
585
+ const packageManagerMajor = resolvePnpmMajor(packageManager);
586
+ const devPackageManagerMajors = normalizePackageManagers(devPackageManager).filter((item) => item.name === "pnpm").map((item) => resolvePnpmMajor(`pnpm@${item.version ?? ""}`)).filter((major) => major !== void 0);
587
+ const major = packageManagerMajor ?? (devPackageManagerMajors.length ? Math.max(...devPackageManagerMajors) : void 0);
588
+ if (major !== void 0 && major >= PNPM_V12_MAJOR) return "v12";
589
+ return major !== void 0 && major >= PNPM_V11_MAJOR ? "v11" : "v10";
395
590
  }
396
591
  //#endregion
397
592
  //#region src/utils/merge.ts
@@ -470,6 +665,9 @@ function mergeByStrategy(existing, incoming, strategy) {
470
665
  }
471
666
  //#endregion
472
667
  //#region src/utils/npmrc.ts
668
+ /**
669
+ * Authentication and registry keys that must remain in `.npmrc`.
670
+ */
473
671
  const NPMRC_AUTH_OR_REGISTRY_KEYS = [
474
672
  "_auth",
475
673
  "_authtoken",
@@ -486,15 +684,26 @@ const NPMRC_AUTH_OR_REGISTRY_KEYS = [
486
684
  "tokenhelper",
487
685
  "username"
488
686
  ];
687
+ /**
688
+ * Pattern for pnpm's channel-specific Node.js mirror keys.
689
+ */
489
690
  const NODE_MIRROR_KEY_PATTERN = /^node-mirror:(?<channel>.+)$/iu;
490
691
  /**
491
692
  * Normalize `.npmrc` key for case-insensitive matching.
693
+ *
694
+ * @param key - Raw `.npmrc` key
695
+ *
696
+ * @returns Trimmed lower-case key without an array suffix
492
697
  */
493
698
  function normalizeNpmrcKey(key) {
494
699
  return key.trim().replace(/\[\]$/u, "").toLowerCase();
495
700
  }
496
701
  /**
497
702
  * Extract key name from a raw `.npmrc` line.
703
+ *
704
+ * @param line - Raw line from an `.npmrc` file
705
+ *
706
+ * @returns Normalized key, or `undefined` for comments and invalid lines
498
707
  */
499
708
  function getNpmrcLineKey(line) {
500
709
  const trimmed = line.trim();
@@ -506,6 +715,10 @@ function getNpmrcLineKey(line) {
506
715
  /**
507
716
  * Check whether a `.npmrc` key is auth/registry-related and should stay in
508
717
  * pnpm v11 and newer.
718
+ *
719
+ * @param key - Raw or normalized `.npmrc` key
720
+ *
721
+ * @returns `true` when the key must remain in `.npmrc`
509
722
  */
510
723
  function isNpmrcAuthOrRegistryKey(key) {
511
724
  const normalized = normalizeNpmrcKey(key);
@@ -516,11 +729,12 @@ function isNpmrcAuthOrRegistryKey(key) {
516
729
  /**
517
730
  * Remove pnpm-related settings from `.npmrc` file.
518
731
  *
519
- * This function reads the `.npmrc` file, filters out all lines that start with
520
- * pnpm-specific configuration keys (as defined in PNPM_SETTINGS_FIELDS), and
521
- * writes the cleaned content back to the file.
732
+ * This function reads the `.npmrc` file, removes lines whose exact normalized
733
+ * keys were migrated, and writes the cleaned content back to the file.
522
734
  *
523
735
  * @param path - Absolute path to the `.npmrc` file
736
+ * @param compatibility - Concrete pnpm compatibility target
737
+ * @param migratedKeys - Original `.npmrc` keys that were migrated
524
738
  *
525
739
  * @returns A promise that resolves when the file has been pruned
526
740
  *
@@ -528,17 +742,15 @@ function isNpmrcAuthOrRegistryKey(key) {
528
742
  *
529
743
  * @example
530
744
  * ```ts
531
- * await pruneNpmrc('/path/to/.npmrc')
745
+ * await pruneNpmrc('/path/to/.npmrc', 'v10', ['node-linker'])
532
746
  * ```
533
747
  */
534
- async function pruneNpmrc(path, compatibility = "v10", migratedKeys = []) {
535
- const pnpmSettingsFields = PNPM_SETTINGS_FIELDS.map((field) => kebabCase(field));
748
+ async function pruneNpmrc(path, compatibility, migratedKeys) {
536
749
  const migratedKeySet = new Set(migratedKeys.map(normalizeNpmrcKey));
537
750
  const updatedContent = (await fsReadFile(path)).split(/\r?\n/u).filter((line) => {
538
751
  const key = getNpmrcLineKey(line);
539
752
  if (!key) return true;
540
- if (compatibility !== "v10") return !migratedKeySet.has(normalizeNpmrcKey(key));
541
- return !pnpmSettingsFields.some((field) => key.startsWith(field));
753
+ return !migratedKeySet.has(key);
542
754
  }).join("\n").trimEnd();
543
755
  if (compatibility !== "v10" && !updatedContent.trim()) {
544
756
  await fsRemoveFile(path);
@@ -547,38 +759,28 @@ async function pruneNpmrc(path, compatibility = "v10", migratedKeys = []) {
547
759
  await fsWriteFile(path, updatedContent);
548
760
  }
549
761
  /**
550
- * Read and parse `.npmrc` file with camelCase key conversion.
762
+ * Read `.npmrc` and return settings that should be migrated into workspace config.
551
763
  *
552
- * This function reads an `.npmrc` INI-format file and parses it into an object.
553
- * All keys are automatically converted from kebab-case to camelCase for easier
554
- * JavaScript consumption (e.g., `allow-builds` → `allowBuilds`).
764
+ * - `v10`: returns settings from the legacy whitelist.
765
+ * - `v11+`: excludes auth/registry keys because pnpm still reads them from
766
+ * `.npmrc`.
555
767
  *
556
768
  * @param path - Absolute path to the `.npmrc` file
769
+ * @param compatibility - Concrete pnpm compatibility target
557
770
  *
558
- * @returns A promise that resolves to the parsed `.npmrc` configuration object with camelCase keys
771
+ * @returns Migratable settings and their original `.npmrc` keys
559
772
  *
560
773
  * @throws {Error} When file reading or INI parsing fails
561
- *
562
- * @example
563
- * ```ts
564
- * const config = await readNpmrc('/path/to/.npmrc')
565
- * // config.allowBuilds, config.packageExtensions, etc.
566
- * ```
567
- */
568
- /**
569
- * Read `.npmrc` and return settings that should be migrated into workspace config.
570
- *
571
- * - `v10`: returns settings from the legacy whitelist.
572
- * - `v11+`: excludes auth/registry keys because pnpm still reads them from
573
- * `.npmrc`.
574
774
  */
575
775
  async function readMigratableNpmrc(path, compatibility) {
576
776
  const raw = await readIniFile(path);
577
777
  if (compatibility === "v10") {
578
- const settings = camelcaseKeys(raw);
778
+ const pnpmSettingsFields = new Set(PNPM_V10_NPMRC_SETTINGS_FIELDS.map((field) => normalizeNpmrcKey(kebabCase(field))));
779
+ const keys = Object.keys(raw).filter((key) => pnpmSettingsFields.has(normalizeNpmrcKey(key)));
780
+ const migratable = Object.fromEntries(keys.map((key) => [key, raw[key]]));
579
781
  return {
580
- keys: PNPM_SETTINGS_FIELDS,
581
- settings: pick(settings, PNPM_SETTINGS_FIELDS)
782
+ keys,
783
+ settings: camelcaseKeys(migratable)
582
784
  };
583
785
  }
584
786
  const migratable = {};
@@ -598,11 +800,225 @@ async function readMigratableNpmrc(path, compatibility) {
598
800
  };
599
801
  }
600
802
  //#endregion
803
+ //#region src/utils/settings-change.ts
804
+ /**
805
+ * Maximum dynamic-programming cells used for an exact line diff.
806
+ */
807
+ const MAX_LCS_CELLS = 1e6;
808
+ /**
809
+ * URL userinfo can contain proxy usernames and passwords. It must never be
810
+ * printed in a migration report.
811
+ */
812
+ const URL_USERINFO_PATTERN = /(?<scheme>[a-z][a-z\d+.-]*:\/\/)[^/\s@]+@/giu;
813
+ /**
814
+ * Collect changed root settings between two workspace configurations.
815
+ *
816
+ * @param before - Workspace settings before migration
817
+ * @param after - Workspace settings after migration
818
+ *
819
+ * @returns Changed root settings with their before and after values
820
+ */
821
+ function collectSettingsChanges(before, after) {
822
+ const beforeSettings = { ...before };
823
+ const afterSettings = { ...after };
824
+ const keys = /* @__PURE__ */ new Set([...Object.keys(beforeSettings), ...Object.keys(afterSettings)]);
825
+ return Array.from(keys).filter((key) => !isDeepStrictEqual(beforeSettings[key], afterSettings[key])).map((key) => ({
826
+ after: afterSettings[key],
827
+ before: beforeSettings[key],
828
+ key
829
+ }));
830
+ }
831
+ /**
832
+ * Create a bounded-memory diff that retains shared prefixes and suffixes.
833
+ *
834
+ * This fallback deliberately gives up the exact LCS for unusually large
835
+ * settings to keep reporting from exhausting memory after migration writes.
836
+ */
837
+ function createLinearLineDiff(beforeLines, afterLines) {
838
+ let prefixLength = 0;
839
+ const maximumPrefixLength = Math.min(beforeLines.length, afterLines.length);
840
+ while (prefixLength < maximumPrefixLength && beforeLines[prefixLength] === afterLines[prefixLength]) prefixLength++;
841
+ let suffixLength = 0;
842
+ const maximumSuffixLength = maximumPrefixLength - prefixLength;
843
+ while (suffixLength < maximumSuffixLength && beforeLines[beforeLines.length - suffixLength - 1] === afterLines[afterLines.length - suffixLength - 1]) suffixLength++;
844
+ return [
845
+ ...beforeLines.slice(0, prefixLength).map((value) => ({
846
+ kind: "unchanged",
847
+ value
848
+ })),
849
+ ...beforeLines.slice(prefixLength, beforeLines.length - suffixLength).map((value) => ({
850
+ kind: "removed",
851
+ value
852
+ })),
853
+ ...afterLines.slice(prefixLength, afterLines.length - suffixLength).map((value) => ({
854
+ kind: "added",
855
+ value
856
+ })),
857
+ ...beforeLines.slice(beforeLines.length - suffixLength).map((value) => ({
858
+ kind: "unchanged",
859
+ value
860
+ }))
861
+ ];
862
+ }
863
+ /**
864
+ * Create a line-based diff using the longest common subsequence.
865
+ *
866
+ * @param beforeLines - YAML lines before migration
867
+ * @param afterLines - YAML lines after migration
868
+ *
869
+ * @returns Classified diff lines in display order
870
+ */
871
+ function createLineDiff(beforeLines, afterLines) {
872
+ if (beforeLines.length * afterLines.length > MAX_LCS_CELLS) return createLinearLineDiff(beforeLines, afterLines);
873
+ const lengths = Array.from({ length: beforeLines.length + 1 }, () => Array(afterLines.length + 1).fill(0));
874
+ for (let beforeIndex = beforeLines.length - 1; beforeIndex >= 0; beforeIndex--) for (let afterIndex = afterLines.length - 1; afterIndex >= 0; afterIndex--) lengths[beforeIndex][afterIndex] = beforeLines[beforeIndex] === afterLines[afterIndex] ? lengths[beforeIndex + 1][afterIndex + 1] + 1 : Math.max(lengths[beforeIndex + 1][afterIndex], lengths[beforeIndex][afterIndex + 1]);
875
+ const diff = [];
876
+ let beforeIndex = 0;
877
+ let afterIndex = 0;
878
+ while (beforeIndex < beforeLines.length && afterIndex < afterLines.length) if (beforeLines[beforeIndex] === afterLines[afterIndex]) {
879
+ diff.push({
880
+ kind: "unchanged",
881
+ value: beforeLines[beforeIndex]
882
+ });
883
+ beforeIndex++;
884
+ afterIndex++;
885
+ } else if (lengths[beforeIndex + 1][afterIndex] >= lengths[beforeIndex][afterIndex + 1]) {
886
+ diff.push({
887
+ kind: "removed",
888
+ value: beforeLines[beforeIndex]
889
+ });
890
+ beforeIndex++;
891
+ } else {
892
+ diff.push({
893
+ kind: "added",
894
+ value: afterLines[afterIndex]
895
+ });
896
+ afterIndex++;
897
+ }
898
+ while (beforeIndex < beforeLines.length) {
899
+ diff.push({
900
+ kind: "removed",
901
+ value: beforeLines[beforeIndex]
902
+ });
903
+ beforeIndex++;
904
+ }
905
+ while (afterIndex < afterLines.length) {
906
+ diff.push({
907
+ kind: "added",
908
+ value: afterLines[afterIndex]
909
+ });
910
+ afterIndex++;
911
+ }
912
+ return diff;
913
+ }
914
+ /**
915
+ * Format one root setting as YAML lines.
916
+ *
917
+ * @param key - Root setting key
918
+ * @param value - Setting value, or `undefined` when the key is absent
919
+ *
920
+ * @returns YAML lines for the setting, or an empty array when absent
921
+ */
922
+ function formatSettingLines(key, value) {
923
+ if (value === void 0) return [];
924
+ return stringify({ [key]: value }).trimEnd().split("\n");
925
+ }
926
+ /**
927
+ * Remove credentials embedded in URLs while preserving enough context to
928
+ * identify the changed setting.
929
+ *
930
+ * @param value - Rendered YAML diff line
931
+ *
932
+ * @returns Diff line safe to print to a terminal or CI log
933
+ */
934
+ function redactUrlCredentials(value) {
935
+ return value.replace(URL_USERINFO_PATTERN, "$<scheme>***@");
936
+ }
937
+ /**
938
+ * Format a setting change as a GitHub-style YAML diff.
939
+ *
940
+ * @param change - Root setting change to format
941
+ *
942
+ * @returns Classified YAML diff lines
943
+ */
944
+ function createSettingsDiffLines(change) {
945
+ return createLineDiff(formatSettingLines(change.key, change.before), formatSettingLines(change.key, change.after)).map((line) => ({
946
+ kind: line.kind,
947
+ value: redactUrlCredentials(line.value)
948
+ }));
949
+ }
950
+ /**
951
+ * Print a summary and GitHub-style YAML diff for every changed setting.
952
+ *
953
+ * @param changes - Root setting changes to report
954
+ *
955
+ * @returns Nothing; output is written through consola
956
+ */
957
+ function reportSettingsChanges(changes) {
958
+ consola$1.info(`${changes.length} ${changes.length === 1 ? "setting" : "settings"} changed`);
959
+ for (const [index, change] of changes.entries()) {
960
+ if (index > 0) consola$1.log("");
961
+ for (const line of createSettingsDiffLines(change)) if (line.kind === "added") consola$1.log(green(`+ ${line.value}`));
962
+ else if (line.kind === "removed") consola$1.log(red(`- ${line.value}`));
963
+ else consola$1.log(dim(` ${line.value}`));
964
+ }
965
+ }
966
+ //#endregion
967
+ //#region src/utils/yaml-document.ts
968
+ /**
969
+ * Sort every mapping in a YAML node without replacing its comments or anchors.
970
+ */
971
+ function sortYamlMappings(node) {
972
+ if (isMap(node)) {
973
+ node.items.sort((left, right) => {
974
+ const leftKey = isScalar(left.key) ? String(left.key.value) : "";
975
+ const rightKey = isScalar(right.key) ? String(right.key.value) : "";
976
+ return leftKey.localeCompare(rightKey);
977
+ });
978
+ for (const pair of node.items) if (isNode(pair.value)) sortYamlMappings(pair.value);
979
+ } else if (isSeq(node)) {
980
+ for (const item of node.items) if (isNode(item)) sortYamlMappings(item);
981
+ }
982
+ }
983
+ /**
984
+ * Apply semantic root changes to the parsed document while retaining untouched
985
+ * YAML nodes and their source metadata.
986
+ */
987
+ function updateYamlDocument(document, options) {
988
+ const { after, before, sortKeys } = options;
989
+ for (const key of Object.keys(before)) if (!Object.hasOwn(after, key)) document.delete(key);
990
+ for (const [key, value] of Object.entries(after)) if (!Object.hasOwn(before, key) || !isDeepStrictEqual(Reflect.get(before, key), value)) document.set(key, value);
991
+ if (sortKeys) sortYamlMappings(document.contents);
992
+ }
993
+ /**
994
+ * Normalize blank lines immediately before root mapping keys.
995
+ */
996
+ function formatRootSpacing(content, newlineBetween) {
997
+ return content.replace(/\n+(?=[^\s#][^:\n]*:)/gu, newlineBetween ? "\n\n" : "\n");
998
+ }
999
+ //#endregion
601
1000
  //#region src/utils/config.ts
1001
+ /**
1002
+ * Resolve the indentation width for a YAML document.
1003
+ *
1004
+ * @param content - Raw YAML content
1005
+ *
1006
+ * @returns Detected indentation width or the project default
1007
+ */
602
1008
  function resolveYamlIndent(content) {
603
1009
  const detectedIndent = detectIndent(content).amount;
604
1010
  return detectedIndent > 0 ? detectedIndent : 2;
605
1011
  }
1012
+ /**
1013
+ * Read and parse a package manifest when it exists.
1014
+ *
1015
+ * @param path - Absolute path to `package.json`
1016
+ * @param exists - Whether the package manifest exists
1017
+ *
1018
+ * @returns Parsed package manifest and detected indentation
1019
+ *
1020
+ * @throws {SyntaxError} When the package manifest contains invalid JSON
1021
+ */
606
1022
  async function readPackageJson(path, exists) {
607
1023
  if (!exists) return {
608
1024
  indent: 2,
@@ -614,17 +1030,46 @@ async function readPackageJson(path, exists) {
614
1030
  value: JSON.parse(content)
615
1031
  };
616
1032
  }
1033
+ /**
1034
+ * Read and parse a pnpm workspace manifest when it exists.
1035
+ *
1036
+ * @param path - Absolute path to `pnpm-workspace.yaml`
1037
+ * @param exists - Whether the workspace manifest exists
1038
+ *
1039
+ * @returns Parsed workspace settings and detected indentation
1040
+ *
1041
+ * @throws {Error} When the workspace manifest contains invalid YAML
1042
+ */
617
1043
  async function readPnpmWorkspace(path, exists) {
618
1044
  if (!exists) return {
1045
+ document: new Document({}),
619
1046
  indent: 2,
620
1047
  value: {}
621
1048
  };
622
1049
  const content = await fsReadFile(path);
1050
+ const document = parseDocument(content);
1051
+ const [parseError] = document.errors;
1052
+ if (parseError) throw parseError;
1053
+ const parsedValue = document.toJS();
1054
+ if (parsedValue !== null && !isMap(document.contents)) throw new TypeError("pnpm-workspace.yaml must contain a root mapping.");
1055
+ if (parsedValue === null) document.contents = document.createNode({});
623
1056
  return {
1057
+ document,
624
1058
  indent: resolveYamlIndent(content),
625
- value: parse(content) ?? {}
1059
+ value: parsedValue ?? {}
626
1060
  };
627
1061
  }
1062
+ /**
1063
+ * Resolve migratable pnpm settings from a package manifest.
1064
+ *
1065
+ * Yarn resolutions are merged into pnpm overrides when enabled. Empty
1066
+ * overrides are removed from the result.
1067
+ *
1068
+ * @param packageJson - Parsed package manifest
1069
+ * @param yarnResolutions - Whether to convert Yarn resolutions to pnpm overrides
1070
+ *
1071
+ * @returns Pnpm workspace settings resolved from the package manifest
1072
+ */
628
1073
  function resolvePackageJsonSettings(packageJson, yarnResolutions) {
629
1074
  const pnpmSettings = yarnResolutions && packageJson.resolutions ? {
630
1075
  ...packageJson.pnpm,
@@ -649,6 +1094,10 @@ function hasMigratableSettings(sources) {
649
1094
  function assertCanMigrateRuntime(runtimeVersion, packageJsonExists) {
650
1095
  if (runtimeVersion && !packageJsonExists) throw new Error("Cannot migrate the removed Node.js runtime setting without a package.json file.");
651
1096
  }
1097
+ function reportMigrationChanges(showChanges, before = {}, after = {}) {
1098
+ if (!showChanges) return;
1099
+ reportSettingsChanges(collectSettingsChanges(before, after));
1100
+ }
652
1101
  /**
653
1102
  * Migrate pnpm settings from legacy locations to `pnpm-workspace.yaml`.
654
1103
  *
@@ -666,6 +1115,7 @@ function assertCanMigrateRuntime(runtimeVersion, packageJsonExists) {
666
1115
  * @param rawOptions.yarnResolutions - Whether to migrate resolutions field (default: true)
667
1116
  * @param rawOptions.sortKeys - Whether to sort keys in output YAML (default: false)
668
1117
  * @param rawOptions.newlineBetween - Add newlines between root keys (default: true)
1118
+ * @param rawOptions.showChanges - Show settings changes after migration (default: true)
669
1119
  *
670
1120
  * @returns A promise that resolves when migration is complete
671
1121
  *
@@ -695,19 +1145,19 @@ async function migratePnpmSettings(rawOptions = {}) {
695
1145
  fsExists(packageJsonPath),
696
1146
  fsExists(pnpmWorkspaceYamlPath)
697
1147
  ]);
698
- if (!hasSettingsSources(npmrcExists, packageJsonExists, pnpmWorkspaceExists)) return;
1148
+ if (!hasSettingsSources(npmrcExists, packageJsonExists, pnpmWorkspaceExists)) {
1149
+ reportMigrationChanges(options.showChanges);
1150
+ return;
1151
+ }
699
1152
  const [packageJson, pnpmWorkspace] = await Promise.all([readPackageJson(packageJsonPath, packageJsonExists), readPnpmWorkspace(pnpmWorkspaceYamlPath, pnpmWorkspaceExists)]);
1153
+ const pnpmWorkspaceBefore = structuredClone(pnpmWorkspace.value);
700
1154
  const compatibility = resolveCompatibilityTarget(options.compatibility, packageJson.value.packageManager, packageJson.value.devEngines?.packageManager);
701
1155
  const npmrcMigratable = npmrcExists ? await readMigratableNpmrc(npmrcPath, compatibility) : {
702
1156
  keys: [],
703
1157
  settings: {}
704
1158
  };
705
1159
  const pnpmSettingsInNpmrc = npmrcMigratable.settings;
706
- const pnpmSettingsInPackageJson = resolvePackageJsonSettings(packageJson.value, options.yarnResolutions);
707
- const incomingSettings = {
708
- ...pnpmSettingsInNpmrc,
709
- ...pnpmSettingsInPackageJson
710
- };
1160
+ const incomingSettings = mergeByStrategy(resolvePackageJsonSettings(packageJson.value, options.yarnResolutions), pnpmSettingsInNpmrc, "merge");
711
1161
  const [existingNormalization, incomingNormalization] = await Promise.all([normalizeIncomingSettings(pnpmWorkspace.value, {
712
1162
  compatibility,
713
1163
  cwd: options.cwd,
@@ -729,19 +1179,16 @@ async function migratePnpmSettings(rawOptions = {}) {
729
1179
  yarnResolutions: options.yarnResolutions
730
1180
  })) {
731
1181
  consola$1.warn("No pnpm settings fields to migrate");
1182
+ reportMigrationChanges(options.showChanges);
732
1183
  return;
733
1184
  }
734
1185
  const pnpmWorkspaceResult = mergeByStrategy(pnpmWorkspace.value, incomingSettings, options.strategy);
735
- const yamlDocument = new Document({}, { sortMapEntries: options.sortKeys });
736
- Object.entries(pnpmWorkspaceResult).forEach(([key, value]) => {
737
- yamlDocument.add({
738
- key,
739
- value
740
- });
1186
+ updateYamlDocument(pnpmWorkspace.document, {
1187
+ after: pnpmWorkspaceResult,
1188
+ before: pnpmWorkspaceBefore,
1189
+ sortKeys: options.sortKeys
741
1190
  });
742
- const yamlContent = yamlDocument.toString({ indent: pnpmWorkspace.indent });
743
- await fsWriteFile(pnpmWorkspaceYamlPath, options.newlineBetween ? yamlContent.replace(/\n(?=[^\s#][^:\n]*:)/gu, "\n\n") : yamlContent);
744
- if (npmrcExists && options.cleanNpmrc) await pruneNpmrc(npmrcPath, compatibility, npmrcMigratable.keys);
1191
+ const finalYamlContent = formatRootSpacing(pnpmWorkspace.document.toString({ indent: pnpmWorkspace.indent }), options.newlineBetween);
745
1192
  let packageJsonChanged = runtimeMigration.changed;
746
1193
  if (packageJsonExists && options.cleanPackageJson) {
747
1194
  if (packageJson.value.pnpm) packageJsonChanged = true;
@@ -751,7 +1198,10 @@ async function migratePnpmSettings(rawOptions = {}) {
751
1198
  delete packageJson.value.resolutions;
752
1199
  }
753
1200
  }
1201
+ await fsWriteFile(pnpmWorkspaceYamlPath, finalYamlContent);
754
1202
  if (packageJsonExists && packageJsonChanged) await fsWriteFile(packageJsonPath, JSON.stringify(packageJson.value, null, packageJson.indent));
1203
+ if (npmrcExists && options.cleanNpmrc) await pruneNpmrc(npmrcPath, compatibility, npmrcMigratable.keys);
1204
+ reportMigrationChanges(options.showChanges, pnpmWorkspaceBefore, pnpmWorkspaceResult);
755
1205
  } catch (err) {
756
1206
  consola$1.error("Failed to migrate pnpm settings:", err);
757
1207
  throw err;
@@ -759,9 +1209,8 @@ async function migratePnpmSettings(rawOptions = {}) {
759
1209
  }
760
1210
  //#endregion
761
1211
  //#region src/cli.ts
762
- const cli = cac(name);
763
- cli.version(version).option("--cwd [cwd]", "Current working directory").option("--sort-keys", "Sort keys when write pnpm-workspace.yaml").option("--compatibility <compatibility>", "Compatibility target (auto, v10, v11, v12)").option("--replace-deprecated", "Replace deprecated pnpm settings with new ones and remove old keys").option("--strategy <strategy>", "Strategy to handle conflicts (discard, merge, overwrite)").option("--no-yarn-resolutions", "Disable migrating resolutions field in package.json").option("--no-newline-between", "Disable adding newlines between each root keys").option("--no-clean-npmrc", "Disable removing pnpm settings in .npmrc file").option("--no-clean-package-json", "Disable removing pnpm fields in package.json").help();
764
- cli.command("").action(async (options) => {
1212
+ const cli = createCli();
1213
+ cli.command("").usage("[options]").action(async (options) => {
765
1214
  try {
766
1215
  consola.log(`\n${bold(magenta(name))} ${dim(`v${version}`)}`);
767
1216
  consola.log(dim("\n--------------\n"));