@openrewrite/rewrite 8.69.0-20251210-214835 → 8.69.0-20251211-085327

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.
Files changed (50) hide show
  1. package/dist/index.d.ts.map +1 -1
  2. package/dist/index.js +2 -1
  3. package/dist/index.js.map +1 -1
  4. package/dist/javascript/node-resolution-result.d.ts +9 -0
  5. package/dist/javascript/node-resolution-result.d.ts.map +1 -1
  6. package/dist/javascript/node-resolution-result.js +10 -1
  7. package/dist/javascript/node-resolution-result.js.map +1 -1
  8. package/dist/javascript/package-manager.d.ts +76 -89
  9. package/dist/javascript/package-manager.d.ts.map +1 -1
  10. package/dist/javascript/package-manager.js +114 -139
  11. package/dist/javascript/package-manager.js.map +1 -1
  12. package/dist/javascript/recipes/add-dependency.d.ts +57 -0
  13. package/dist/javascript/recipes/add-dependency.d.ts.map +1 -0
  14. package/dist/javascript/recipes/add-dependency.js +404 -0
  15. package/dist/javascript/recipes/add-dependency.js.map +1 -0
  16. package/dist/javascript/recipes/index.d.ts +1 -0
  17. package/dist/javascript/recipes/index.d.ts.map +1 -1
  18. package/dist/javascript/recipes/index.js +1 -0
  19. package/dist/javascript/recipes/index.js.map +1 -1
  20. package/dist/javascript/recipes/upgrade-dependency-version.d.ts +3 -24
  21. package/dist/javascript/recipes/upgrade-dependency-version.d.ts.map +1 -1
  22. package/dist/javascript/recipes/upgrade-dependency-version.js +34 -157
  23. package/dist/javascript/recipes/upgrade-dependency-version.js.map +1 -1
  24. package/dist/javascript/recipes/upgrade-transitive-dependency-version.d.ts +2 -19
  25. package/dist/javascript/recipes/upgrade-transitive-dependency-version.d.ts.map +1 -1
  26. package/dist/javascript/recipes/upgrade-transitive-dependency-version.js +21 -137
  27. package/dist/javascript/recipes/upgrade-transitive-dependency-version.js.map +1 -1
  28. package/dist/javascript/search/find-dependency.d.ts.map +1 -1
  29. package/dist/javascript/search/find-dependency.js +8 -47
  30. package/dist/javascript/search/find-dependency.js.map +1 -1
  31. package/dist/json/tree.d.ts +30 -0
  32. package/dist/json/tree.d.ts.map +1 -1
  33. package/dist/json/tree.js +113 -0
  34. package/dist/json/tree.js.map +1 -1
  35. package/dist/parser.d.ts +9 -0
  36. package/dist/parser.d.ts.map +1 -1
  37. package/dist/parser.js +27 -0
  38. package/dist/parser.js.map +1 -1
  39. package/dist/version.txt +1 -1
  40. package/package.json +1 -1
  41. package/src/index.ts +2 -1
  42. package/src/javascript/node-resolution-result.ts +16 -0
  43. package/src/javascript/package-manager.ts +197 -174
  44. package/src/javascript/recipes/add-dependency.ts +467 -0
  45. package/src/javascript/recipes/index.ts +1 -0
  46. package/src/javascript/recipes/upgrade-dependency-version.ts +52 -199
  47. package/src/javascript/recipes/upgrade-transitive-dependency-version.ts +39 -165
  48. package/src/javascript/search/find-dependency.ts +13 -52
  49. package/src/json/tree.ts +98 -1
  50. package/src/parser.ts +17 -0
@@ -17,25 +17,26 @@
17
17
  import {Option, ScanningRecipe} from "../../recipe";
18
18
  import {ExecutionContext} from "../../execution";
19
19
  import {TreeVisitor} from "../../visitor";
20
- import {Json, JsonParser, JsonVisitor} from "../../json";
20
+ import {getMemberKeyName, isLiteral, Json, JsonParser, JsonVisitor} from "../../json";
21
21
  import {
22
- createNodeResolutionResultMarker,
22
+ allDependencyScopes,
23
+ DependencyScope,
23
24
  findNodeResolutionResult,
24
- PackageJsonContent,
25
- PackageLockContent,
26
- PackageManager,
27
- readNpmrcConfigs
25
+ PackageManager
28
26
  } from "../node-resolution-result";
29
27
  import * as path from "path";
30
28
  import * as semver from "semver";
31
29
  import {markupWarn, replaceMarkerByKind} from "../../markers";
32
30
  import {TreePrinters} from "../../print";
33
- import {getAllLockFileNames, getLockFileName, runInstallInTempDir} from "../package-manager";
34
-
35
- /**
36
- * Represents a dependency scope in package.json
37
- */
38
- type DependencyScope = 'dependencies' | 'devDependencies' | 'peerDependencies' | 'optionalDependencies';
31
+ import {
32
+ createDependencyRecipeAccumulator,
33
+ DependencyRecipeAccumulator,
34
+ getUpdatedLockFileContent,
35
+ runInstallIfNeeded,
36
+ runInstallInTempDir,
37
+ storeInstallResult,
38
+ updateNodeResolutionMarker
39
+ } from "../package-manager";
39
40
 
40
41
  /**
41
42
  * Information about a project that needs updating
@@ -62,25 +63,7 @@ interface ProjectUpdateInfo {
62
63
  skipInstall: boolean;
63
64
  }
64
65
 
65
- /**
66
- * Accumulator for tracking state across scanning and editing phases
67
- */
68
- interface Accumulator {
69
- /** Projects that need updating: packageJsonPath -> update info */
70
- projectsToUpdate: Map<string, ProjectUpdateInfo>;
71
-
72
- /** After running package manager, store the updated lock file content */
73
- updatedLockFiles: Map<string, string>;
74
-
75
- /** Updated package.json content (after npm install may have modified it) */
76
- updatedPackageJsons: Map<string, string>;
77
-
78
- /** Track which projects have been processed (npm install has run) */
79
- processedProjects: Set<string>;
80
-
81
- /** Track projects where npm install failed: packageJsonPath -> error message */
82
- failedProjects: Map<string, string>;
83
- }
66
+ type Accumulator = DependencyRecipeAccumulator<ProjectUpdateInfo>;
84
67
 
85
68
  /**
86
69
  * Upgrades the version of a direct dependency in package.json and updates the lock file.
@@ -103,26 +86,20 @@ export class UpgradeDependencyVersion extends ScanningRecipe<Accumulator> {
103
86
 
104
87
  @Option({
105
88
  displayName: "Package name",
106
- description: "The name of the npm package to upgrade (e.g., 'lodash', '@types/node')",
89
+ description: "The name of the npm package to upgrade (e.g., `lodash`, `@types/node`)",
107
90
  example: "lodash"
108
91
  })
109
92
  packageName!: string;
110
93
 
111
94
  @Option({
112
- displayName: "New version",
113
- description: "The new version constraint to set (e.g., '^5.0.0', '~2.1.0', '3.0.0')",
95
+ displayName: "Version",
96
+ description: "The version constraint to set (e.g., `^5.0.0`, `~2.1.0`, `3.0.0`)",
114
97
  example: "^5.0.0"
115
98
  })
116
99
  newVersion!: string;
117
100
 
118
101
  initialValue(_ctx: ExecutionContext): Accumulator {
119
- return {
120
- projectsToUpdate: new Map(),
121
- updatedLockFiles: new Map(),
122
- updatedPackageJsons: new Map(),
123
- processedProjects: new Set(),
124
- failedProjects: new Map()
125
- };
102
+ return createDependencyRecipeAccumulator();
126
103
  }
127
104
 
128
105
  /**
@@ -178,7 +155,7 @@ export class UpgradeDependencyVersion extends ScanningRecipe<Accumulator> {
178
155
  const pm = marker.packageManager ?? PackageManager.Npm;
179
156
 
180
157
  // Check each dependency scope for the target package
181
- const scopes: DependencyScope[] = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'];
158
+ const scopes = allDependencyScopes;
182
159
  let foundScope: DependencyScope | undefined;
183
160
  let currentVersion: string | undefined;
184
161
 
@@ -244,15 +221,13 @@ export class UpgradeDependencyVersion extends ScanningRecipe<Accumulator> {
244
221
  return doc; // This package.json doesn't need updating
245
222
  }
246
223
 
247
- // Run package manager install if we haven't processed this project yet
224
+ // Run package manager install if needed, check for failure
248
225
  // Skip if the resolved version already satisfies the new constraint
249
- if (!updateInfo.skipInstall && !acc.processedProjects.has(sourcePath)) {
250
- await recipe.runPackageManagerInstall(acc, updateInfo, ctx);
251
- acc.processedProjects.add(sourcePath);
252
- }
253
-
254
- // Check if the install failed - if so, don't update, just add warning
255
- const failureMessage = acc.failedProjects.get(sourcePath);
226
+ const failureMessage = updateInfo.skipInstall
227
+ ? undefined
228
+ : await runInstallIfNeeded(sourcePath, acc, () =>
229
+ recipe.runPackageManagerInstall(acc, updateInfo, ctx)
230
+ );
256
231
  if (failureMessage) {
257
232
  return markupWarn(
258
233
  doc,
@@ -270,54 +245,24 @@ export class UpgradeDependencyVersion extends ScanningRecipe<Accumulator> {
270
245
  const modifiedDoc = await visitor.visit(doc, undefined) as Json.Document;
271
246
 
272
247
  // Update the NodeResolutionResult marker
273
- return recipe.updateMarker(modifiedDoc, updateInfo, acc);
248
+ if (updateInfo.skipInstall) {
249
+ // Just update the versionConstraint in the marker - resolved version is unchanged
250
+ return recipe.updateMarkerVersionConstraint(modifiedDoc, updateInfo);
251
+ }
252
+ return updateNodeResolutionMarker(modifiedDoc, updateInfo, acc);
274
253
  }
275
254
 
276
255
  // Handle lock files for all package managers
277
- for (const lockFileName of getAllLockFileNames()) {
278
- if (sourcePath.endsWith(lockFileName)) {
279
- // Find the corresponding package.json path
280
- const packageJsonPath = sourcePath.replace(lockFileName, 'package.json');
281
- const updateInfo = acc.projectsToUpdate.get(packageJsonPath);
282
-
283
- if (updateInfo && acc.updatedLockFiles.has(sourcePath)) {
284
- // Parse the updated lock file content and return it
285
- const updatedContent = acc.updatedLockFiles.get(sourcePath)!;
286
- return this.parseUpdatedLockFile(doc, updatedContent);
287
- }
288
- break;
289
- }
256
+ const updatedLockContent = getUpdatedLockFileContent(sourcePath, acc);
257
+ if (updatedLockContent) {
258
+ return await new JsonParser({}).parseOne({
259
+ text: updatedLockContent,
260
+ sourcePath: doc.sourcePath
261
+ }) as Json.Document;
290
262
  }
291
263
 
292
264
  return doc;
293
265
  }
294
-
295
- /**
296
- * Parses updated lock file content and creates a new document.
297
- */
298
- private async parseUpdatedLockFile(
299
- originalDoc: Json.Document,
300
- updatedContent: string
301
- ): Promise<Json.Document> {
302
- // Parse the updated content using JsonParser
303
- const parser = new JsonParser({});
304
- const parsed: Json.Document[] = [];
305
-
306
- for await (const sf of parser.parse({text: updatedContent, sourcePath: originalDoc.sourcePath})) {
307
- parsed.push(sf as Json.Document);
308
- }
309
-
310
- if (parsed.length > 0) {
311
- // Preserve the original source path and markers
312
- return {
313
- ...parsed[0],
314
- sourcePath: originalDoc.sourcePath,
315
- markers: originalDoc.markers
316
- };
317
- }
318
-
319
- return originalDoc;
320
- }
321
266
  };
322
267
  }
323
268
 
@@ -343,20 +288,7 @@ export class UpgradeDependencyVersion extends ScanningRecipe<Accumulator> {
343
288
  modifiedPackageJson
344
289
  );
345
290
 
346
- if (result.success) {
347
- // Store the modified package.json (we'll use our visitor for actual output)
348
- acc.updatedPackageJsons.set(updateInfo.packageJsonPath, modifiedPackageJson);
349
-
350
- // Store the updated lock file content
351
- if (result.lockFileContent) {
352
- const lockFileName = getLockFileName(updateInfo.packageManager);
353
- const lockFilePath = updateInfo.packageJsonPath.replace('package.json', lockFileName);
354
- acc.updatedLockFiles.set(lockFilePath, result.lockFileContent);
355
- }
356
- } else {
357
- // Track the failure - don't update package.json, the version likely doesn't exist
358
- acc.failedProjects.set(updateInfo.packageJsonPath, result.error || 'Unknown error');
359
- }
291
+ storeInstallResult(result, acc, updateInfo, modifiedPackageJson);
360
292
  }
361
293
 
362
294
  /**
@@ -378,91 +310,29 @@ export class UpgradeDependencyVersion extends ScanningRecipe<Accumulator> {
378
310
  }
379
311
 
380
312
  /**
381
- * Updates the NodeResolutionResult marker with new dependency information.
313
+ * Updates just the versionConstraint in the marker for the target dependency.
314
+ * Used when skipInstall is true - the resolved version is unchanged.
382
315
  */
383
- private async updateMarker(
316
+ private updateMarkerVersionConstraint(
384
317
  doc: Json.Document,
385
- updateInfo: ProjectUpdateInfo,
386
- acc: Accumulator
387
- ): Promise<Json.Document> {
318
+ updateInfo: ProjectUpdateInfo
319
+ ): Json.Document {
388
320
  const existingMarker = findNodeResolutionResult(doc);
389
321
  if (!existingMarker) {
390
322
  return doc;
391
323
  }
392
324
 
393
- // If we skipped install, just update the versionConstraint in the marker
394
- // The resolved version is already correct, we only changed the constraint
395
- if (updateInfo.skipInstall) {
396
- return this.updateMarkerVersionConstraint(doc, existingMarker, updateInfo);
397
- }
398
-
399
- // Parse the updated package.json and lock file to create new marker
400
- const updatedPackageJson = acc.updatedPackageJsons.get(updateInfo.packageJsonPath);
401
- const lockFileName = getLockFileName(updateInfo.packageManager);
402
- const updatedLockFile = acc.updatedLockFiles.get(
403
- updateInfo.packageJsonPath.replace('package.json', lockFileName)
325
+ // Update the versionConstraint for the target dependency
326
+ const deps = existingMarker[updateInfo.dependencyScope];
327
+ const updatedDeps = deps?.map(dep =>
328
+ dep.name === this.packageName
329
+ ? {...dep, versionConstraint: updateInfo.newVersion}
330
+ : dep
404
331
  );
405
332
 
406
- let packageJsonContent: PackageJsonContent;
407
- let lockContent: PackageLockContent | undefined;
408
-
409
- try {
410
- packageJsonContent = JSON.parse(updatedPackageJson || updateInfo.originalPackageJson);
411
- } catch {
412
- return doc; // Failed to parse, keep original marker
413
- }
414
-
415
- if (updatedLockFile) {
416
- try {
417
- lockContent = JSON.parse(updatedLockFile);
418
- } catch {
419
- // Continue without lock file content
420
- }
421
- }
422
-
423
- // Read npmrc configs from the project directory
424
- const npmrcConfigs = await readNpmrcConfigs(updateInfo.projectDir);
425
-
426
- // Create new marker
427
- const newMarker = createNodeResolutionResultMarker(
428
- existingMarker.path,
429
- packageJsonContent,
430
- lockContent,
431
- existingMarker.workspacePackagePaths,
432
- existingMarker.packageManager,
433
- npmrcConfigs.length > 0 ? npmrcConfigs : undefined
434
- );
435
-
436
- // Replace the marker in the document
437
- return {
438
- ...doc,
439
- markers: replaceMarkerByKind(doc.markers, newMarker)
440
- };
441
- }
442
-
443
- /**
444
- * Updates just the versionConstraint in the marker for the target dependency.
445
- * Used when skipInstall is true - the resolved version is unchanged.
446
- */
447
- private updateMarkerVersionConstraint(
448
- doc: Json.Document,
449
- existingMarker: any,
450
- updateInfo: ProjectUpdateInfo
451
- ): Json.Document {
452
- // Create updated dependency lists with the new versionConstraint
453
- const updateDeps = (deps: any[] | undefined) => {
454
- if (!deps) return deps;
455
- return deps.map(dep => {
456
- if (dep.name === this.packageName) {
457
- return {...dep, versionConstraint: updateInfo.newVersion};
458
- }
459
- return dep;
460
- });
461
- };
462
-
463
333
  const newMarker = {
464
334
  ...existingMarker,
465
- [updateInfo.dependencyScope]: updateDeps(existingMarker[updateInfo.dependencyScope])
335
+ [updateInfo.dependencyScope]: updatedDeps
466
336
  };
467
337
 
468
338
  return {
@@ -490,7 +360,7 @@ class UpdateVersionVisitor extends JsonVisitor<void> {
490
360
 
491
361
  protected async visitMember(member: Json.Member, p: void): Promise<Json | undefined> {
492
362
  // Check if we're entering the target scope
493
- const keyName = this.getMemberKeyName(member);
363
+ const keyName = getMemberKeyName(member);
494
364
 
495
365
  if (keyName === this.targetScope) {
496
366
  // We're entering the dependencies scope
@@ -509,33 +379,16 @@ class UpdateVersionVisitor extends JsonVisitor<void> {
509
379
  return super.visitMember(member, p);
510
380
  }
511
381
 
512
- private getMemberKeyName(member: Json.Member): string | undefined {
513
- const key = member.key.element;
514
- if (key.kind === Json.Kind.Literal) {
515
- // Remove quotes from string literal
516
- const source = (key as Json.Literal).source;
517
- if (source.startsWith('"') && source.endsWith('"')) {
518
- return source.slice(1, -1);
519
- }
520
- return source;
521
- } else if (key.kind === Json.Kind.Identifier) {
522
- return (key as Json.Identifier).name;
523
- }
524
- return undefined;
525
- }
526
-
527
382
  private updateVersion(member: Json.Member): Json.Member {
528
383
  const value = member.value;
529
384
 
530
- if (value.kind !== Json.Kind.Literal) {
385
+ if (!isLiteral(value)) {
531
386
  return member; // Not a literal value, can't update
532
387
  }
533
388
 
534
- const literal = value as Json.Literal;
535
-
536
389
  // Create new literal with updated version
537
390
  const newLiteral: Json.Literal = {
538
- ...literal,
391
+ ...value,
539
392
  source: `"${this.newVersion}"`,
540
393
  value: this.newVersion
541
394
  };
@@ -19,19 +19,24 @@ import {ExecutionContext} from "../../execution";
19
19
  import {TreeVisitor} from "../../visitor";
20
20
  import {Json, JsonParser, JsonVisitor} from "../../json";
21
21
  import {
22
- createNodeResolutionResultMarker,
22
+ allDependencyScopes,
23
23
  findNodeResolutionResult,
24
24
  NodeResolutionResultQueries,
25
- PackageJsonContent,
26
- PackageLockContent,
27
- PackageManager,
28
- readNpmrcConfigs
25
+ PackageManager
29
26
  } from "../node-resolution-result";
30
27
  import * as path from "path";
31
28
  import * as semver from "semver";
32
- import {markupWarn, replaceMarkerByKind} from "../../markers";
29
+ import {markupWarn} from "../../markers";
33
30
  import {TreePrinters} from "../../print";
34
- import {getAllLockFileNames, getLockFileName, runInstallInTempDir} from "../package-manager";
31
+ import {
32
+ createDependencyRecipeAccumulator,
33
+ DependencyRecipeAccumulator,
34
+ getUpdatedLockFileContent,
35
+ runInstallIfNeeded,
36
+ runInstallInTempDir,
37
+ storeInstallResult,
38
+ updateNodeResolutionMarker
39
+ } from "../package-manager";
35
40
  import {applyOverrideToPackageJson, DependencyPathSegment, parseDependencyPath} from "../dependency-manager";
36
41
 
37
42
  /**
@@ -57,25 +62,7 @@ interface ProjectUpdateInfo {
57
62
  dependencyPathSegments?: DependencyPathSegment[];
58
63
  }
59
64
 
60
- /**
61
- * Accumulator for tracking state across scanning and editing phases
62
- */
63
- interface Accumulator {
64
- /** Projects that need updating: packageJsonPath -> update info */
65
- projectsToUpdate: Map<string, ProjectUpdateInfo>;
66
-
67
- /** After running package manager, store the updated lock file content */
68
- updatedLockFiles: Map<string, string>;
69
-
70
- /** Updated package.json content (after npm install may have modified it) */
71
- updatedPackageJsons: Map<string, string>;
72
-
73
- /** Track which projects have been processed (npm install has run) */
74
- processedProjects: Set<string>;
75
-
76
- /** Track projects where npm install failed: packageJsonPath -> error message */
77
- failedProjects: Map<string, string>;
78
- }
65
+ type Accumulator = DependencyRecipeAccumulator<ProjectUpdateInfo>;
79
66
 
80
67
  /**
81
68
  * Upgrades the version of a transitive dependency by adding override entries to package.json.
@@ -98,14 +85,14 @@ export class UpgradeTransitiveDependencyVersion extends ScanningRecipe<Accumulat
98
85
 
99
86
  @Option({
100
87
  displayName: "Package name",
101
- description: "The name of the npm package to upgrade (e.g., 'lodash', '@types/node')",
88
+ description: "The name of the npm package to upgrade (e.g., `lodash`, `@types/node`)",
102
89
  example: "lodash"
103
90
  })
104
91
  packageName!: string;
105
92
 
106
93
  @Option({
107
- displayName: "New version",
108
- description: "The new version constraint to set (e.g., '^5.0.0', '~2.1.0', '3.0.0')",
94
+ displayName: "Version",
95
+ description: "The version constraint to set (e.g., `^5.0.0`, `~2.1.0`, `3.0.0`)",
109
96
  example: "^5.0.0"
110
97
  })
111
98
  newVersion!: string;
@@ -119,13 +106,7 @@ export class UpgradeTransitiveDependencyVersion extends ScanningRecipe<Accumulat
119
106
  dependencyPath?: string;
120
107
 
121
108
  initialValue(_ctx: ExecutionContext): Accumulator {
122
- return {
123
- projectsToUpdate: new Map(),
124
- updatedLockFiles: new Map(),
125
- updatedPackageJsons: new Map(),
126
- processedProjects: new Set(),
127
- failedProjects: new Map()
128
- };
109
+ return createDependencyRecipeAccumulator();
129
110
  }
130
111
 
131
112
  async scanner(acc: Accumulator): Promise<TreeVisitor<any, ExecutionContext>> {
@@ -148,8 +129,7 @@ export class UpgradeTransitiveDependencyVersion extends ScanningRecipe<Accumulat
148
129
  const pm = marker.packageManager ?? PackageManager.Npm;
149
130
 
150
131
  // Check if package is a direct dependency - if so, skip (use UpgradeDependencyVersion instead)
151
- const scopes = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'] as const;
152
- for (const scope of scopes) {
132
+ for (const scope of allDependencyScopes) {
153
133
  const deps = marker[scope];
154
134
  if (deps?.find(d => d.name === recipe.packageName)) {
155
135
  // Package is a direct dependency, don't add override
@@ -218,14 +198,10 @@ export class UpgradeTransitiveDependencyVersion extends ScanningRecipe<Accumulat
218
198
  return doc; // This package.json doesn't need updating
219
199
  }
220
200
 
221
- // Run package manager install if we haven't processed this project yet
222
- if (!acc.processedProjects.has(sourcePath)) {
223
- await recipe.runPackageManagerInstall(acc, updateInfo, ctx);
224
- acc.processedProjects.add(sourcePath);
225
- }
226
-
227
- // Check if the install failed - if so, don't update, just add warning
228
- const failureMessage = acc.failedProjects.get(sourcePath);
201
+ // Run package manager install if needed, check for failure
202
+ const failureMessage = await runInstallIfNeeded(sourcePath, acc, () =>
203
+ recipe.runPackageManagerInstall(acc, updateInfo, ctx)
204
+ );
229
205
  if (failureMessage) {
230
206
  return markupWarn(
231
207
  doc,
@@ -238,23 +214,16 @@ export class UpgradeTransitiveDependencyVersion extends ScanningRecipe<Accumulat
238
214
  const modifiedDoc = await this.addOverrideEntry(doc, updateInfo);
239
215
 
240
216
  // Update the NodeResolutionResult marker
241
- return recipe.updateMarker(modifiedDoc, updateInfo, acc);
217
+ return updateNodeResolutionMarker(modifiedDoc, updateInfo, acc);
242
218
  }
243
219
 
244
220
  // Handle lock files for all package managers
245
- for (const lockFileName of getAllLockFileNames()) {
246
- if (sourcePath.endsWith(lockFileName)) {
247
- // Find the corresponding package.json path
248
- const packageJsonPath = sourcePath.replace(lockFileName, 'package.json');
249
- const updateInfo = acc.projectsToUpdate.get(packageJsonPath);
250
-
251
- if (updateInfo && acc.updatedLockFiles.has(sourcePath)) {
252
- // Parse the updated lock file content and return it
253
- const updatedContent = acc.updatedLockFiles.get(sourcePath)!;
254
- return this.parseUpdatedLockFile(doc, updatedContent);
255
- }
256
- break;
257
- }
221
+ const updatedLockContent = getUpdatedLockFileContent(sourcePath, acc);
222
+ if (updatedLockContent) {
223
+ return await new JsonParser({}).parseOne({
224
+ text: updatedLockContent,
225
+ sourcePath: doc.sourcePath
226
+ }) as Json.Document;
258
227
  }
259
228
 
260
229
  return doc;
@@ -291,46 +260,15 @@ export class UpgradeTransitiveDependencyVersion extends ScanningRecipe<Accumulat
291
260
  const newContent = JSON.stringify(modifiedPackageJson, null, indent);
292
261
 
293
262
  // Re-parse with JsonParser to get proper AST
294
- const parser = new JsonParser({});
295
- const parsed: Json.Document[] = [];
296
- for await (const sf of parser.parse({text: newContent, sourcePath: doc.sourcePath})) {
297
- parsed.push(sf as Json.Document);
298
- }
299
-
300
- if (parsed.length > 0) {
301
- return {
302
- ...parsed[0],
303
- sourcePath: doc.sourcePath,
304
- markers: doc.markers
305
- };
306
- }
307
-
308
- return doc;
309
- }
310
-
311
- /**
312
- * Parses updated lock file content and creates a new document.
313
- */
314
- private async parseUpdatedLockFile(
315
- originalDoc: Json.Document,
316
- updatedContent: string
317
- ): Promise<Json.Document> {
318
- const parser = new JsonParser({});
319
- const parsed: Json.Document[] = [];
320
-
321
- for await (const sf of parser.parse({text: updatedContent, sourcePath: originalDoc.sourcePath})) {
322
- parsed.push(sf as Json.Document);
323
- }
324
-
325
- if (parsed.length > 0) {
326
- return {
327
- ...parsed[0],
328
- sourcePath: originalDoc.sourcePath,
329
- markers: originalDoc.markers
330
- };
331
- }
332
-
333
- return originalDoc;
263
+ const parsed = await new JsonParser({}).parseOne({
264
+ text: newContent,
265
+ sourcePath: doc.sourcePath
266
+ }) as Json.Document;
267
+
268
+ return {
269
+ ...parsed,
270
+ markers: doc.markers
271
+ };
334
272
  }
335
273
  };
336
274
  }
@@ -355,18 +293,7 @@ export class UpgradeTransitiveDependencyVersion extends ScanningRecipe<Accumulat
355
293
  modifiedPackageJson
356
294
  );
357
295
 
358
- if (result.success) {
359
- acc.updatedPackageJsons.set(updateInfo.packageJsonPath, modifiedPackageJson);
360
-
361
- // Store the updated lock file content
362
- if (result.lockFileContent) {
363
- const lockFileName = getLockFileName(updateInfo.packageManager);
364
- const lockFilePath = updateInfo.packageJsonPath.replace('package.json', lockFileName);
365
- acc.updatedLockFiles.set(lockFilePath, result.lockFileContent);
366
- }
367
- } else {
368
- acc.failedProjects.set(updateInfo.packageJsonPath, result.error || 'Unknown error');
369
- }
296
+ storeInstallResult(result, acc, updateInfo, modifiedPackageJson);
370
297
  }
371
298
 
372
299
  /**
@@ -389,57 +316,4 @@ export class UpgradeTransitiveDependencyVersion extends ScanningRecipe<Accumulat
389
316
  return JSON.stringify(packageJson, null, 2);
390
317
  }
391
318
 
392
- /**
393
- * Updates the NodeResolutionResult marker with new dependency information.
394
- */
395
- private async updateMarker(
396
- doc: Json.Document,
397
- updateInfo: ProjectUpdateInfo,
398
- acc: Accumulator
399
- ): Promise<Json.Document> {
400
- const existingMarker = findNodeResolutionResult(doc);
401
- if (!existingMarker) {
402
- return doc;
403
- }
404
-
405
- // Parse the updated package.json and lock file to create new marker
406
- const updatedPackageJson = acc.updatedPackageJsons.get(updateInfo.packageJsonPath);
407
- const lockFileName = getLockFileName(updateInfo.packageManager);
408
- const updatedLockFile = acc.updatedLockFiles.get(
409
- updateInfo.packageJsonPath.replace('package.json', lockFileName)
410
- );
411
-
412
- let packageJsonContent: PackageJsonContent;
413
- let lockContent: PackageLockContent | undefined;
414
-
415
- try {
416
- packageJsonContent = JSON.parse(updatedPackageJson || updateInfo.originalPackageJson);
417
- } catch {
418
- return doc;
419
- }
420
-
421
- if (updatedLockFile) {
422
- try {
423
- lockContent = JSON.parse(updatedLockFile);
424
- } catch {
425
- // Continue without lock file content
426
- }
427
- }
428
-
429
- const npmrcConfigs = await readNpmrcConfigs(updateInfo.projectDir);
430
-
431
- const newMarker = createNodeResolutionResultMarker(
432
- existingMarker.path,
433
- packageJsonContent,
434
- lockContent,
435
- existingMarker.workspacePackagePaths,
436
- existingMarker.packageManager,
437
- npmrcConfigs.length > 0 ? npmrcConfigs : undefined
438
- );
439
-
440
- return {
441
- ...doc,
442
- markers: replaceMarkerByKind(doc.markers, newMarker)
443
- };
444
- }
445
319
  }