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 +2 -2
- package/dist/cli.mjs +304 -40
- package/dist/index.d.mts +121 -64
- package/dist/index.mjs +285 -13
- package/package.json +22 -29
- package/dist/cli.d.mts +0 -2
- package/dist/shared/pnpm-settings-migrator.Cwv3sjIn.mjs +0 -169
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
|
-
###
|
|
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
|
-
###
|
|
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
|
|
2
|
-
import { cac } from
|
|
3
|
-
import { consola } from
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
$
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
|
1
|
+
import { PnpmSettings } from "@pnpm/types";
|
|
2
2
|
|
|
3
|
+
//#region src/options.d.ts
|
|
3
4
|
interface Options {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
|
|
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
|
|
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.
|
|
41
|
-
"@pnpm/types": "^
|
|
40
|
+
"@ntnyq/utils": "^0.9.2",
|
|
41
|
+
"@pnpm/types": "^1001.2.0",
|
|
42
42
|
"cac": "^6.7.14",
|
|
43
|
-
"camelcase-keys": "^
|
|
43
|
+
"camelcase-keys": "^10.0.1",
|
|
44
44
|
"consola": "^3.4.2",
|
|
45
45
|
"defu": "^6.1.4",
|
|
46
|
-
"detect-indent": "^7.0.
|
|
46
|
+
"detect-indent": "^7.0.2",
|
|
47
47
|
"pathe": "^2.0.3",
|
|
48
48
|
"read-ini-file": "^4.0.0",
|
|
49
|
-
"uncase": "^0.
|
|
50
|
-
"yaml": "^2.
|
|
49
|
+
"uncase": "^0.2.0",
|
|
50
|
+
"yaml": "^2.8.2"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@ntnyq/eslint-config": "^
|
|
54
|
-
"@ntnyq/prettier-config": "^
|
|
55
|
-
"@types/node": "^
|
|
56
|
-
"bumpp": "^10.
|
|
57
|
-
"eslint": "^9.
|
|
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.
|
|
60
|
-
"npm-run-all2": "^8.0.
|
|
61
|
-
"prettier": "^3.
|
|
62
|
-
"
|
|
63
|
-
"typescript": "^5.
|
|
64
|
-
"
|
|
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,
|
|
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": "
|
|
76
|
-
"dev": "
|
|
70
|
+
"build": "tsdown",
|
|
71
|
+
"dev": "tsdown --watch",
|
|
77
72
|
"lint": "eslint",
|
|
78
|
-
"release": "run-s release:check release:version
|
|
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,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 };
|