@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
@@ -14,7 +14,16 @@
14
14
  * limitations under the License.
15
15
  */
16
16
 
17
- import {PackageManager} from "./node-resolution-result";
17
+ import {
18
+ createNodeResolutionResultMarker,
19
+ findNodeResolutionResult,
20
+ PackageJsonContent,
21
+ PackageLockContent,
22
+ PackageManager,
23
+ readNpmrcConfigs
24
+ } from "./node-resolution-result";
25
+ import {replaceMarkerByKind} from "../markers";
26
+ import {Json} from "../json";
18
27
  import * as fs from "fs";
19
28
  import * as fsp from "fs/promises";
20
29
  import * as path from "path";
@@ -108,7 +117,7 @@ const LOCK_FILE_DETECTION: ReadonlyArray<LockFileDetectionConfig> = [
108
117
  /**
109
118
  * Result of running a package manager command.
110
119
  */
111
- export interface PackageManagerResult {
120
+ interface PackageManagerResult {
112
121
  success: boolean;
113
122
  stdout?: string;
114
123
  stderr?: string;
@@ -118,7 +127,7 @@ export interface PackageManagerResult {
118
127
  /**
119
128
  * Options for running package manager install.
120
129
  */
121
- export interface InstallOptions {
130
+ interface InstallOptions {
122
131
  /** Working directory */
123
132
  cwd: string;
124
133
 
@@ -164,13 +173,6 @@ export function getLockFileDetectionConfig(): ReadonlyArray<LockFileDetectionCon
164
173
  return LOCK_FILE_DETECTION;
165
174
  }
166
175
 
167
- /**
168
- * Gets the configuration for a package manager.
169
- */
170
- export function getPackageManagerConfig(pm: PackageManager): PackageManagerConfig {
171
- return PACKAGE_MANAGER_CONFIGS[pm];
172
- }
173
-
174
176
  /**
175
177
  * Gets the lock file name for a package manager.
176
178
  */
@@ -185,22 +187,10 @@ export function getAllLockFileNames(): string[] {
185
187
  return LOCK_FILE_DETECTION.map(c => c.filename);
186
188
  }
187
189
 
188
- /**
189
- * Checks if a file path is a lock file.
190
- */
191
- export function isLockFile(filePath: string): boolean {
192
- const fileName = path.basename(filePath);
193
- return getAllLockFileNames().includes(fileName);
194
- }
195
-
196
190
  /**
197
191
  * Runs the package manager install command.
198
- *
199
- * @param pm The package manager to use
200
- * @param options Install options
201
- * @returns Result of the install command
202
192
  */
203
- export function runInstall(pm: PackageManager, options: InstallOptions): PackageManagerResult {
193
+ function runInstall(pm: PackageManager, options: InstallOptions): PackageManagerResult {
204
194
  const config = PACKAGE_MANAGER_CONFIGS[pm];
205
195
  const command = options.lockOnly ? config.installLockOnlyCommand : config.installCommand;
206
196
  const [cmd, ...args] = command;
@@ -244,121 +234,6 @@ export function runInstall(pm: PackageManager, options: InstallOptions): Package
244
234
  }
245
235
  }
246
236
 
247
- /**
248
- * Options for adding/upgrading a package.
249
- */
250
- export interface AddPackageOptions {
251
- /** Working directory */
252
- cwd: string;
253
-
254
- /** Package name to add/upgrade */
255
- packageName: string;
256
-
257
- /** Version constraint (e.g., "^5.0.0") */
258
- version: string;
259
-
260
- /** If true, only update lock file without installing to node_modules */
261
- lockOnly?: boolean;
262
-
263
- /** Timeout in milliseconds (default: 120000 = 2 minutes) */
264
- timeout?: number;
265
-
266
- /** Additional environment variables */
267
- env?: Record<string, string>;
268
- }
269
-
270
- /**
271
- * Runs a package manager command to add or upgrade a package.
272
- * This updates both package.json and the lock file.
273
- *
274
- * @param pm The package manager to use
275
- * @param options Add package options
276
- * @returns Result of the command
277
- */
278
- export function runAddPackage(pm: PackageManager, options: AddPackageOptions): PackageManagerResult {
279
- const packageSpec = `${options.packageName}@${options.version}`;
280
-
281
- // Build command based on package manager
282
- let cmd: string;
283
- let args: string[];
284
-
285
- switch (pm) {
286
- case PackageManager.Npm:
287
- cmd = 'npm';
288
- args = ['install', packageSpec];
289
- if (options.lockOnly) {
290
- args.push('--package-lock-only');
291
- }
292
- break;
293
- case PackageManager.YarnClassic:
294
- cmd = 'yarn';
295
- args = ['add', packageSpec];
296
- if (options.lockOnly) {
297
- args.push('--ignore-scripts');
298
- }
299
- break;
300
- case PackageManager.YarnBerry:
301
- cmd = 'yarn';
302
- args = ['add', packageSpec];
303
- if (options.lockOnly) {
304
- args.push('--mode', 'skip-build');
305
- }
306
- break;
307
- case PackageManager.Pnpm:
308
- cmd = 'pnpm';
309
- args = ['add', packageSpec];
310
- if (options.lockOnly) {
311
- args.push('--lockfile-only');
312
- }
313
- break;
314
- case PackageManager.Bun:
315
- cmd = 'bun';
316
- args = ['add', packageSpec];
317
- if (options.lockOnly) {
318
- args.push('--ignore-scripts');
319
- }
320
- break;
321
- }
322
-
323
- try {
324
- const result = spawnSync(cmd, args, {
325
- cwd: options.cwd,
326
- encoding: 'utf-8',
327
- stdio: ['pipe', 'pipe', 'pipe'],
328
- timeout: options.timeout ?? 120000,
329
- env: options.env ? {...process.env, ...options.env} : process.env,
330
- });
331
-
332
- if (result.error) {
333
- return {
334
- success: false,
335
- error: result.error.message,
336
- stderr: result.stderr,
337
- };
338
- }
339
-
340
- if (result.status !== 0) {
341
- return {
342
- success: false,
343
- stdout: result.stdout,
344
- stderr: result.stderr,
345
- error: `Command exited with code ${result.status}`,
346
- };
347
- }
348
-
349
- return {
350
- success: true,
351
- stdout: result.stdout,
352
- stderr: result.stderr,
353
- };
354
- } catch (error: any) {
355
- return {
356
- success: false,
357
- error: error.message,
358
- };
359
- }
360
- }
361
-
362
237
  /**
363
238
  * Runs a package manager list command to get dependency information.
364
239
  *
@@ -390,55 +265,203 @@ export function runList(pm: PackageManager, cwd: string, timeout: number = 30000
390
265
  }
391
266
 
392
267
  /**
393
- * Checks if a package manager is available on the system.
268
+ * Result of running install in a temporary directory.
269
+ */
270
+ export interface TempInstallResult {
271
+ /** Whether the install succeeded */
272
+ success: boolean;
273
+ /** The updated lock file content (if successful and lock file exists) */
274
+ lockFileContent?: string;
275
+ /** Error message (if failed) */
276
+ error?: string;
277
+ }
278
+
279
+ /**
280
+ * Generic accumulator for dependency recipes that run package manager operations.
281
+ * Used by scanning recipes to track state across scanning and editing phases.
394
282
  *
395
- * @param pm The package manager to check
396
- * @returns True if the package manager is available
283
+ * @typeParam T The recipe-specific project update info type
397
284
  */
398
- export function isPackageManagerAvailable(pm: PackageManager): boolean {
399
- const config = PACKAGE_MANAGER_CONFIGS[pm];
400
- const cmd = config.installCommand[0];
285
+ export interface DependencyRecipeAccumulator<T> {
286
+ /** Projects that need updating: packageJsonPath -> update info */
287
+ projectsToUpdate: Map<string, T>;
401
288
 
402
- try {
403
- const result = spawnSync(cmd, ['--version'], {
404
- encoding: 'utf-8',
405
- stdio: ['pipe', 'pipe', 'pipe'],
406
- timeout: 5000,
407
- });
408
- return result.status === 0;
409
- } catch {
410
- return false;
289
+ /** After running package manager, store the updated lock file content */
290
+ updatedLockFiles: Map<string, string>;
291
+
292
+ /** Updated package.json content (after npm install may have modified it) */
293
+ updatedPackageJsons: Map<string, string>;
294
+
295
+ /** Track which projects have been processed (npm install has run) */
296
+ processedProjects: Set<string>;
297
+
298
+ /** Track projects where npm install failed: packageJsonPath -> error message */
299
+ failedProjects: Map<string, string>;
300
+ }
301
+
302
+ /**
303
+ * Creates a new empty accumulator for dependency recipes.
304
+ */
305
+ export function createDependencyRecipeAccumulator<T>(): DependencyRecipeAccumulator<T> {
306
+ return {
307
+ projectsToUpdate: new Map(),
308
+ updatedLockFiles: new Map(),
309
+ updatedPackageJsons: new Map(),
310
+ processedProjects: new Set(),
311
+ failedProjects: new Map()
312
+ };
313
+ }
314
+
315
+ /**
316
+ * Checks if a source path is a lock file and returns the updated content if available.
317
+ * This is a helper for dependency recipes that need to update lock files.
318
+ *
319
+ * @param sourcePath The source path to check
320
+ * @param acc The recipe accumulator containing updated lock file content
321
+ * @returns The updated lock file content if this is a lock file that was updated, undefined otherwise
322
+ */
323
+ export function getUpdatedLockFileContent<T>(
324
+ sourcePath: string,
325
+ acc: DependencyRecipeAccumulator<T>
326
+ ): string | undefined {
327
+ for (const lockFileName of getAllLockFileNames()) {
328
+ if (sourcePath.endsWith(lockFileName)) {
329
+ // Find the corresponding package.json path
330
+ const packageJsonPath = sourcePath.replace(lockFileName, 'package.json');
331
+ const updateInfo = acc.projectsToUpdate.get(packageJsonPath);
332
+
333
+ if (updateInfo && acc.updatedLockFiles.has(sourcePath)) {
334
+ return acc.updatedLockFiles.get(sourcePath);
335
+ }
336
+ break;
337
+ }
411
338
  }
339
+ return undefined;
340
+ }
341
+
342
+ /**
343
+ * Base interface for project update info used by dependency recipes.
344
+ * Recipes extend this with additional fields specific to their needs.
345
+ */
346
+ export interface BaseProjectUpdateInfo {
347
+ /** Absolute path to the project directory */
348
+ projectDir: string;
349
+ /** Relative path to package.json (from source root) */
350
+ packageJsonPath: string;
351
+ /** The package manager used by this project */
352
+ packageManager: PackageManager;
412
353
  }
413
354
 
414
355
  /**
415
- * Gets a human-readable name for a package manager.
356
+ * Stores the result of a package manager install into the accumulator.
357
+ * This handles the common pattern of storing updated lock files and tracking failures.
358
+ *
359
+ * @param result The result from runInstallInTempDir
360
+ * @param acc The recipe accumulator
361
+ * @param updateInfo The project update info (must have packageJsonPath and packageManager)
362
+ * @param modifiedPackageJson The modified package.json content that was used for install
416
363
  */
417
- export function getPackageManagerDisplayName(pm: PackageManager): string {
418
- switch (pm) {
419
- case PackageManager.Npm:
420
- return 'npm';
421
- case PackageManager.YarnClassic:
422
- return 'Yarn Classic';
423
- case PackageManager.YarnBerry:
424
- return 'Yarn Berry';
425
- case PackageManager.Pnpm:
426
- return 'pnpm';
427
- case PackageManager.Bun:
428
- return 'Bun';
364
+ export function storeInstallResult<T extends BaseProjectUpdateInfo>(
365
+ result: TempInstallResult,
366
+ acc: DependencyRecipeAccumulator<T>,
367
+ updateInfo: T,
368
+ modifiedPackageJson: string
369
+ ): void {
370
+ if (result.success) {
371
+ acc.updatedPackageJsons.set(updateInfo.packageJsonPath, modifiedPackageJson);
372
+
373
+ if (result.lockFileContent) {
374
+ const lockFileName = getLockFileName(updateInfo.packageManager);
375
+ const lockFilePath = updateInfo.packageJsonPath.replace('package.json', lockFileName);
376
+ acc.updatedLockFiles.set(lockFilePath, result.lockFileContent);
377
+ }
378
+ } else {
379
+ acc.failedProjects.set(updateInfo.packageJsonPath, result.error || 'Unknown error');
429
380
  }
430
381
  }
431
382
 
432
383
  /**
433
- * Result of running install in a temporary directory.
384
+ * Runs the package manager install for a project if it hasn't been processed yet.
385
+ * Updates the accumulator's processedProjects set after running.
386
+ *
387
+ * @param sourcePath The source path (package.json path) being processed
388
+ * @param acc The recipe accumulator
389
+ * @param runInstall Function that performs the actual install (recipe-specific)
390
+ * @returns The failure message if install failed, undefined otherwise
434
391
  */
435
- export interface TempInstallResult {
436
- /** Whether the install succeeded */
437
- success: boolean;
438
- /** The updated lock file content (if successful and lock file exists) */
439
- lockFileContent?: string;
440
- /** Error message (if failed) */
441
- error?: string;
392
+ export async function runInstallIfNeeded<T>(
393
+ sourcePath: string,
394
+ acc: DependencyRecipeAccumulator<T>,
395
+ runInstall: () => Promise<void>
396
+ ): Promise<string | undefined> {
397
+ if (!acc.processedProjects.has(sourcePath)) {
398
+ await runInstall();
399
+ acc.processedProjects.add(sourcePath);
400
+ }
401
+ return acc.failedProjects.get(sourcePath);
402
+ }
403
+
404
+ /**
405
+ * Updates the NodeResolutionResult marker on a JSON document after a package manager operation.
406
+ * This recreates the marker based on the updated package.json and lock file content.
407
+ *
408
+ * @param doc The JSON document containing the marker
409
+ * @param updateInfo Project update info with paths and package manager
410
+ * @param acc The recipe accumulator containing updated content
411
+ * @returns The document with the updated marker, or unchanged if no existing marker
412
+ */
413
+ export async function updateNodeResolutionMarker<T extends BaseProjectUpdateInfo>(
414
+ doc: Json.Document,
415
+ updateInfo: T & { originalPackageJson: string },
416
+ acc: DependencyRecipeAccumulator<T>
417
+ ): Promise<Json.Document> {
418
+ const existingMarker = findNodeResolutionResult(doc);
419
+ if (!existingMarker) {
420
+ return doc;
421
+ }
422
+
423
+ // Parse the updated package.json and lock file to create new marker
424
+ const updatedPackageJson = acc.updatedPackageJsons.get(updateInfo.packageJsonPath);
425
+ const lockFileName = getLockFileName(updateInfo.packageManager);
426
+ const updatedLockFile = acc.updatedLockFiles.get(
427
+ updateInfo.packageJsonPath.replace('package.json', lockFileName)
428
+ );
429
+
430
+ let packageJsonContent: PackageJsonContent;
431
+ let lockContent: PackageLockContent | undefined;
432
+
433
+ try {
434
+ packageJsonContent = JSON.parse(updatedPackageJson || updateInfo.originalPackageJson);
435
+ } catch {
436
+ return doc; // Failed to parse, keep original marker
437
+ }
438
+
439
+ if (updatedLockFile) {
440
+ try {
441
+ lockContent = JSON.parse(updatedLockFile);
442
+ } catch {
443
+ // Continue without lock file content
444
+ }
445
+ }
446
+
447
+ // Read npmrc configs from the project directory
448
+ const npmrcConfigs = await readNpmrcConfigs(updateInfo.projectDir);
449
+
450
+ // Create new marker
451
+ const newMarker = createNodeResolutionResultMarker(
452
+ existingMarker.path,
453
+ packageJsonContent,
454
+ lockContent,
455
+ existingMarker.workspacePackagePaths,
456
+ existingMarker.packageManager,
457
+ npmrcConfigs.length > 0 ? npmrcConfigs : undefined
458
+ );
459
+
460
+ // Replace the marker in the document
461
+ return {
462
+ ...doc,
463
+ markers: replaceMarkerByKind(doc.markers, newMarker)
464
+ };
442
465
  }
443
466
 
444
467
  /**