@openrewrite/recipes-nodejs 0.37.0-20260103-170432 → 0.37.0-20260106-082310
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/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/resources/advisories-npm.csv +19 -4
- package/dist/security/dependency-vulnerability-check.d.ts +25 -2
- package/dist/security/dependency-vulnerability-check.d.ts.map +1 -1
- package/dist/security/dependency-vulnerability-check.js +338 -96
- package/dist/security/dependency-vulnerability-check.js.map +1 -1
- package/dist/security/index.d.ts +1 -0
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +1 -0
- package/dist/security/index.js.map +1 -1
- package/dist/security/npm-utils.d.ts +21 -0
- package/dist/security/npm-utils.d.ts.map +1 -0
- package/dist/security/npm-utils.js +268 -0
- package/dist/security/npm-utils.js.map +1 -0
- package/dist/security/remove-redundant-overrides.d.ts +40 -0
- package/dist/security/remove-redundant-overrides.d.ts.map +1 -0
- package/dist/security/remove-redundant-overrides.js +379 -0
- package/dist/security/remove-redundant-overrides.js.map +1 -0
- package/package.json +7 -3
- package/src/index.ts +2 -1
- package/src/security/dependency-vulnerability-check.ts +622 -66
- package/src/security/index.ts +1 -0
- package/src/security/npm-utils.ts +414 -0
- package/src/security/remove-redundant-overrides.ts +515 -0
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 the original author or authors.
|
|
3
|
+
*
|
|
4
|
+
* Moderne Proprietary. Only for use by Moderne customers under the terms of a commercial contract.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
ExecutionContext,
|
|
9
|
+
Option,
|
|
10
|
+
ScanningRecipe,
|
|
11
|
+
Tree,
|
|
12
|
+
TreePrinters,
|
|
13
|
+
TreeVisitor
|
|
14
|
+
} from "@openrewrite/rewrite";
|
|
15
|
+
import {isJson, Json, JsonParser, JsonVisitor} from "@openrewrite/rewrite/json";
|
|
16
|
+
import {
|
|
17
|
+
findNodeResolutionResult,
|
|
18
|
+
NpmrcScope,
|
|
19
|
+
PackageManager,
|
|
20
|
+
runInstallInTempDir
|
|
21
|
+
} from "@openrewrite/rewrite/javascript";
|
|
22
|
+
import * as semver from "semver";
|
|
23
|
+
import {extractVersionFromLockFile} from "./npm-utils";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Information about an override that might be redundant.
|
|
27
|
+
*/
|
|
28
|
+
interface OverrideInfo {
|
|
29
|
+
/** The package name (or version-specific key like "package@^1") */
|
|
30
|
+
key: string;
|
|
31
|
+
/** The base package name (without version specifier) */
|
|
32
|
+
packageName: string;
|
|
33
|
+
/** The version the override pins to */
|
|
34
|
+
version: string;
|
|
35
|
+
/** Whether this is a version-specific override (e.g., "package@^1") */
|
|
36
|
+
isVersionSpecific: boolean;
|
|
37
|
+
/** The version range specifier if version-specific (e.g., "^1") */
|
|
38
|
+
versionRange?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Project information for override analysis.
|
|
43
|
+
*/
|
|
44
|
+
interface ProjectInfo {
|
|
45
|
+
/** Path to package.json */
|
|
46
|
+
packageJsonPath: string;
|
|
47
|
+
/** Original package.json content */
|
|
48
|
+
originalPackageJson: string;
|
|
49
|
+
/** Package manager used */
|
|
50
|
+
packageManager: PackageManager;
|
|
51
|
+
/** List of overrides found */
|
|
52
|
+
overrides: OverrideInfo[];
|
|
53
|
+
/** Config files (.npmrc, etc.) */
|
|
54
|
+
configFiles?: Record<string, string>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Accumulator for the recipe.
|
|
59
|
+
*/
|
|
60
|
+
interface Accumulator {
|
|
61
|
+
/** Projects to analyze */
|
|
62
|
+
projects: Map<string, ProjectInfo>;
|
|
63
|
+
/** Overrides confirmed as redundant (project path -> set of override keys) */
|
|
64
|
+
redundantOverrides: Map<string, Set<string>>;
|
|
65
|
+
/** Whether redundant override analysis has been completed */
|
|
66
|
+
analysisComplete: boolean;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Removes overrides from package.json that are redundant because the dependency tree
|
|
71
|
+
* already resolves to the overridden version (or higher) without the override.
|
|
72
|
+
*
|
|
73
|
+
* This recipe is useful for cleaning up package.json after dependency upgrades that
|
|
74
|
+
* may have made previously-necessary overrides redundant.
|
|
75
|
+
*
|
|
76
|
+
* For each override, the recipe:
|
|
77
|
+
* 1. Temporarily removes the override from package.json
|
|
78
|
+
* 2. Runs `npm install --package-lock-only` (or equivalent)
|
|
79
|
+
* 3. Checks if the resolved version satisfies the override constraint
|
|
80
|
+
* 4. If yes, marks the override as redundant and removes it
|
|
81
|
+
*
|
|
82
|
+
* Also removes associated comments (e.g., from `//overrides`).
|
|
83
|
+
*/
|
|
84
|
+
export class RemoveRedundantOverrides extends ScanningRecipe<Accumulator> {
|
|
85
|
+
readonly name = "org.openrewrite.node.security.remove-redundant-overrides";
|
|
86
|
+
readonly displayName = "Remove redundant dependency overrides";
|
|
87
|
+
readonly description = "Removes overrides/resolutions from package.json that are redundant " +
|
|
88
|
+
"because the dependency tree already resolves to the overridden version or higher.";
|
|
89
|
+
|
|
90
|
+
@Option({
|
|
91
|
+
displayName: "Dry run",
|
|
92
|
+
description: "If true, only report which overrides are redundant without removing them.",
|
|
93
|
+
required: false,
|
|
94
|
+
example: "true"
|
|
95
|
+
})
|
|
96
|
+
dryRun?: boolean;
|
|
97
|
+
|
|
98
|
+
constructor(options?: { dryRun?: boolean }) {
|
|
99
|
+
super();
|
|
100
|
+
this.dryRun = options?.dryRun ?? false;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
initialValue(_ctx: ExecutionContext): Accumulator {
|
|
104
|
+
return {
|
|
105
|
+
projects: new Map(),
|
|
106
|
+
redundantOverrides: new Map(),
|
|
107
|
+
analysisComplete: false
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async scanner(acc: Accumulator): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
112
|
+
const recipe = this;
|
|
113
|
+
|
|
114
|
+
return new class extends TreeVisitor<Tree, ExecutionContext> {
|
|
115
|
+
protected async accept(tree: Tree, _ctx: ExecutionContext): Promise<Tree | undefined> {
|
|
116
|
+
if (!isJson(tree) || tree.kind !== Json.Kind.Document) {
|
|
117
|
+
return tree;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const doc = tree as Json.Document;
|
|
121
|
+
if (!doc.sourcePath.endsWith('package.json')) {
|
|
122
|
+
return doc;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const marker = findNodeResolutionResult(doc);
|
|
126
|
+
if (!marker) {
|
|
127
|
+
return doc;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const pm = marker.packageManager ?? PackageManager.Npm;
|
|
131
|
+
const content = await TreePrinters.print(doc);
|
|
132
|
+
let packageJson: Record<string, any>;
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
packageJson = JSON.parse(content);
|
|
136
|
+
} catch {
|
|
137
|
+
return doc;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Extract overrides based on package manager
|
|
141
|
+
const overrides = recipe.extractOverrides(packageJson, pm);
|
|
142
|
+
if (overrides.length === 0) {
|
|
143
|
+
return doc;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Extract config files
|
|
147
|
+
const configFiles: Record<string, string> = {};
|
|
148
|
+
const projectNpmrc = marker.npmrcConfigs?.find(c => c.scope === NpmrcScope.Project);
|
|
149
|
+
if (projectNpmrc) {
|
|
150
|
+
const lines = Object.entries(projectNpmrc.properties)
|
|
151
|
+
.map(([key, value]) => `${key}=${value}`);
|
|
152
|
+
configFiles['.npmrc'] = lines.join('\n');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
acc.projects.set(doc.sourcePath, {
|
|
156
|
+
packageJsonPath: doc.sourcePath,
|
|
157
|
+
originalPackageJson: content,
|
|
158
|
+
packageManager: pm,
|
|
159
|
+
overrides,
|
|
160
|
+
configFiles: Object.keys(configFiles).length > 0 ? configFiles : undefined
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
return doc;
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async editorWithData(acc: Accumulator): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
169
|
+
const recipe = this;
|
|
170
|
+
|
|
171
|
+
// Only run the expensive analysis once (editorWithData is called per source file)
|
|
172
|
+
if (!acc.analysisComplete) {
|
|
173
|
+
for (const [projectPath, project] of acc.projects) {
|
|
174
|
+
const redundant = await recipe.findRedundantOverrides(project);
|
|
175
|
+
if (redundant.size > 0) {
|
|
176
|
+
acc.redundantOverrides.set(projectPath, redundant);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
acc.analysisComplete = true;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// If dry run, don't modify files
|
|
183
|
+
if (recipe.dryRun) {
|
|
184
|
+
return new class extends TreeVisitor<Tree, ExecutionContext> {
|
|
185
|
+
protected async accept(tree: Tree, _ctx: ExecutionContext): Promise<Tree | undefined> {
|
|
186
|
+
return tree;
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return new class extends JsonVisitor<ExecutionContext> {
|
|
192
|
+
protected async visitDocument(doc: Json.Document, _ctx: ExecutionContext): Promise<Json | undefined> {
|
|
193
|
+
if (!doc.sourcePath.endsWith('package.json')) {
|
|
194
|
+
return doc;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const redundant = acc.redundantOverrides.get(doc.sourcePath);
|
|
198
|
+
if (!redundant || redundant.size === 0) {
|
|
199
|
+
return doc;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const project = acc.projects.get(doc.sourcePath);
|
|
203
|
+
if (!project) {
|
|
204
|
+
return doc;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Remove redundant overrides
|
|
208
|
+
const modifiedContent = recipe.removeOverrides(
|
|
209
|
+
project.originalPackageJson,
|
|
210
|
+
project.packageManager,
|
|
211
|
+
redundant
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
// Re-parse the modified content
|
|
215
|
+
const parsed = await new JsonParser({}).parseOne({
|
|
216
|
+
text: modifiedContent,
|
|
217
|
+
sourcePath: doc.sourcePath
|
|
218
|
+
}) as Json.Document;
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
...doc,
|
|
222
|
+
value: parsed.value,
|
|
223
|
+
eof: parsed.eof
|
|
224
|
+
} as Json.Document;
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Extracts override information from package.json.
|
|
231
|
+
*/
|
|
232
|
+
private extractOverrides(
|
|
233
|
+
packageJson: Record<string, any>,
|
|
234
|
+
pm: PackageManager
|
|
235
|
+
): OverrideInfo[] {
|
|
236
|
+
const overrides: OverrideInfo[] = [];
|
|
237
|
+
|
|
238
|
+
let overrideObj: Record<string, any> | undefined;
|
|
239
|
+
|
|
240
|
+
switch (pm) {
|
|
241
|
+
case PackageManager.Npm:
|
|
242
|
+
case PackageManager.Bun:
|
|
243
|
+
overrideObj = packageJson.overrides;
|
|
244
|
+
break;
|
|
245
|
+
case PackageManager.Pnpm:
|
|
246
|
+
overrideObj = packageJson.pnpm?.overrides;
|
|
247
|
+
break;
|
|
248
|
+
case PackageManager.YarnClassic:
|
|
249
|
+
case PackageManager.YarnBerry:
|
|
250
|
+
overrideObj = packageJson.resolutions;
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (!overrideObj) {
|
|
255
|
+
return overrides;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
for (const [key, value] of Object.entries(overrideObj)) {
|
|
259
|
+
// Skip nested overrides (npm supports objects as values)
|
|
260
|
+
if (typeof value !== 'string') {
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Parse the key to extract package name and version range
|
|
265
|
+
const atIndex = key.lastIndexOf('@');
|
|
266
|
+
let packageName: string;
|
|
267
|
+
let versionRange: string | undefined;
|
|
268
|
+
let isVersionSpecific = false;
|
|
269
|
+
|
|
270
|
+
// Check if this is a version-specific override like "package@^1"
|
|
271
|
+
// But be careful with scoped packages like "@scope/package"
|
|
272
|
+
if (atIndex > 0 && !key.startsWith('@')) {
|
|
273
|
+
// Unscoped package with version specifier
|
|
274
|
+
packageName = key.substring(0, atIndex);
|
|
275
|
+
versionRange = key.substring(atIndex + 1);
|
|
276
|
+
isVersionSpecific = true;
|
|
277
|
+
} else if (atIndex > 0 && key.startsWith('@')) {
|
|
278
|
+
// Scoped package - check if there's another @ after the scope
|
|
279
|
+
const secondAtIndex = key.indexOf('@', 1);
|
|
280
|
+
if (secondAtIndex > 0 && secondAtIndex !== atIndex) {
|
|
281
|
+
// Has version specifier: @scope/package@^1
|
|
282
|
+
packageName = key.substring(0, secondAtIndex);
|
|
283
|
+
versionRange = key.substring(secondAtIndex + 1);
|
|
284
|
+
isVersionSpecific = true;
|
|
285
|
+
} else {
|
|
286
|
+
// Just @scope/package
|
|
287
|
+
packageName = key;
|
|
288
|
+
}
|
|
289
|
+
} else {
|
|
290
|
+
packageName = key;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
overrides.push({
|
|
294
|
+
key,
|
|
295
|
+
packageName,
|
|
296
|
+
version: value,
|
|
297
|
+
isVersionSpecific,
|
|
298
|
+
versionRange
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return overrides;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Tests each override to see if it's redundant.
|
|
307
|
+
*
|
|
308
|
+
* Optimization: Instead of running N installs (one per override), we run ONE install
|
|
309
|
+
* with all overrides removed and check each override against that single result.
|
|
310
|
+
* This is much faster for projects with multiple overrides.
|
|
311
|
+
*/
|
|
312
|
+
private async findRedundantOverrides(project: ProjectInfo): Promise<Set<string>> {
|
|
313
|
+
const redundant = new Set<string>();
|
|
314
|
+
|
|
315
|
+
if (project.overrides.length === 0) {
|
|
316
|
+
return redundant;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
try {
|
|
320
|
+
// Create package.json with ALL overrides removed
|
|
321
|
+
const packageJson = JSON.parse(project.originalPackageJson);
|
|
322
|
+
for (const override of project.overrides) {
|
|
323
|
+
this.removeOverrideFromObject(packageJson, project.packageManager, override.key);
|
|
324
|
+
}
|
|
325
|
+
const modifiedPackageJson = JSON.stringify(packageJson, null, 2);
|
|
326
|
+
|
|
327
|
+
// Run ONE install to see what versions get resolved without any overrides
|
|
328
|
+
const result = await runInstallInTempDir(
|
|
329
|
+
project.packageManager,
|
|
330
|
+
modifiedPackageJson,
|
|
331
|
+
{
|
|
332
|
+
configFiles: project.configFiles,
|
|
333
|
+
lockOnly: true
|
|
334
|
+
}
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
if (!result.success || !result.lockFileContent) {
|
|
338
|
+
// Can't determine - keep all overrides
|
|
339
|
+
return redundant;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Check each override against the resolved versions
|
|
343
|
+
for (const override of project.overrides) {
|
|
344
|
+
const isRedundant = this.isOverrideRedundantForLockFile(
|
|
345
|
+
override,
|
|
346
|
+
result.lockFileContent,
|
|
347
|
+
project.packageManager
|
|
348
|
+
);
|
|
349
|
+
if (isRedundant) {
|
|
350
|
+
redundant.add(override.key);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
} catch {
|
|
354
|
+
// Error during check - keep all overrides
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return redundant;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Checks if a single override is redundant given a lock file without any overrides.
|
|
362
|
+
*/
|
|
363
|
+
private isOverrideRedundantForLockFile(
|
|
364
|
+
override: OverrideInfo,
|
|
365
|
+
lockFileContent: string,
|
|
366
|
+
packageManager: PackageManager
|
|
367
|
+
): boolean {
|
|
368
|
+
// Find the resolved version
|
|
369
|
+
const resolvedVersion = extractVersionFromLockFile(
|
|
370
|
+
lockFileContent,
|
|
371
|
+
override.packageName,
|
|
372
|
+
packageManager
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
if (!resolvedVersion) {
|
|
376
|
+
// Package not found in lock file - might no longer be a dependency
|
|
377
|
+
// This override is definitely redundant
|
|
378
|
+
return true;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Check if resolved version satisfies the override
|
|
382
|
+
// The override is redundant if the natural resolution is >= the override version
|
|
383
|
+
try {
|
|
384
|
+
if (semver.gte(resolvedVersion, override.version)) {
|
|
385
|
+
return true;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Also check if resolved version satisfies the override as a range
|
|
389
|
+
if (semver.satisfies(resolvedVersion, `>=${override.version}`)) {
|
|
390
|
+
return true;
|
|
391
|
+
}
|
|
392
|
+
} catch {
|
|
393
|
+
// Invalid version comparison - keep the override
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return false;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Removes a single override from the package.json object.
|
|
402
|
+
*/
|
|
403
|
+
private removeOverrideFromObject(
|
|
404
|
+
packageJson: Record<string, any>,
|
|
405
|
+
pm: PackageManager,
|
|
406
|
+
key: string
|
|
407
|
+
): void {
|
|
408
|
+
switch (pm) {
|
|
409
|
+
case PackageManager.Npm:
|
|
410
|
+
case PackageManager.Bun:
|
|
411
|
+
if (packageJson.overrides) {
|
|
412
|
+
delete packageJson.overrides[key];
|
|
413
|
+
if (Object.keys(packageJson.overrides).length === 0) {
|
|
414
|
+
delete packageJson.overrides;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
break;
|
|
418
|
+
case PackageManager.Pnpm:
|
|
419
|
+
if (packageJson.pnpm?.overrides) {
|
|
420
|
+
delete packageJson.pnpm.overrides[key];
|
|
421
|
+
if (Object.keys(packageJson.pnpm.overrides).length === 0) {
|
|
422
|
+
delete packageJson.pnpm.overrides;
|
|
423
|
+
}
|
|
424
|
+
if (Object.keys(packageJson.pnpm).length === 0) {
|
|
425
|
+
delete packageJson.pnpm;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
break;
|
|
429
|
+
case PackageManager.YarnClassic:
|
|
430
|
+
case PackageManager.YarnBerry:
|
|
431
|
+
if (packageJson.resolutions) {
|
|
432
|
+
delete packageJson.resolutions[key];
|
|
433
|
+
if (Object.keys(packageJson.resolutions).length === 0) {
|
|
434
|
+
delete packageJson.resolutions;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
break;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Removes the specified overrides from package.json content.
|
|
443
|
+
* Also removes associated comments.
|
|
444
|
+
*/
|
|
445
|
+
private removeOverrides(
|
|
446
|
+
originalContent: string,
|
|
447
|
+
pm: PackageManager,
|
|
448
|
+
keysToRemove: Set<string>
|
|
449
|
+
): string {
|
|
450
|
+
const packageJson = JSON.parse(originalContent);
|
|
451
|
+
|
|
452
|
+
// Determine field names
|
|
453
|
+
let overrideField: string;
|
|
454
|
+
let commentField: string;
|
|
455
|
+
|
|
456
|
+
switch (pm) {
|
|
457
|
+
case PackageManager.Npm:
|
|
458
|
+
case PackageManager.Bun:
|
|
459
|
+
overrideField = 'overrides';
|
|
460
|
+
commentField = '//overrides';
|
|
461
|
+
break;
|
|
462
|
+
case PackageManager.Pnpm:
|
|
463
|
+
overrideField = 'pnpm';
|
|
464
|
+
commentField = '//pnpm.overrides';
|
|
465
|
+
break;
|
|
466
|
+
case PackageManager.YarnClassic:
|
|
467
|
+
case PackageManager.YarnBerry:
|
|
468
|
+
overrideField = 'resolutions';
|
|
469
|
+
commentField = '//resolutions';
|
|
470
|
+
break;
|
|
471
|
+
default:
|
|
472
|
+
return originalContent;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Remove overrides
|
|
476
|
+
if (pm === PackageManager.Pnpm) {
|
|
477
|
+
if (packageJson.pnpm?.overrides) {
|
|
478
|
+
for (const key of keysToRemove) {
|
|
479
|
+
delete packageJson.pnpm.overrides[key];
|
|
480
|
+
}
|
|
481
|
+
if (Object.keys(packageJson.pnpm.overrides).length === 0) {
|
|
482
|
+
delete packageJson.pnpm.overrides;
|
|
483
|
+
}
|
|
484
|
+
if (Object.keys(packageJson.pnpm).length === 0) {
|
|
485
|
+
delete packageJson.pnpm;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
} else {
|
|
489
|
+
if (packageJson[overrideField]) {
|
|
490
|
+
for (const key of keysToRemove) {
|
|
491
|
+
delete packageJson[overrideField][key];
|
|
492
|
+
}
|
|
493
|
+
if (Object.keys(packageJson[overrideField]).length === 0) {
|
|
494
|
+
delete packageJson[overrideField];
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Remove associated comments
|
|
500
|
+
if (packageJson[commentField]) {
|
|
501
|
+
for (const key of keysToRemove) {
|
|
502
|
+
delete packageJson[commentField][key];
|
|
503
|
+
}
|
|
504
|
+
if (Object.keys(packageJson[commentField]).length === 0) {
|
|
505
|
+
delete packageJson[commentField];
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Preserve original indentation
|
|
510
|
+
const indentMatch = originalContent.match(/^(\s+)"/m);
|
|
511
|
+
const indent = indentMatch ? indentMatch[1].length : 2;
|
|
512
|
+
|
|
513
|
+
return JSON.stringify(packageJson, null, indent);
|
|
514
|
+
}
|
|
515
|
+
}
|