pnpm-settings-migrator 0.0.8 → 0.1.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 CHANGED
@@ -45,14 +45,14 @@ Disable migrating `resolutions` field in `package.json`.
45
45
 
46
46
  Disable removing pnpm settings in `.npmrc` file.
47
47
 
48
- ### `---no-clean-package-json`
48
+ ### `--no-clean-package-json`
49
49
 
50
50
  - **Type**: `boolean`
51
51
  - **Default**: `false`
52
52
 
53
53
  Disable removing `pnpm` field in `package.json`.
54
54
 
55
- ### `---no-newline-between`
55
+ ### `--no-newline-between`
56
56
 
57
57
  - **Type**: `boolean`
58
58
  - **Default**: `false`
package/dist/cli.mjs CHANGED
@@ -1,45 +1,309 @@
1
- import process from 'node:process';
2
- import { cac } from 'cac';
3
- import { consola } from 'consola';
4
- import { b as bold, d as dim, a as magenta, m as migratePnpmSettings, g as green, c as red } from './shared/pnpm-settings-migrator.Cwv3sjIn.mjs';
5
- import '@ntnyq/utils';
6
- import 'defu';
7
- import 'detect-indent';
8
- import 'pathe';
9
- import 'yaml';
10
- import 'node:fs/promises';
11
- import 'consola/utils';
12
- import 'camelcase-keys';
13
- import 'read-ini-file';
14
- import 'uncase';
15
-
16
- const name = "pnpm-settings-migrator";
17
- const version = "0.0.8";
1
+ import process from "node:process";
2
+ import { cac } from "cac";
3
+ import consola, { consola as consola$1 } from "consola";
4
+ import { pick } from "@ntnyq/utils";
5
+ import { defu } from "defu";
6
+ import detectIndent from "detect-indent";
7
+ import { resolve } from "pathe";
8
+ import { Document, parse } from "yaml";
9
+ import { access, readFile, writeFile } from "node:fs/promises";
10
+ import { getColor } from "consola/utils";
11
+ import camelcaseKeys from "camelcase-keys";
12
+ import { readIniFile } from "read-ini-file";
13
+ import { kebabCase } from "uncase";
18
14
 
15
+ //#region package.json
16
+ var name = "pnpm-settings-migrator";
17
+ var version = "0.1.0";
18
+
19
+ //#endregion
20
+ //#region src/constants.ts
21
+ const NPMRC = ".npmrc";
22
+ const PACKAGE_JSON = "package.json";
23
+ const PNPM_WORKSPACE_YAML = "pnpm-workspace.yaml";
24
+ /**
25
+ * Default indent: 2 spaces
26
+ */
27
+ const DEFAULT_INDENT = 2;
28
+ /**
29
+ * @see {@link https://github.com/pnpm/pnpm/blob/main/packages/types/src/package.ts}
30
+ */
31
+ const PNPM_SETTINGS_FIELDS = [
32
+ "allowBuilds",
33
+ "allowedDeprecatedVersions",
34
+ "allowNonAppliedPatches",
35
+ "allowUnusedPatches",
36
+ "auditConfig",
37
+ "configDependencies",
38
+ "executionEnv",
39
+ "ignoredBuiltDependencies",
40
+ "ignoredOptionalDependencies",
41
+ "ignorePatchFailures",
42
+ "neverBuiltDependencies",
43
+ "onlyBuiltDependencies",
44
+ "onlyBuiltDependenciesFile",
45
+ "overrides",
46
+ "packageExtensions",
47
+ "patchedDependencies",
48
+ "peerDependencyRules",
49
+ "requiredScripts",
50
+ "supportedArchitectures",
51
+ "updateConfig"
52
+ ];
53
+
54
+ //#endregion
55
+ //#region src/options.ts
56
+ /**
57
+ * Default values for migration options.
58
+ */
59
+ const DEFAULT_OPTIONS = {
60
+ cleanNpmrc: true,
61
+ cleanPackageJson: true,
62
+ cwd: process.cwd(),
63
+ newlineBetween: true,
64
+ sortKeys: false,
65
+ strategy: "merge",
66
+ yarnResolutions: true
67
+ };
68
+ /**
69
+ * Resolve and normalize migration options with defaults.
70
+ *
71
+ * This function takes partial options and returns a complete options object
72
+ * with all properties set to either the provided value or the default value.
73
+ *
74
+ * @param options - Partial migration options
75
+ *
76
+ * @returns Complete options object with all required properties
77
+ *
78
+ * @example
79
+ * ```ts
80
+ * // Use all defaults
81
+ * const opts = resolveOptions()
82
+ * // { cleanNpmrc: true, cleanPackageJson: true, cwd: '/current/dir', ... }
83
+ *
84
+ * // Override specific options
85
+ * const opts = resolveOptions({ sortKeys: true, cleanNpmrc: false })
86
+ * // { cleanNpmrc: false, cleanPackageJson: true, sortKeys: true, ... }
87
+ * ```
88
+ */
89
+ function resolveOptions(options = {}) {
90
+ return {
91
+ cleanNpmrc: options.cleanNpmrc ?? DEFAULT_OPTIONS.cleanNpmrc,
92
+ cwd: options.cwd ?? DEFAULT_OPTIONS.cwd,
93
+ newlineBetween: options.newlineBetween ?? DEFAULT_OPTIONS.newlineBetween,
94
+ sortKeys: options.sortKeys ?? DEFAULT_OPTIONS.sortKeys,
95
+ strategy: options.strategy ?? DEFAULT_OPTIONS.strategy,
96
+ yarnResolutions: options.yarnResolutions ?? DEFAULT_OPTIONS.yarnResolutions,
97
+ cleanPackageJson: options.cleanPackageJson ?? DEFAULT_OPTIONS.cleanPackageJson
98
+ };
99
+ }
100
+
101
+ //#endregion
102
+ //#region src/utils/fs.ts
103
+ /**
104
+ * Check if a path exists
105
+ * @param path - given path
106
+ * @returns `true` if exists, false otherwise
107
+ */
108
+ async function fsExists(path) {
109
+ return access(path).then(() => true).catch(() => false);
110
+ }
111
+ /**
112
+ * Read file content
113
+ * @param path - file path
114
+ * @returns content of given file
115
+ */
116
+ async function fsReadFile(path) {
117
+ return await readFile(path, "utf-8");
118
+ }
119
+ /**
120
+ * Write file
121
+ * @param path - file path
122
+ * @param content - file content
123
+ */
124
+ async function fsWriteFile(path, content) {
125
+ await writeFile(path, `${content.trimEnd()}\n`, "utf-8");
126
+ }
127
+
128
+ //#endregion
129
+ //#region src/utils/color.ts
130
+ const cyan = getColor("cyan");
131
+ const yellow = getColor("yellow");
132
+ const dim = getColor("dim");
133
+ const green = getColor("green");
134
+ const red = getColor("red");
135
+ const bold = getColor("bold");
136
+ const magenta = getColor("magenta");
137
+
138
+ //#endregion
139
+ //#region src/utils/npmrc.ts
140
+ /**
141
+ * Remove pnpm-related settings from `.npmrc` file.
142
+ *
143
+ * This function reads the `.npmrc` file, filters out all lines that start with
144
+ * pnpm-specific configuration keys (as defined in PNPM_SETTINGS_FIELDS), and
145
+ * writes the cleaned content back to the file.
146
+ *
147
+ * @param path - Absolute path to the `.npmrc` file
148
+ *
149
+ * @returns A promise that resolves when the file has been pruned
150
+ *
151
+ * @throws {Error} When file read/write operations fail
152
+ *
153
+ * @example
154
+ * ```ts
155
+ * await pruneNpmrc('/path/to/.npmrc')
156
+ * ```
157
+ */
158
+ async function pruneNpmrc(path) {
159
+ const pnpmSettingsFields = PNPM_SETTINGS_FIELDS.map((v) => kebabCase(v));
160
+ await fsWriteFile(path, (await fsReadFile(path)).split(/\r?\n/).filter((line) => !pnpmSettingsFields.some((v) => line.trim().startsWith(v))).join("\n"));
161
+ }
162
+ /**
163
+ * Read and parse `.npmrc` file with camelCase key conversion.
164
+ *
165
+ * This function reads an `.npmrc` INI-format file and parses it into an object.
166
+ * All keys are automatically converted from kebab-case to camelCase for easier
167
+ * JavaScript consumption (e.g., `allow-builds` → `allowBuilds`).
168
+ *
169
+ * @param path - Absolute path to the `.npmrc` file
170
+ *
171
+ * @returns A promise that resolves to the parsed `.npmrc` configuration object with camelCase keys
172
+ *
173
+ * @throws {Error} When file reading or INI parsing fails
174
+ *
175
+ * @example
176
+ * ```ts
177
+ * const config = await readNpmrc('/path/to/.npmrc')
178
+ * // config.allowBuilds, config.packageExtensions, etc.
179
+ * ```
180
+ */
181
+ async function readNpmrc(path) {
182
+ return camelcaseKeys(await readIniFile(path));
183
+ }
184
+
185
+ //#endregion
186
+ //#region src/core.ts
187
+ /**
188
+ * Migrate pnpm settings from legacy locations to `pnpm-workspace.yaml`.
189
+ *
190
+ * This function collects pnpm configurations from multiple sources and consolidates
191
+ * them into a single `pnpm-workspace.yaml` file:
192
+ * - `package.json` pnpm field
193
+ * - `.npmrc` pnpm-related settings
194
+ * - `package.json` resolutions (optional, converts to pnpm overrides)
195
+ *
196
+ * @param rawOptions - Migration options
197
+ * @param rawOptions.cwd - Current working directory (default: process.cwd())
198
+ * @param rawOptions.cleanNpmrc - Whether to remove pnpm settings from `.npmrc` (default: true)
199
+ * @param rawOptions.cleanPackageJson - Whether to remove pnpm field from `package.json` (default: true)
200
+ * @param rawOptions.yarnResolutions - Whether to migrate resolutions field (default: true)
201
+ * @param rawOptions.sortKeys - Whether to sort keys in output YAML (default: false)
202
+ * @param rawOptions.newlineBetween - Add newlines between root keys (default: true)
203
+ *
204
+ * @returns A promise that resolves when migration is complete
205
+ *
206
+ * @throws {Error} When file operations fail or JSON/YAML parsing errors occur
207
+ *
208
+ * @example
209
+ * ```ts
210
+ * // Migrate with default options
211
+ * await migratePnpmSettings()
212
+ *
213
+ * // Migrate with custom options
214
+ * await migratePnpmSettings({
215
+ * cwd: '/path/to/workspace',
216
+ * cleanNpmrc: false,
217
+ * sortKeys: true
218
+ * })
219
+ * ```
220
+ */
221
+ async function migratePnpmSettings(rawOptions = {}) {
222
+ try {
223
+ const options = resolveOptions(rawOptions);
224
+ const npmrcPath = resolve(options.cwd, NPMRC);
225
+ const packageJsonPath = resolve(options.cwd, PACKAGE_JSON);
226
+ const pnpmWorkspaceYamlPath = resolve(options.cwd, PNPM_WORKSPACE_YAML);
227
+ const [npmrcExists, packageJsonExists, pnpmWorkspaceExists] = await Promise.all([
228
+ fsExists(npmrcPath),
229
+ fsExists(packageJsonPath),
230
+ fsExists(pnpmWorkspaceYamlPath)
231
+ ]);
232
+ if (!npmrcExists) consola.info(`${dim(NPMRC)} not found`);
233
+ if (!packageJsonExists) consola.info(`${dim(PACKAGE_JSON)} not found`);
234
+ if (!npmrcExists && !packageJsonExists) {
235
+ consola.warn("No pnpm settings files to migrate");
236
+ return;
237
+ }
238
+ let packageJsonIndent = DEFAULT_INDENT;
239
+ let packageJsonObject = {};
240
+ let pnpmWorkspaceYamlIndent = DEFAULT_INDENT;
241
+ let pnpmWorkspaceYamlObject = {};
242
+ if (packageJsonExists) {
243
+ const content = await fsReadFile(packageJsonPath);
244
+ packageJsonIndent = detectIndent(content).indent;
245
+ packageJsonObject = JSON.parse(content);
246
+ }
247
+ if (pnpmWorkspaceExists) {
248
+ const content = await fsReadFile(pnpmWorkspaceYamlPath);
249
+ pnpmWorkspaceYamlIndent = detectIndent(content).amount;
250
+ pnpmWorkspaceYamlObject = parse(content);
251
+ }
252
+ const pnpmSettingsInNpmrc = npmrcExists ? pick(await readNpmrc(npmrcPath), PNPM_SETTINGS_FIELDS) : {};
253
+ const hasPnpmInPackageJson = !!packageJsonObject.pnpm;
254
+ const hasResolutions = options.yarnResolutions && !!packageJsonObject.resolutions;
255
+ const hasNpmrcSettings = Object.keys(pnpmSettingsInNpmrc).length > 0;
256
+ if (!hasPnpmInPackageJson && !hasResolutions && !hasNpmrcSettings) {
257
+ consola.warn("No pnpm settings fields to migrate");
258
+ return;
259
+ }
260
+ const pnpmSettingsInPackageJson = options.yarnResolutions && packageJsonObject.resolutions ? {
261
+ ...packageJsonObject.pnpm,
262
+ overrides: defu(packageJsonObject.pnpm?.overrides, packageJsonObject.resolutions)
263
+ } : { ...packageJsonObject.pnpm };
264
+ if (pnpmSettingsInPackageJson.overrides && !Object.keys(pnpmSettingsInPackageJson.overrides).length) delete pnpmSettingsInPackageJson.overrides;
265
+ const pnpmWorkspaceResult = defu(pnpmWorkspaceYamlObject, {
266
+ ...pnpmSettingsInNpmrc,
267
+ ...pnpmSettingsInPackageJson
268
+ });
269
+ const yamlDocument = new Document({}, { sortMapEntries: options.sortKeys });
270
+ Object.entries(pnpmWorkspaceResult).forEach(([key, value], index) => {
271
+ yamlDocument.add({
272
+ key,
273
+ value
274
+ });
275
+ if (options.newlineBetween && index < Object.keys(pnpmWorkspaceResult).length - 1) {}
276
+ });
277
+ await fsWriteFile(pnpmWorkspaceYamlPath, yamlDocument.toString({ indent: pnpmWorkspaceYamlIndent }));
278
+ if (npmrcExists && options.cleanNpmrc) await pruneNpmrc(npmrcPath);
279
+ if (packageJsonExists && options.cleanPackageJson && (packageJsonObject.pnpm || packageJsonObject.resolutions)) {
280
+ delete packageJsonObject.pnpm;
281
+ if (options.yarnResolutions) delete packageJsonObject.resolutions;
282
+ await fsWriteFile(packageJsonPath, JSON.stringify(packageJsonObject, null, packageJsonIndent));
283
+ }
284
+ } catch (err) {
285
+ consola.error("Failed to migrate pnpm settings:", err);
286
+ throw err;
287
+ }
288
+ }
289
+
290
+ //#endregion
291
+ //#region src/cli.ts
19
292
  const cli = cac(name);
20
- cli.version(version).option("--cwd [cwd]", "Current working directory").option("--sort-keys", "Sort keys when write pnpm-workspace.yaml").option(
21
- "--no-yarn-resolutions",
22
- "Disable migrating resolutions field in package.json"
23
- ).option(
24
- "--no-newline-between",
25
- "Disable adding newlines between each root keys"
26
- ).option("--no-clean-npmrc", "Disable removing pnpm settings in .npmrc file").option(
27
- "--no-clean-package-json",
28
- "Disable removing pnpm field in package.json"
29
- ).help();
293
+ cli.version(version).option("--cwd [cwd]", "Current working directory").option("--sort-keys", "Sort keys when write pnpm-workspace.yaml").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();
30
294
  cli.command("").action(async (options) => {
31
- try {
32
- consola.log(`
33
- ${bold(magenta(name))} ${dim(`v${version}`)}`);
34
- consola.log(dim("\n--------------\n"));
35
- await migratePnpmSettings(options);
36
- consola.success(green("pnpm settings migrate has finished"));
37
- } catch (err) {
38
- consola.fail(red(String(err)));
39
- if (err instanceof Error && err.stack) {
40
- consola.fail(dim(err.stack?.split("\n").slice(1).join("\n")));
41
- }
42
- process.exit(1);
43
- }
295
+ try {
296
+ consola$1.log(`\n${bold(magenta(name))} ${dim(`v${version}`)}`);
297
+ consola$1.log(dim("\n--------------\n"));
298
+ await migratePnpmSettings(options);
299
+ consola$1.success(green("pnpm settings migrate has finished"));
300
+ } catch (err) {
301
+ consola$1.fail(red(String(err)));
302
+ if (err instanceof Error && err.stack) consola$1.fail(dim(err.stack?.split("\n").slice(1).join("\n")));
303
+ process.exit(1);
304
+ }
44
305
  });
45
306
  cli.parse();
307
+
308
+ //#endregion
309
+ export { };
package/dist/index.d.mts CHANGED
@@ -1,79 +1,137 @@
1
- import { PnpmSettings } from '@pnpm/types';
1
+ import { PnpmSettings } from "@pnpm/types";
2
2
 
3
+ //#region src/options.d.ts
3
4
  interface Options {
4
- /**
5
- * Whether to remove pnpm settings in `.npmrc` file
6
- *
7
- * @default true
8
- */
9
- cleanNpmrc?: boolean;
10
- /**
11
- * Whether to remove `pnpm` field in `package.json`
12
- *
13
- * @default true
14
- */
15
- cleanPackageJson?: boolean;
16
- /**
17
- * Current working directory
18
- *
19
- * @default process.cwd()
20
- */
21
- cwd?: string;
22
- /**
23
- * Add newlines between each root keys like pnpm does
24
- * @default true
25
- */
26
- newlineBetween?: boolean;
27
- /**
28
- * Sort keys when write `pnpm-workspace.yaml`
29
- *
30
- * @default false
31
- */
32
- sortKeys?: boolean;
33
- /**
34
- * Strategy to handle conflicts
35
- */
36
- strategy?: 'discard' | 'merge' | 'overwrite';
37
- /**
38
- * Whether to migrate `resolutions` filed in `package.json`
39
- *
40
- * @default true
41
- */
42
- yarnResolutions?: boolean;
5
+ /**
6
+ * Whether to remove pnpm settings in `.npmrc` file
7
+ *
8
+ * @default true
9
+ */
10
+ cleanNpmrc?: boolean;
11
+ /**
12
+ * Whether to remove `pnpm` field in `package.json`
13
+ *
14
+ * @default true
15
+ */
16
+ cleanPackageJson?: boolean;
17
+ /**
18
+ * Current working directory
19
+ *
20
+ * @default process.cwd()
21
+ */
22
+ cwd?: string;
23
+ /**
24
+ * Add newlines between each root keys like pnpm does
25
+ * @default true
26
+ */
27
+ newlineBetween?: boolean;
28
+ /**
29
+ * Sort keys when write `pnpm-workspace.yaml`
30
+ *
31
+ * @default false
32
+ */
33
+ sortKeys?: boolean;
34
+ /**
35
+ * Strategy to handle conflicts
36
+ */
37
+ strategy?: 'discard' | 'merge' | 'overwrite';
38
+ /**
39
+ * Whether to migrate `resolutions` filed in `package.json`
40
+ *
41
+ * @default true
42
+ */
43
+ yarnResolutions?: boolean;
43
44
  }
45
+ /**
46
+ * Resolve and normalize migration options with defaults.
47
+ *
48
+ * This function takes partial options and returns a complete options object
49
+ * with all properties set to either the provided value or the default value.
50
+ *
51
+ * @param options - Partial migration options
52
+ *
53
+ * @returns Complete options object with all required properties
54
+ *
55
+ * @example
56
+ * ```ts
57
+ * // Use all defaults
58
+ * const opts = resolveOptions()
59
+ * // { cleanNpmrc: true, cleanPackageJson: true, cwd: '/current/dir', ... }
60
+ *
61
+ * // Override specific options
62
+ * const opts = resolveOptions({ sortKeys: true, cleanNpmrc: false })
63
+ * // { cleanNpmrc: false, cleanPackageJson: true, sortKeys: true, ... }
64
+ * ```
65
+ */
44
66
  declare function resolveOptions(options?: Options): Required<Options>;
45
-
67
+ //#endregion
68
+ //#region src/core.d.ts
69
+ /**
70
+ * Migrate pnpm settings from legacy locations to `pnpm-workspace.yaml`.
71
+ *
72
+ * This function collects pnpm configurations from multiple sources and consolidates
73
+ * them into a single `pnpm-workspace.yaml` file:
74
+ * - `package.json` pnpm field
75
+ * - `.npmrc` pnpm-related settings
76
+ * - `package.json` resolutions (optional, converts to pnpm overrides)
77
+ *
78
+ * @param rawOptions - Migration options
79
+ * @param rawOptions.cwd - Current working directory (default: process.cwd())
80
+ * @param rawOptions.cleanNpmrc - Whether to remove pnpm settings from `.npmrc` (default: true)
81
+ * @param rawOptions.cleanPackageJson - Whether to remove pnpm field from `package.json` (default: true)
82
+ * @param rawOptions.yarnResolutions - Whether to migrate resolutions field (default: true)
83
+ * @param rawOptions.sortKeys - Whether to sort keys in output YAML (default: false)
84
+ * @param rawOptions.newlineBetween - Add newlines between root keys (default: true)
85
+ *
86
+ * @returns A promise that resolves when migration is complete
87
+ *
88
+ * @throws {Error} When file operations fail or JSON/YAML parsing errors occur
89
+ *
90
+ * @example
91
+ * ```ts
92
+ * // Migrate with default options
93
+ * await migratePnpmSettings()
94
+ *
95
+ * // Migrate with custom options
96
+ * await migratePnpmSettings({
97
+ * cwd: '/path/to/workspace',
98
+ * cleanNpmrc: false,
99
+ * sortKeys: true
100
+ * })
101
+ * ```
102
+ */
46
103
  declare function migratePnpmSettings(rawOptions?: Options): Promise<void>;
47
-
104
+ //#endregion
105
+ //#region src/types.d.ts
48
106
  /**
49
107
  * legacy `pnpm-workspace` types
50
108
  */
51
109
  type PnpmWorkspaceLegacy = {
52
- catalog?: Record<string, string>;
53
- catalogs?: Record<string, Record<string, string>>;
54
- packages?: string[];
110
+ catalog?: Record<string, string>;
111
+ catalogs?: Record<string, Record<string, string>>;
112
+ packages?: string[];
55
113
  };
56
114
  /**
57
115
  * `package-json` types
58
116
  * @pg
59
117
  */
60
118
  interface PackageJson {
61
- /**
62
- * same as `pnpm.overrides`
63
- *
64
- * @compatibility npm, bun
65
- */
66
- overrides?: Record<string, string>;
67
- /**
68
- * pnpm settings
69
- */
70
- pnpm?: PnpmSettings;
71
- /**
72
- * same as `pnpm.overrides`
73
- *
74
- * @compatibility yarn, bun
75
- */
76
- resolutions?: Record<string, string>;
119
+ /**
120
+ * same as `pnpm.overrides`
121
+ *
122
+ * @compatibility npm, bun
123
+ */
124
+ overrides?: Record<string, string>;
125
+ /**
126
+ * pnpm settings
127
+ */
128
+ pnpm?: PnpmSettings;
129
+ /**
130
+ * same as `pnpm.overrides`
131
+ *
132
+ * @compatibility yarn, bun
133
+ */
134
+ resolutions?: Record<string, string>;
77
135
  }
78
136
  /**
79
137
  * `.npmrc` types
@@ -85,6 +143,5 @@ type NpmRC = Record<string, any>;
85
143
  * @pg
86
144
  */
87
145
  type PnpmWorkspace = PnpmSettings & PnpmWorkspaceLegacy;
88
-
89
- export { migratePnpmSettings, resolveOptions };
90
- export type { NpmRC, Options, PackageJson, PnpmWorkspace, PnpmWorkspaceLegacy };
146
+ //#endregion
147
+ export { NpmRC, Options, PackageJson, PnpmWorkspace, PnpmWorkspaceLegacy, migratePnpmSettings, resolveOptions };
package/dist/index.mjs CHANGED
@@ -1,13 +1,285 @@
1
- export { m as migratePnpmSettings, r as resolveOptions } from './shared/pnpm-settings-migrator.Cwv3sjIn.mjs';
2
- import '@ntnyq/utils';
3
- import 'consola';
4
- import 'defu';
5
- import 'detect-indent';
6
- import 'pathe';
7
- import 'yaml';
8
- import 'node:process';
9
- import 'node:fs/promises';
10
- import 'consola/utils';
11
- import 'camelcase-keys';
12
- import 'read-ini-file';
13
- import 'uncase';
1
+ import { pick } from "@ntnyq/utils";
2
+ import consola from "consola";
3
+ import { defu } from "defu";
4
+ import detectIndent from "detect-indent";
5
+ import { resolve } from "pathe";
6
+ import { Document, parse } from "yaml";
7
+ import process from "node:process";
8
+ import { access, readFile, writeFile } from "node:fs/promises";
9
+ import { getColor } from "consola/utils";
10
+ import camelcaseKeys from "camelcase-keys";
11
+ import { readIniFile } from "read-ini-file";
12
+ import { kebabCase } from "uncase";
13
+
14
+ //#region src/constants.ts
15
+ const NPMRC = ".npmrc";
16
+ const PACKAGE_JSON = "package.json";
17
+ const PNPM_WORKSPACE_YAML = "pnpm-workspace.yaml";
18
+ /**
19
+ * Default indent: 2 spaces
20
+ */
21
+ const DEFAULT_INDENT = 2;
22
+ /**
23
+ * @see {@link https://github.com/pnpm/pnpm/blob/main/packages/types/src/package.ts}
24
+ */
25
+ const PNPM_SETTINGS_FIELDS = [
26
+ "allowBuilds",
27
+ "allowedDeprecatedVersions",
28
+ "allowNonAppliedPatches",
29
+ "allowUnusedPatches",
30
+ "auditConfig",
31
+ "configDependencies",
32
+ "executionEnv",
33
+ "ignoredBuiltDependencies",
34
+ "ignoredOptionalDependencies",
35
+ "ignorePatchFailures",
36
+ "neverBuiltDependencies",
37
+ "onlyBuiltDependencies",
38
+ "onlyBuiltDependenciesFile",
39
+ "overrides",
40
+ "packageExtensions",
41
+ "patchedDependencies",
42
+ "peerDependencyRules",
43
+ "requiredScripts",
44
+ "supportedArchitectures",
45
+ "updateConfig"
46
+ ];
47
+
48
+ //#endregion
49
+ //#region src/options.ts
50
+ /**
51
+ * Default values for migration options.
52
+ */
53
+ const DEFAULT_OPTIONS = {
54
+ cleanNpmrc: true,
55
+ cleanPackageJson: true,
56
+ cwd: process.cwd(),
57
+ newlineBetween: true,
58
+ sortKeys: false,
59
+ strategy: "merge",
60
+ yarnResolutions: true
61
+ };
62
+ /**
63
+ * Resolve and normalize migration options with defaults.
64
+ *
65
+ * This function takes partial options and returns a complete options object
66
+ * with all properties set to either the provided value or the default value.
67
+ *
68
+ * @param options - Partial migration options
69
+ *
70
+ * @returns Complete options object with all required properties
71
+ *
72
+ * @example
73
+ * ```ts
74
+ * // Use all defaults
75
+ * const opts = resolveOptions()
76
+ * // { cleanNpmrc: true, cleanPackageJson: true, cwd: '/current/dir', ... }
77
+ *
78
+ * // Override specific options
79
+ * const opts = resolveOptions({ sortKeys: true, cleanNpmrc: false })
80
+ * // { cleanNpmrc: false, cleanPackageJson: true, sortKeys: true, ... }
81
+ * ```
82
+ */
83
+ function resolveOptions(options = {}) {
84
+ return {
85
+ cleanNpmrc: options.cleanNpmrc ?? DEFAULT_OPTIONS.cleanNpmrc,
86
+ cwd: options.cwd ?? DEFAULT_OPTIONS.cwd,
87
+ newlineBetween: options.newlineBetween ?? DEFAULT_OPTIONS.newlineBetween,
88
+ sortKeys: options.sortKeys ?? DEFAULT_OPTIONS.sortKeys,
89
+ strategy: options.strategy ?? DEFAULT_OPTIONS.strategy,
90
+ yarnResolutions: options.yarnResolutions ?? DEFAULT_OPTIONS.yarnResolutions,
91
+ cleanPackageJson: options.cleanPackageJson ?? DEFAULT_OPTIONS.cleanPackageJson
92
+ };
93
+ }
94
+
95
+ //#endregion
96
+ //#region src/utils/fs.ts
97
+ /**
98
+ * Check if a path exists
99
+ * @param path - given path
100
+ * @returns `true` if exists, false otherwise
101
+ */
102
+ async function fsExists(path) {
103
+ return access(path).then(() => true).catch(() => false);
104
+ }
105
+ /**
106
+ * Read file content
107
+ * @param path - file path
108
+ * @returns content of given file
109
+ */
110
+ async function fsReadFile(path) {
111
+ return await readFile(path, "utf-8");
112
+ }
113
+ /**
114
+ * Write file
115
+ * @param path - file path
116
+ * @param content - file content
117
+ */
118
+ async function fsWriteFile(path, content) {
119
+ await writeFile(path, `${content.trimEnd()}\n`, "utf-8");
120
+ }
121
+
122
+ //#endregion
123
+ //#region src/utils/color.ts
124
+ const cyan = getColor("cyan");
125
+ const yellow = getColor("yellow");
126
+ const dim = getColor("dim");
127
+ const green = getColor("green");
128
+ const red = getColor("red");
129
+ const bold = getColor("bold");
130
+ const magenta = getColor("magenta");
131
+
132
+ //#endregion
133
+ //#region src/utils/npmrc.ts
134
+ /**
135
+ * Remove pnpm-related settings from `.npmrc` file.
136
+ *
137
+ * This function reads the `.npmrc` file, filters out all lines that start with
138
+ * pnpm-specific configuration keys (as defined in PNPM_SETTINGS_FIELDS), and
139
+ * writes the cleaned content back to the file.
140
+ *
141
+ * @param path - Absolute path to the `.npmrc` file
142
+ *
143
+ * @returns A promise that resolves when the file has been pruned
144
+ *
145
+ * @throws {Error} When file read/write operations fail
146
+ *
147
+ * @example
148
+ * ```ts
149
+ * await pruneNpmrc('/path/to/.npmrc')
150
+ * ```
151
+ */
152
+ async function pruneNpmrc(path) {
153
+ const pnpmSettingsFields = PNPM_SETTINGS_FIELDS.map((v) => kebabCase(v));
154
+ await fsWriteFile(path, (await fsReadFile(path)).split(/\r?\n/).filter((line) => !pnpmSettingsFields.some((v) => line.trim().startsWith(v))).join("\n"));
155
+ }
156
+ /**
157
+ * Read and parse `.npmrc` file with camelCase key conversion.
158
+ *
159
+ * This function reads an `.npmrc` INI-format file and parses it into an object.
160
+ * All keys are automatically converted from kebab-case to camelCase for easier
161
+ * JavaScript consumption (e.g., `allow-builds` → `allowBuilds`).
162
+ *
163
+ * @param path - Absolute path to the `.npmrc` file
164
+ *
165
+ * @returns A promise that resolves to the parsed `.npmrc` configuration object with camelCase keys
166
+ *
167
+ * @throws {Error} When file reading or INI parsing fails
168
+ *
169
+ * @example
170
+ * ```ts
171
+ * const config = await readNpmrc('/path/to/.npmrc')
172
+ * // config.allowBuilds, config.packageExtensions, etc.
173
+ * ```
174
+ */
175
+ async function readNpmrc(path) {
176
+ return camelcaseKeys(await readIniFile(path));
177
+ }
178
+
179
+ //#endregion
180
+ //#region src/core.ts
181
+ /**
182
+ * Migrate pnpm settings from legacy locations to `pnpm-workspace.yaml`.
183
+ *
184
+ * This function collects pnpm configurations from multiple sources and consolidates
185
+ * them into a single `pnpm-workspace.yaml` file:
186
+ * - `package.json` pnpm field
187
+ * - `.npmrc` pnpm-related settings
188
+ * - `package.json` resolutions (optional, converts to pnpm overrides)
189
+ *
190
+ * @param rawOptions - Migration options
191
+ * @param rawOptions.cwd - Current working directory (default: process.cwd())
192
+ * @param rawOptions.cleanNpmrc - Whether to remove pnpm settings from `.npmrc` (default: true)
193
+ * @param rawOptions.cleanPackageJson - Whether to remove pnpm field from `package.json` (default: true)
194
+ * @param rawOptions.yarnResolutions - Whether to migrate resolutions field (default: true)
195
+ * @param rawOptions.sortKeys - Whether to sort keys in output YAML (default: false)
196
+ * @param rawOptions.newlineBetween - Add newlines between root keys (default: true)
197
+ *
198
+ * @returns A promise that resolves when migration is complete
199
+ *
200
+ * @throws {Error} When file operations fail or JSON/YAML parsing errors occur
201
+ *
202
+ * @example
203
+ * ```ts
204
+ * // Migrate with default options
205
+ * await migratePnpmSettings()
206
+ *
207
+ * // Migrate with custom options
208
+ * await migratePnpmSettings({
209
+ * cwd: '/path/to/workspace',
210
+ * cleanNpmrc: false,
211
+ * sortKeys: true
212
+ * })
213
+ * ```
214
+ */
215
+ async function migratePnpmSettings(rawOptions = {}) {
216
+ try {
217
+ const options = resolveOptions(rawOptions);
218
+ const npmrcPath = resolve(options.cwd, NPMRC);
219
+ const packageJsonPath = resolve(options.cwd, PACKAGE_JSON);
220
+ const pnpmWorkspaceYamlPath = resolve(options.cwd, PNPM_WORKSPACE_YAML);
221
+ const [npmrcExists, packageJsonExists, pnpmWorkspaceExists] = await Promise.all([
222
+ fsExists(npmrcPath),
223
+ fsExists(packageJsonPath),
224
+ fsExists(pnpmWorkspaceYamlPath)
225
+ ]);
226
+ if (!npmrcExists) consola.info(`${dim(NPMRC)} not found`);
227
+ if (!packageJsonExists) consola.info(`${dim(PACKAGE_JSON)} not found`);
228
+ if (!npmrcExists && !packageJsonExists) {
229
+ consola.warn("No pnpm settings files to migrate");
230
+ return;
231
+ }
232
+ let packageJsonIndent = DEFAULT_INDENT;
233
+ let packageJsonObject = {};
234
+ let pnpmWorkspaceYamlIndent = DEFAULT_INDENT;
235
+ let pnpmWorkspaceYamlObject = {};
236
+ if (packageJsonExists) {
237
+ const content = await fsReadFile(packageJsonPath);
238
+ packageJsonIndent = detectIndent(content).indent;
239
+ packageJsonObject = JSON.parse(content);
240
+ }
241
+ if (pnpmWorkspaceExists) {
242
+ const content = await fsReadFile(pnpmWorkspaceYamlPath);
243
+ pnpmWorkspaceYamlIndent = detectIndent(content).amount;
244
+ pnpmWorkspaceYamlObject = parse(content);
245
+ }
246
+ const pnpmSettingsInNpmrc = npmrcExists ? pick(await readNpmrc(npmrcPath), PNPM_SETTINGS_FIELDS) : {};
247
+ const hasPnpmInPackageJson = !!packageJsonObject.pnpm;
248
+ const hasResolutions = options.yarnResolutions && !!packageJsonObject.resolutions;
249
+ const hasNpmrcSettings = Object.keys(pnpmSettingsInNpmrc).length > 0;
250
+ if (!hasPnpmInPackageJson && !hasResolutions && !hasNpmrcSettings) {
251
+ consola.warn("No pnpm settings fields to migrate");
252
+ return;
253
+ }
254
+ const pnpmSettingsInPackageJson = options.yarnResolutions && packageJsonObject.resolutions ? {
255
+ ...packageJsonObject.pnpm,
256
+ overrides: defu(packageJsonObject.pnpm?.overrides, packageJsonObject.resolutions)
257
+ } : { ...packageJsonObject.pnpm };
258
+ if (pnpmSettingsInPackageJson.overrides && !Object.keys(pnpmSettingsInPackageJson.overrides).length) delete pnpmSettingsInPackageJson.overrides;
259
+ const pnpmWorkspaceResult = defu(pnpmWorkspaceYamlObject, {
260
+ ...pnpmSettingsInNpmrc,
261
+ ...pnpmSettingsInPackageJson
262
+ });
263
+ const yamlDocument = new Document({}, { sortMapEntries: options.sortKeys });
264
+ Object.entries(pnpmWorkspaceResult).forEach(([key, value], index) => {
265
+ yamlDocument.add({
266
+ key,
267
+ value
268
+ });
269
+ if (options.newlineBetween && index < Object.keys(pnpmWorkspaceResult).length - 1) {}
270
+ });
271
+ await fsWriteFile(pnpmWorkspaceYamlPath, yamlDocument.toString({ indent: pnpmWorkspaceYamlIndent }));
272
+ if (npmrcExists && options.cleanNpmrc) await pruneNpmrc(npmrcPath);
273
+ if (packageJsonExists && options.cleanPackageJson && (packageJsonObject.pnpm || packageJsonObject.resolutions)) {
274
+ delete packageJsonObject.pnpm;
275
+ if (options.yarnResolutions) delete packageJsonObject.resolutions;
276
+ await fsWriteFile(packageJsonPath, JSON.stringify(packageJsonObject, null, packageJsonIndent));
277
+ }
278
+ } catch (err) {
279
+ consola.error("Failed to migrate pnpm settings:", err);
280
+ throw err;
281
+ }
282
+ }
283
+
284
+ //#endregion
285
+ export { migratePnpmSettings, resolveOptions };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pnpm-settings-migrator",
3
3
  "type": "module",
4
- "version": "0.0.8",
4
+ "version": "0.1.0",
5
5
  "description": "Move pnpm settings from `pnpm` field in `package.json` and `.npmrc` file to `pnpm-workspace.yaml`",
6
6
  "keywords": [
7
7
  "migrator",
@@ -37,49 +37,42 @@
37
37
  },
38
38
  "sideEffects": false,
39
39
  "dependencies": {
40
- "@ntnyq/utils": "^0.7.0",
41
- "@pnpm/types": "^1000.5.0",
40
+ "@ntnyq/utils": "^0.9.2",
41
+ "@pnpm/types": "^1001.2.0",
42
42
  "cac": "^6.7.14",
43
- "camelcase-keys": "^9.1.3",
43
+ "camelcase-keys": "^10.0.1",
44
44
  "consola": "^3.4.2",
45
45
  "defu": "^6.1.4",
46
- "detect-indent": "^7.0.1",
46
+ "detect-indent": "^7.0.2",
47
47
  "pathe": "^2.0.3",
48
48
  "read-ini-file": "^4.0.0",
49
- "uncase": "^0.1.0",
50
- "yaml": "^2.7.1"
49
+ "uncase": "^0.2.0",
50
+ "yaml": "^2.8.2"
51
51
  },
52
52
  "devDependencies": {
53
- "@ntnyq/eslint-config": "^4.2.0",
54
- "@ntnyq/prettier-config": "^2.0.0",
55
- "@types/node": "^22.15.17",
56
- "bumpp": "^10.1.0",
57
- "eslint": "^9.26.0",
53
+ "@ntnyq/eslint-config": "^5.8.0",
54
+ "@ntnyq/prettier-config": "^3.0.1",
55
+ "@types/node": "^24.10.4",
56
+ "bumpp": "^10.3.2",
57
+ "eslint": "^9.39.2",
58
58
  "husky": "^9.1.7",
59
- "nano-staged": "^0.8.0",
60
- "npm-run-all2": "^8.0.1",
61
- "prettier": "^3.5.3",
62
- "tsx": "^4.19.4",
63
- "typescript": "^5.8.3",
64
- "unbuild": "^3.5.0",
65
- "vitest": "^3.1.3"
66
- },
67
- "engines": {
68
- "node": ">=18.18.0"
59
+ "nano-staged": "^0.9.0",
60
+ "npm-run-all2": "^8.0.4",
61
+ "prettier": "^3.7.4",
62
+ "tsdown": "^0.18.0",
63
+ "typescript": "^5.9.3",
64
+ "vitest": "^4.0.15"
69
65
  },
70
66
  "nano-staged": {
71
- "*.{js,ts,mjs,cjs,md,vue,yml,yaml,toml,json}": "eslint --fix",
72
- "*.{css,scss,html}": "prettier -uw"
67
+ "*.{js,ts,mjs,cjs,md,yml,yaml,toml,json}": "eslint --fix"
73
68
  },
74
69
  "scripts": {
75
- "build": "unbuild",
76
- "dev": "unbuild --watch",
70
+ "build": "tsdown",
71
+ "dev": "tsdown --watch",
77
72
  "lint": "eslint",
78
- "release": "run-s release:check release:version release:publish",
73
+ "release": "run-s release:check release:version",
79
74
  "release:check": "run-s lint typecheck test",
80
- "release:publish": "pnpm publish",
81
75
  "release:version": "bumpp",
82
- "start": "tsx src/cli.ts",
83
76
  "test": "vitest",
84
77
  "typecheck": "tsc --noEmit"
85
78
  }
package/dist/cli.d.mts DELETED
@@ -1,2 +0,0 @@
1
-
2
- export { };
@@ -1,169 +0,0 @@
1
- import { pick } from '@ntnyq/utils';
2
- import consola from 'consola';
3
- import { defu } from 'defu';
4
- import detectIndent from 'detect-indent';
5
- import { resolve } from 'pathe';
6
- import { parse, Document } from 'yaml';
7
- import process from 'node:process';
8
- import { access, readFile, writeFile } from 'node:fs/promises';
9
- import { getColor } from 'consola/utils';
10
- import camelcaseKeys from 'camelcase-keys';
11
- import { readIniFile } from 'read-ini-file';
12
- import { kebabCase } from 'uncase';
13
-
14
- const NPMRC = ".npmrc";
15
- const PACKAGE_JSON = "package.json";
16
- const PNPM_WORKSPACE_YAML = "pnpm-workspace.yaml";
17
- const DEFAULT_INDENT = 2;
18
- const PNPM_SETTINGS_FIELDS = [
19
- "allowedDeprecatedVersions",
20
- "allowNonAppliedPatches",
21
- "allowUnusedPatches",
22
- "auditConfig",
23
- "configDependencies",
24
- "executionEnv",
25
- "ignoredBuiltDependencies",
26
- "ignoredOptionalDependencies",
27
- "ignorePatchFailures",
28
- "neverBuiltDependencies",
29
- "onlyBuiltDependencies",
30
- "onlyBuiltDependenciesFile",
31
- "overrides",
32
- "packageExtensions",
33
- "patchedDependencies",
34
- "peerDependencyRules",
35
- "requiredScripts",
36
- "supportedArchitectures",
37
- "updateConfig"
38
- ];
39
-
40
- function resolveOptions(options = {}) {
41
- return {
42
- cleanNpmrc: options.cleanNpmrc ?? true,
43
- cleanPackageJson: options.cleanPackageJson ?? true,
44
- cwd: options.cwd ?? process.cwd(),
45
- newlineBetween: options.newlineBetween ?? true,
46
- sortKeys: options.sortKeys ?? false,
47
- strategy: options.strategy ?? "merge",
48
- yarnResolutions: options.yarnResolutions ?? true
49
- };
50
- }
51
-
52
- async function fsExists(path) {
53
- return access(path).then(() => true).catch(() => false);
54
- }
55
- async function fsReadFile(path) {
56
- return await readFile(path, "utf-8");
57
- }
58
- async function fsWriteFile(path, content) {
59
- await writeFile(path, `${content.trimEnd()}
60
- `, "utf-8");
61
- }
62
-
63
- getColor("cyan");
64
- getColor("yellow");
65
- const dim = getColor("dim");
66
- const green = getColor("green");
67
- const red = getColor("red");
68
- const bold = getColor("bold");
69
- const magenta = getColor("magenta");
70
-
71
- async function pruneNpmrc(path) {
72
- const pnpmSettingsFields = PNPM_SETTINGS_FIELDS.map((v) => kebabCase(v));
73
- const content = await fsReadFile(path);
74
- const lines = content.split(/\r?\n/).filter((line) => !pnpmSettingsFields.some((v) => line.trim().startsWith(v)));
75
- await fsWriteFile(path, lines.join("\n"));
76
- }
77
- async function readNpmrc(path) {
78
- const content = await readIniFile(path);
79
- return camelcaseKeys(content);
80
- }
81
-
82
- async function migratePnpmSettings(rawOptions = {}) {
83
- const options = resolveOptions(rawOptions);
84
- const npmrcPath = resolve(options.cwd, NPMRC);
85
- const packageJsonPath = resolve(options.cwd, PACKAGE_JSON);
86
- const pnpmWorkspaceYamlPath = resolve(options.cwd, PNPM_WORKSPACE_YAML);
87
- const isNpmrcExist = await fsExists(npmrcPath);
88
- if (!isNpmrcExist) {
89
- consola.info(`${dim(NPMRC)} not found`);
90
- }
91
- const isPackageJsonExist = await fsExists(packageJsonPath);
92
- if (!isPackageJsonExist) {
93
- consola.info(`${dim(PACKAGE_JSON)} not found`);
94
- }
95
- const isPnpmWorkspaceExist = await fsExists(pnpmWorkspaceYamlPath);
96
- if (!isNpmrcExist && !isPackageJsonExist) {
97
- consola.warn("No pnpm settings files to migrate");
98
- return;
99
- }
100
- let packageJsonIndent = DEFAULT_INDENT;
101
- let packageJsonObject = {};
102
- let pnpmWorkspaceYamlIndent = DEFAULT_INDENT;
103
- let pnpmWorkspaceYamlObject = {};
104
- if (isPackageJsonExist) {
105
- const content = await fsReadFile(packageJsonPath);
106
- packageJsonIndent = detectIndent(content).indent;
107
- packageJsonObject = JSON.parse(content);
108
- }
109
- if (isPnpmWorkspaceExist) {
110
- const content = await fsReadFile(pnpmWorkspaceYamlPath);
111
- pnpmWorkspaceYamlIndent = detectIndent(content).amount;
112
- pnpmWorkspaceYamlObject = parse(content);
113
- }
114
- const pnpmSettingsInNpmrc = isNpmrcExist ? pick(await readNpmrc(npmrcPath), PNPM_SETTINGS_FIELDS) : {};
115
- if (!packageJsonObject.pnpm && (!options.yarnResolutions || !packageJsonObject.resolutions) && !Object.keys(pnpmSettingsInNpmrc).length) {
116
- consola.warn("No pnpm settings fields to migrate");
117
- return;
118
- }
119
- const pnpmSettingsInPackageJson = options.yarnResolutions && packageJsonObject.resolutions ? {
120
- ...packageJsonObject.pnpm,
121
- overrides: defu(
122
- packageJsonObject.pnpm?.overrides,
123
- packageJsonObject.resolutions
124
- )
125
- } : { ...packageJsonObject.pnpm };
126
- if (pnpmSettingsInPackageJson.overrides && !Object.keys(pnpmSettingsInPackageJson.overrides).length) {
127
- delete pnpmSettingsInPackageJson.overrides;
128
- }
129
- const pnpmWorkspaceResult = defu(pnpmWorkspaceYamlObject, {
130
- ...pnpmSettingsInNpmrc,
131
- ...pnpmSettingsInPackageJson
132
- });
133
- const yamlDocument = new Document(
134
- {},
135
- {
136
- sortMapEntries: options.sortKeys
137
- }
138
- );
139
- Object.entries(pnpmWorkspaceResult).forEach(([key, value], index) => {
140
- yamlDocument.add({ key, value });
141
- if (options.newlineBetween && index < Object.keys(pnpmWorkspaceResult).length - 1) {
142
- yamlDocument.add({
143
- key: "",
144
- value: null
145
- });
146
- }
147
- });
148
- await fsWriteFile(
149
- pnpmWorkspaceYamlPath,
150
- yamlDocument.toString({
151
- indent: pnpmWorkspaceYamlIndent
152
- })
153
- );
154
- if (isNpmrcExist && options.cleanNpmrc) {
155
- await pruneNpmrc(npmrcPath);
156
- }
157
- if (isPackageJsonExist && options.cleanPackageJson && (packageJsonObject.pnpm || packageJsonObject.resolutions)) {
158
- delete packageJsonObject.pnpm;
159
- if (options.yarnResolutions) {
160
- delete packageJsonObject.resolutions;
161
- }
162
- await fsWriteFile(
163
- packageJsonPath,
164
- JSON.stringify(packageJsonObject, null, packageJsonIndent)
165
- );
166
- }
167
- }
168
-
169
- export { magenta as a, bold as b, red as c, dim as d, green as g, migratePnpmSettings as m, resolveOptions as r };