@openrewrite/rewrite 8.70.3 → 8.70.4

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 (46) hide show
  1. package/dist/javascript/add-import.d.ts +5 -0
  2. package/dist/javascript/add-import.d.ts.map +1 -1
  3. package/dist/javascript/add-import.js +22 -9
  4. package/dist/javascript/add-import.js.map +1 -1
  5. package/dist/javascript/assertions.d.ts.map +1 -1
  6. package/dist/javascript/assertions.js +45 -13
  7. package/dist/javascript/assertions.js.map +1 -1
  8. package/dist/javascript/dependency-workspace.d.ts +5 -0
  9. package/dist/javascript/dependency-workspace.d.ts.map +1 -1
  10. package/dist/javascript/dependency-workspace.js +47 -13
  11. package/dist/javascript/dependency-workspace.js.map +1 -1
  12. package/dist/javascript/package-json-parser.d.ts +24 -0
  13. package/dist/javascript/package-json-parser.d.ts.map +1 -1
  14. package/dist/javascript/package-json-parser.js +137 -31
  15. package/dist/javascript/package-json-parser.js.map +1 -1
  16. package/dist/javascript/package-manager.d.ts +45 -7
  17. package/dist/javascript/package-manager.d.ts.map +1 -1
  18. package/dist/javascript/package-manager.js +83 -45
  19. package/dist/javascript/package-manager.js.map +1 -1
  20. package/dist/javascript/recipes/add-dependency.d.ts +7 -3
  21. package/dist/javascript/recipes/add-dependency.d.ts.map +1 -1
  22. package/dist/javascript/recipes/add-dependency.js +71 -13
  23. package/dist/javascript/recipes/add-dependency.js.map +1 -1
  24. package/dist/javascript/recipes/upgrade-dependency-version.d.ts +7 -3
  25. package/dist/javascript/recipes/upgrade-dependency-version.d.ts.map +1 -1
  26. package/dist/javascript/recipes/upgrade-dependency-version.js +71 -13
  27. package/dist/javascript/recipes/upgrade-dependency-version.js.map +1 -1
  28. package/dist/javascript/recipes/upgrade-transitive-dependency-version.d.ts +7 -3
  29. package/dist/javascript/recipes/upgrade-transitive-dependency-version.d.ts.map +1 -1
  30. package/dist/javascript/recipes/upgrade-transitive-dependency-version.js +71 -13
  31. package/dist/javascript/recipes/upgrade-transitive-dependency-version.js.map +1 -1
  32. package/dist/path-utils.d.ts +45 -0
  33. package/dist/path-utils.d.ts.map +1 -0
  34. package/dist/path-utils.js +210 -0
  35. package/dist/path-utils.js.map +1 -0
  36. package/dist/version.txt +1 -1
  37. package/package.json +1 -1
  38. package/src/javascript/add-import.ts +28 -7
  39. package/src/javascript/assertions.ts +48 -6
  40. package/src/javascript/dependency-workspace.ts +66 -13
  41. package/src/javascript/package-json-parser.ts +169 -39
  42. package/src/javascript/package-manager.ts +120 -52
  43. package/src/javascript/recipes/add-dependency.ts +89 -17
  44. package/src/javascript/recipes/upgrade-dependency-version.ts +89 -17
  45. package/src/javascript/recipes/upgrade-transitive-dependency-version.ts +89 -17
  46. package/src/path-utils.ts +208 -0
@@ -29,6 +29,8 @@ import * as fsp from "fs/promises";
29
29
  import * as path from "path";
30
30
  import * as YAML from "yaml";
31
31
  import {getLockFileDetectionConfig, runList} from "./package-manager";
32
+ import picomatch from "picomatch";
33
+ import {DEFAULT_DIR_EXCLUSIONS, walkDirs} from "../path-utils";
32
34
 
33
35
  /**
34
36
  * Bun.lock package entry metadata.
@@ -140,52 +142,42 @@ export class PackageJsonParser extends Parser {
140
142
  }
141
143
 
142
144
  async *parse(...inputs: ParserInput[]): AsyncGenerator<SourceFile> {
143
- // Group inputs by directory to share NodeResolutionResult markers
144
- const inputsByDir = new Map<string, ParserInput[]>();
145
+ // Cache markers by directory to share NodeResolutionResult markers
146
+ // but maintain input order for output
147
+ const markersByDir = new Map<string, NodeResolutionResult | null>();
145
148
 
149
+ // Process each input in order, caching markers per directory
146
150
  for (const input of inputs) {
147
151
  const filePath = parserInputFile(input);
148
152
  const dir = path.dirname(filePath);
149
153
 
150
- if (!inputsByDir.has(dir)) {
151
- inputsByDir.set(dir, []);
152
- }
153
- inputsByDir.get(dir)!.push(input);
154
- }
155
-
156
- // Process each directory's package.json files
157
- for (const [dir, dirInputs] of inputsByDir) {
158
- // Create a shared marker for this directory
159
- let marker: NodeResolutionResult | null = null;
160
-
161
- for (const input of dirInputs) {
162
- // Parse as JSON first
163
- const jsonGenerator = this.jsonParser.parse(input);
164
- const jsonResult = await jsonGenerator.next();
165
-
166
- if (jsonResult.done || !jsonResult.value) {
167
- continue;
168
- }
154
+ // Parse as JSON first
155
+ const jsonGenerator = this.jsonParser.parse(input);
156
+ const jsonResult = await jsonGenerator.next();
169
157
 
170
- const jsonDoc = jsonResult.value as Json.Document;
158
+ if (jsonResult.done || !jsonResult.value) {
159
+ continue;
160
+ }
171
161
 
172
- // Create NodeResolutionResult marker if not already created for this directory
173
- if (!marker) {
174
- marker = await this.createMarker(input, dir);
175
- }
162
+ const jsonDoc = jsonResult.value as Json.Document;
176
163
 
177
- // Attach the marker to the JSON document
178
- if (marker) {
179
- yield {
180
- ...jsonDoc,
181
- markers: {
182
- ...jsonDoc.markers,
183
- markers: [...jsonDoc.markers.markers, marker]
184
- }
185
- };
186
- } else {
187
- yield jsonDoc;
188
- }
164
+ // Create NodeResolutionResult marker if not already created for this directory
165
+ if (!markersByDir.has(dir)) {
166
+ markersByDir.set(dir, await this.createMarker(input, dir));
167
+ }
168
+ const marker = markersByDir.get(dir)!;
169
+
170
+ // Attach the marker to the JSON document
171
+ if (marker) {
172
+ yield {
173
+ ...jsonDoc,
174
+ markers: {
175
+ ...jsonDoc.markers,
176
+ markers: [...jsonDoc.markers.markers, marker]
177
+ }
178
+ };
179
+ } else {
180
+ yield jsonDoc;
189
181
  }
190
182
  }
191
183
  }
@@ -222,11 +214,24 @@ export class PackageJsonParser extends Parser {
222
214
  const projectDir = this.relativeTo || dir;
223
215
  const npmrcConfigs = await readNpmrcConfigs(projectDir);
224
216
 
217
+ // Detect workspace member paths if this is a workspace root
218
+ let workspacePackagePaths: string[] | undefined;
219
+ if (packageJson.workspaces) {
220
+ const absoluteDir = this.relativeTo && !path.isAbsolute(dir)
221
+ ? path.resolve(this.relativeTo, dir)
222
+ : dir;
223
+ workspacePackagePaths = await this.resolveWorkspacePackagePaths(
224
+ packageJson.workspaces,
225
+ absoluteDir,
226
+ this.relativeTo
227
+ );
228
+ }
229
+
225
230
  return createNodeResolutionResultMarker(
226
231
  relativePath,
227
232
  packageJson,
228
233
  lockContent,
229
- undefined,
234
+ workspacePackagePaths,
230
235
  packageManager,
231
236
  npmrcConfigs.length > 0 ? npmrcConfigs : undefined
232
237
  );
@@ -236,6 +241,131 @@ export class PackageJsonParser extends Parser {
236
241
  }
237
242
  }
238
243
 
244
+ /**
245
+ * Resolves workspace glob patterns to actual package.json paths.
246
+ *
247
+ * Workspaces can be specified as:
248
+ * - Array of globs: ["packages/*", "apps/*", "packages/**", "{apps,libs}/*"]
249
+ * - Object with packages array: { packages: ["packages/*"] }
250
+ * - Negation patterns: ["packages/*", "!packages/internal"]
251
+ *
252
+ * @param workspaces The workspaces field from package.json
253
+ * @param projectDir The absolute path to the project directory
254
+ * @param relativeTo Optional base path for creating relative paths
255
+ * @returns Array of relative paths to workspace member package.json files
256
+ */
257
+ private async resolveWorkspacePackagePaths(
258
+ workspaces: string[] | { packages?: string[] },
259
+ projectDir: string,
260
+ relativeTo?: string
261
+ ): Promise<string[] | undefined> {
262
+ // Normalize workspaces to array format
263
+ const patterns = Array.isArray(workspaces)
264
+ ? workspaces
265
+ : workspaces.packages;
266
+
267
+ if (!patterns || patterns.length === 0) {
268
+ return undefined;
269
+ }
270
+
271
+ // Separate include and exclude patterns
272
+ const includePatterns = patterns.filter(p => !p.startsWith('!'));
273
+ const excludePatterns = patterns.filter(p => p.startsWith('!')).map(p => p.slice(1));
274
+
275
+ // Create picomatch matchers
276
+ const isIncluded = includePatterns.length > 0
277
+ ? picomatch(includePatterns, { dot: false })
278
+ : () => false;
279
+ const isExcluded = excludePatterns.length > 0
280
+ ? picomatch(excludePatterns, { dot: false })
281
+ : () => false;
282
+
283
+ // Collect all candidate directories by walking the project
284
+ const candidateDirs = await this.collectCandidateWorkspaceDirs(projectDir, includePatterns);
285
+
286
+ const memberPaths: string[] = [];
287
+ const basePath = relativeTo || projectDir;
288
+
289
+ for (const candidateDir of candidateDirs) {
290
+ // Get relative path from project root for pattern matching
291
+ const relativeDir = path.relative(projectDir, candidateDir);
292
+
293
+ // Check if directory matches include patterns and not exclude patterns
294
+ if (isIncluded(relativeDir) && !isExcluded(relativeDir)) {
295
+ const packageJsonPath = path.join(candidateDir, 'package.json');
296
+ if (fs.existsSync(packageJsonPath)) {
297
+ const relativePath = path.relative(basePath, packageJsonPath);
298
+ memberPaths.push(relativePath);
299
+ }
300
+ }
301
+ }
302
+
303
+ return memberPaths.length > 0 ? memberPaths : undefined;
304
+ }
305
+
306
+ /**
307
+ * Collects candidate directories that might match workspace patterns.
308
+ * Uses the patterns to determine how deep to scan.
309
+ */
310
+ private async collectCandidateWorkspaceDirs(
311
+ projectDir: string,
312
+ patterns: string[]
313
+ ): Promise<string[]> {
314
+ const candidates: string[] = [];
315
+
316
+ // Determine the maximum depth we need to scan based on patterns
317
+ // "packages/*" -> depth 1 under packages/
318
+ // "packages/**" -> unlimited depth under packages/
319
+ // "{apps,libs}/*" -> depth 1 under apps/ and libs/
320
+
321
+ for (const pattern of patterns) {
322
+ // Extract base directories from pattern (before any wildcards)
323
+ const baseDirs = this.extractBaseDirs(pattern);
324
+ const hasRecursive = pattern.includes('**');
325
+
326
+ for (const baseDir of baseDirs) {
327
+ const absoluteBaseDir = path.join(projectDir, baseDir);
328
+
329
+ if (!fs.existsSync(absoluteBaseDir)) {
330
+ continue;
331
+ }
332
+
333
+ // Use walkDirs with appropriate depth limit
334
+ const dirs = await walkDirs(absoluteBaseDir, {
335
+ maxDepth: hasRecursive ? undefined : 0,
336
+ excludeDirs: DEFAULT_DIR_EXCLUSIONS
337
+ });
338
+
339
+ candidates.push(...dirs);
340
+ }
341
+ }
342
+
343
+ return candidates;
344
+ }
345
+
346
+ /**
347
+ * Extracts base directory paths from a glob pattern.
348
+ * Handles brace expansion like "{apps,libs}/*" -> ["apps", "libs"]
349
+ */
350
+ private extractBaseDirs(pattern: string): string[] {
351
+ // Find the first wildcard character
352
+ const wildcardIndex = pattern.search(/[*?]/);
353
+ const beforeWildcard = wildcardIndex >= 0 ? pattern.slice(0, wildcardIndex) : pattern;
354
+
355
+ // Remove trailing slash if present
356
+ const basePath = beforeWildcard.replace(/\/$/, '');
357
+
358
+ // Handle brace expansion at the end of basePath: "dir/{a,b}" or "{a,b}"
359
+ const braceMatch = basePath.match(/^(.*?)(?:\{([^}]+)\})?$/);
360
+ if (braceMatch && braceMatch[2]) {
361
+ const prefix = braceMatch[1];
362
+ const options = braceMatch[2].split(',').map(s => s.trim());
363
+ return options.map(opt => prefix + opt);
364
+ }
365
+
366
+ return basePath ? [basePath] : ['.'];
367
+ }
368
+
239
369
  /**
240
370
  * Attempts to find and read a lock file by walking up the directory tree.
241
371
  * Starts from the directory containing the package.json and walks up toward
@@ -19,8 +19,7 @@ import {
19
19
  findNodeResolutionResult,
20
20
  PackageJsonContent,
21
21
  PackageLockContent,
22
- PackageManager,
23
- readNpmrcConfigs
22
+ PackageManager
24
23
  } from "./node-resolution-result";
25
24
  import {replaceMarkerByKind} from "../markers";
26
25
  import {Json, JsonParser, JsonVisitor} from "../json";
@@ -59,35 +58,37 @@ interface PackageManagerConfig {
59
58
  const PACKAGE_MANAGER_CONFIGS: Record<PackageManager, PackageManagerConfig> = {
60
59
  [PackageManager.Npm]: {
61
60
  lockFile: 'package-lock.json',
62
- installLockOnlyCommand: ['npm', 'install', '--package-lock-only'],
63
- installCommand: ['npm', 'install'],
61
+ // --ignore-scripts prevents prepublish/prepare scripts from running in temp directory
62
+ installLockOnlyCommand: ['npm', 'install', '--package-lock-only', '--ignore-scripts'],
63
+ installCommand: ['npm', 'install', '--ignore-scripts'],
64
64
  listCommand: ['npm', 'list', '--json', '--all'],
65
65
  },
66
66
  [PackageManager.YarnClassic]: {
67
67
  lockFile: 'yarn.lock',
68
- // Yarn Classic doesn't have a lock-only mode
68
+ // Yarn Classic doesn't have a lock-only mode; --ignore-scripts prevents lifecycle scripts
69
69
  installLockOnlyCommand: ['yarn', 'install', '--ignore-scripts'],
70
- installCommand: ['yarn', 'install'],
70
+ installCommand: ['yarn', 'install', '--ignore-scripts'],
71
71
  listCommand: ['yarn', 'list', '--json'],
72
72
  },
73
73
  [PackageManager.YarnBerry]: {
74
74
  lockFile: 'yarn.lock',
75
- // Yarn Berry's mode skip-build skips post-install scripts
75
+ // --mode skip-build skips post-install scripts in Yarn Berry
76
76
  installLockOnlyCommand: ['yarn', 'install', '--mode', 'skip-build'],
77
- installCommand: ['yarn', 'install'],
77
+ installCommand: ['yarn', 'install', '--mode', 'skip-build'],
78
78
  listCommand: ['yarn', 'info', '--all', '--json'],
79
79
  },
80
80
  [PackageManager.Pnpm]: {
81
81
  lockFile: 'pnpm-lock.yaml',
82
- installLockOnlyCommand: ['pnpm', 'install', '--lockfile-only'],
83
- installCommand: ['pnpm', 'install'],
82
+ // --ignore-scripts prevents lifecycle scripts from running in temp directory
83
+ installLockOnlyCommand: ['pnpm', 'install', '--lockfile-only', '--ignore-scripts'],
84
+ installCommand: ['pnpm', 'install', '--ignore-scripts'],
84
85
  listCommand: ['pnpm', 'list', '--json', '--depth=Infinity'],
85
86
  },
86
87
  [PackageManager.Bun]: {
87
88
  lockFile: 'bun.lock',
88
- // Bun doesn't have a lock-only mode, but is very fast anyway
89
+ // Bun doesn't have a lock-only mode; --ignore-scripts prevents lifecycle scripts
89
90
  installLockOnlyCommand: ['bun', 'install', '--ignore-scripts'],
90
- installCommand: ['bun', 'install'],
91
+ installCommand: ['bun', 'install', '--ignore-scripts'],
91
92
  },
92
93
  };
93
94
 
@@ -447,8 +448,6 @@ export async function parseLockFileContent(
447
448
  * Recipes extend this with additional fields specific to their needs.
448
449
  */
449
450
  export interface BaseProjectUpdateInfo {
450
- /** Absolute path to the project directory */
451
- projectDir: string;
452
451
  /** Relative path to package.json (from source root) */
453
452
  packageJsonPath: string;
454
453
  /** The package manager used by this project */
@@ -559,17 +558,15 @@ export async function updateNodeResolutionMarker<T extends BaseProjectUpdateInfo
559
558
  }
560
559
  }
561
560
 
562
- // Read npmrc configs from the project directory
563
- const npmrcConfigs = await readNpmrcConfigs(updateInfo.projectDir);
564
-
565
- // Create new marker
561
+ // Create new marker, preserving existing npmrc configs from the parser
562
+ // (recipes don't have filesystem access to re-read them)
566
563
  const newMarker = createNodeResolutionResultMarker(
567
564
  existingMarker.path,
568
565
  packageJsonContent,
569
566
  lockContent,
570
567
  existingMarker.workspacePackagePaths,
571
568
  existingMarker.packageManager,
572
- npmrcConfigs.length > 0 ? npmrcConfigs : undefined
569
+ existingMarker.npmrcConfigs
573
570
  );
574
571
 
575
572
  // Replace the marker in the document
@@ -591,52 +588,72 @@ export interface TempInstallOptions {
591
588
  * Default: true (lock-only is faster and sufficient for most cases)
592
589
  */
593
590
  lockOnly?: boolean;
591
+ /**
592
+ * Original lock file content to use. If provided, this content will be written
593
+ * to the temp directory instead of copying from projectDir.
594
+ * This allows recipes to work with in-memory SourceFiles without filesystem access.
595
+ */
596
+ originalLockFileContent?: string;
597
+ /**
598
+ * Config file contents to use. Keys are filenames (e.g., '.npmrc'), values are content.
599
+ * If provided, these will be written to the temp directory instead of copying from projectDir.
600
+ */
601
+ configFiles?: Record<string, string>;
594
602
  }
595
603
 
596
604
  /**
597
- * Runs package manager install in a temporary directory.
598
- *
599
- * This function:
600
- * 1. Creates a temp directory
601
- * 2. Writes the provided package.json content
602
- * 3. Copies the existing lock file (if present)
603
- * 4. Copies config files (.npmrc, .yarnrc, etc.)
604
- * 5. Runs the package manager install
605
- * 6. Returns the updated lock file content
606
- * 7. Cleans up the temp directory
607
- *
608
- * @param projectDir The original project directory (for copying lock file and configs)
609
- * @param pm The package manager to use
610
- * @param modifiedPackageJson The modified package.json content to use
611
- * @param options Optional settings for timeout and lock-only mode
612
- * @returns Result containing success status and lock file content or error
605
+ * Options for running install in a temporary directory with workspace support.
613
606
  */
614
- export async function runInstallInTempDir(
615
- projectDir: string,
607
+ export interface WorkspaceTempInstallOptions extends TempInstallOptions {
608
+ /**
609
+ * Workspace package.json files. Keys are relative paths from the project root
610
+ * (e.g., "packages/foo/package.json"), values are the package.json content.
611
+ * The root package.json should have a "workspaces" field pointing to these packages.
612
+ */
613
+ workspacePackages?: Record<string, string>;
614
+ }
615
+
616
+ /**
617
+ * Internal implementation for running package manager install in a temporary directory.
618
+ * Supports both simple projects and workspaces.
619
+ */
620
+ async function runInstallInTempDirCore(
616
621
  pm: PackageManager,
617
- modifiedPackageJson: string,
618
- options: TempInstallOptions = {}
622
+ rootPackageJson: string,
623
+ options: WorkspaceTempInstallOptions = {}
619
624
  ): Promise<TempInstallResult> {
620
- const {timeout = 120000, lockOnly = true} = options;
625
+ const {
626
+ timeout = 120000,
627
+ lockOnly = true,
628
+ originalLockFileContent,
629
+ configFiles: configFileContents,
630
+ workspacePackages
631
+ } = options;
621
632
  const lockFileName = getLockFileName(pm);
622
633
  const tempDir = await fsp.mkdtemp(path.join(os.tmpdir(), 'openrewrite-pm-'));
623
634
 
624
635
  try {
625
- // Write modified package.json to temp directory
626
- await fsp.writeFile(path.join(tempDir, 'package.json'), modifiedPackageJson);
636
+ // Write root package.json to temp directory
637
+ await fsp.writeFile(path.join(tempDir, 'package.json'), rootPackageJson);
638
+
639
+ // Write workspace package.json files (creating subdirectories as needed)
640
+ if (workspacePackages) {
641
+ for (const [relativePath, content] of Object.entries(workspacePackages)) {
642
+ const fullPath = path.join(tempDir, relativePath);
643
+ await fsp.mkdir(path.dirname(fullPath), {recursive: true});
644
+ await fsp.writeFile(fullPath, content);
645
+ }
646
+ }
627
647
 
628
- // Copy existing lock file if present
629
- const originalLockPath = path.join(projectDir, lockFileName);
630
- if (fs.existsSync(originalLockPath)) {
631
- await fsp.copyFile(originalLockPath, path.join(tempDir, lockFileName));
648
+ // Write lock file if provided
649
+ if (originalLockFileContent !== undefined) {
650
+ await fsp.writeFile(path.join(tempDir, lockFileName), originalLockFileContent);
632
651
  }
633
652
 
634
- // Copy config files if present (for registry configuration and workspace setup)
635
- const configFiles = ['.npmrc', '.yarnrc', '.yarnrc.yml', '.pnpmfile.cjs', 'pnpm-workspace.yaml'];
636
- for (const configFile of configFiles) {
637
- const configPath = path.join(projectDir, configFile);
638
- if (fs.existsSync(configPath)) {
639
- await fsp.copyFile(configPath, path.join(tempDir, configFile));
653
+ // Write config files if provided
654
+ if (configFileContents) {
655
+ for (const [configFile, content] of Object.entries(configFileContents)) {
656
+ await fsp.writeFile(path.join(tempDir, configFile), content);
640
657
  }
641
658
  }
642
659
 
@@ -688,6 +705,57 @@ export async function runInstallInTempDir(
688
705
  }
689
706
  }
690
707
 
708
+ /**
709
+ * Runs package manager install in a temporary directory.
710
+ *
711
+ * This function:
712
+ * 1. Creates a temp directory
713
+ * 2. Writes the provided package.json content
714
+ * 3. Writes the lock file content (if provided)
715
+ * 4. Writes config files (if provided)
716
+ * 5. Runs the package manager install
717
+ * 6. Returns the updated lock file content
718
+ * 7. Cleans up the temp directory
719
+ *
720
+ * @param pm The package manager to use
721
+ * @param modifiedPackageJson The modified package.json content to use
722
+ * @param options Optional settings for timeout, lock-only mode, and file contents
723
+ * @returns Result containing success status and lock file content or error
724
+ */
725
+ export async function runInstallInTempDir(
726
+ pm: PackageManager,
727
+ modifiedPackageJson: string,
728
+ options: TempInstallOptions = {}
729
+ ): Promise<TempInstallResult> {
730
+ return runInstallInTempDirCore(pm, modifiedPackageJson, options);
731
+ }
732
+
733
+ /**
734
+ * Runs package manager install in a temporary directory with workspace support.
735
+ *
736
+ * This function:
737
+ * 1. Creates a temp directory
738
+ * 2. Writes the root package.json content
739
+ * 3. Writes workspace package.json files (creating subdirectories as needed)
740
+ * 4. Writes the lock file content (if provided)
741
+ * 5. Writes config files (if provided)
742
+ * 6. Runs the package manager install at the root
743
+ * 7. Returns the updated lock file content
744
+ * 8. Cleans up the temp directory
745
+ *
746
+ * @param pm The package manager to use
747
+ * @param rootPackageJson The root package.json content (should contain "workspaces" field)
748
+ * @param options Optional settings including workspace packages, timeout, lock-only mode, and file contents
749
+ * @returns Result containing success status and lock file content or error
750
+ */
751
+ export async function runWorkspaceInstallInTempDir(
752
+ pm: PackageManager,
753
+ rootPackageJson: string,
754
+ options: WorkspaceTempInstallOptions = {}
755
+ ): Promise<TempInstallResult> {
756
+ return runInstallInTempDirCore(pm, rootPackageJson, options);
757
+ }
758
+
691
759
  /**
692
760
  * Creates a lock file visitor that handles updating YAML lock files (pnpm-lock.yaml).
693
761
  * This is a reusable component for dependency recipes.