@openrewrite/rewrite 8.70.2 → 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.
- package/dist/javascript/add-import.d.ts +5 -0
- package/dist/javascript/add-import.d.ts.map +1 -1
- package/dist/javascript/add-import.js +22 -9
- package/dist/javascript/add-import.js.map +1 -1
- package/dist/javascript/assertions.d.ts.map +1 -1
- package/dist/javascript/assertions.js +45 -13
- package/dist/javascript/assertions.js.map +1 -1
- package/dist/javascript/dependency-workspace.d.ts +5 -0
- package/dist/javascript/dependency-workspace.d.ts.map +1 -1
- package/dist/javascript/dependency-workspace.js +47 -13
- package/dist/javascript/dependency-workspace.js.map +1 -1
- package/dist/javascript/package-json-parser.d.ts +24 -0
- package/dist/javascript/package-json-parser.d.ts.map +1 -1
- package/dist/javascript/package-json-parser.js +147 -34
- package/dist/javascript/package-json-parser.js.map +1 -1
- package/dist/javascript/package-manager.d.ts +45 -7
- package/dist/javascript/package-manager.d.ts.map +1 -1
- package/dist/javascript/package-manager.js +83 -45
- package/dist/javascript/package-manager.js.map +1 -1
- package/dist/javascript/project-parser.d.ts +7 -0
- package/dist/javascript/project-parser.d.ts.map +1 -1
- package/dist/javascript/project-parser.js +10 -9
- package/dist/javascript/project-parser.js.map +1 -1
- package/dist/javascript/recipes/add-dependency.d.ts +7 -3
- package/dist/javascript/recipes/add-dependency.d.ts.map +1 -1
- package/dist/javascript/recipes/add-dependency.js +71 -13
- package/dist/javascript/recipes/add-dependency.js.map +1 -1
- package/dist/javascript/recipes/upgrade-dependency-version.d.ts +7 -3
- package/dist/javascript/recipes/upgrade-dependency-version.d.ts.map +1 -1
- package/dist/javascript/recipes/upgrade-dependency-version.js +71 -13
- package/dist/javascript/recipes/upgrade-dependency-version.js.map +1 -1
- package/dist/javascript/recipes/upgrade-transitive-dependency-version.d.ts +7 -3
- package/dist/javascript/recipes/upgrade-transitive-dependency-version.d.ts.map +1 -1
- package/dist/javascript/recipes/upgrade-transitive-dependency-version.js +71 -13
- package/dist/javascript/recipes/upgrade-transitive-dependency-version.js.map +1 -1
- package/dist/path-utils.d.ts +45 -0
- package/dist/path-utils.d.ts.map +1 -0
- package/dist/path-utils.js +210 -0
- package/dist/path-utils.js.map +1 -0
- package/dist/rpc/request/parse-project.d.ts +13 -1
- package/dist/rpc/request/parse-project.d.ts.map +1 -1
- package/dist/rpc/request/parse-project.js +18 -9
- package/dist/rpc/request/parse-project.js.map +1 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +4 -0
- package/dist/run.js.map +1 -1
- package/dist/version.txt +1 -1
- package/package.json +1 -1
- package/src/javascript/add-import.ts +28 -7
- package/src/javascript/assertions.ts +48 -6
- package/src/javascript/dependency-workspace.ts +66 -13
- package/src/javascript/package-json-parser.ts +181 -42
- package/src/javascript/package-manager.ts +120 -52
- package/src/javascript/project-parser.ts +18 -9
- package/src/javascript/recipes/add-dependency.ts +89 -17
- package/src/javascript/recipes/upgrade-dependency-version.ts +89 -17
- package/src/javascript/recipes/upgrade-transitive-dependency-version.ts +89 -17
- package/src/path-utils.ts +208 -0
- package/src/rpc/request/parse-project.ts +17 -9
- package/src/run.ts +4 -0
|
@@ -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
|
-
|
|
63
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
83
|
-
|
|
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
|
|
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
|
-
//
|
|
563
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
|
615
|
-
|
|
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
|
-
|
|
618
|
-
options:
|
|
622
|
+
rootPackageJson: string,
|
|
623
|
+
options: WorkspaceTempInstallOptions = {}
|
|
619
624
|
): Promise<TempInstallResult> {
|
|
620
|
-
const {
|
|
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
|
|
626
|
-
await fsp.writeFile(path.join(tempDir, 'package.json'),
|
|
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
|
-
//
|
|
629
|
-
|
|
630
|
-
|
|
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
|
-
//
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
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.
|
|
@@ -77,6 +77,13 @@ export interface ProjectParserOptions {
|
|
|
77
77
|
* If not provided, all discovered files are parsed.
|
|
78
78
|
*/
|
|
79
79
|
fileFilter?: (absolutePath: string) => boolean;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Optional path to make source file paths relative to.
|
|
83
|
+
* If not specified, paths are relative to projectPath.
|
|
84
|
+
* Use this when parsing a subdirectory but wanting paths relative to the repository root.
|
|
85
|
+
*/
|
|
86
|
+
relativeTo?: string;
|
|
80
87
|
}
|
|
81
88
|
|
|
82
89
|
/**
|
|
@@ -153,6 +160,7 @@ const TEXT_CONFIG_FILES = new Set([
|
|
|
153
160
|
*/
|
|
154
161
|
export class ProjectParser {
|
|
155
162
|
private readonly projectPath: string;
|
|
163
|
+
private readonly relativeTo: string;
|
|
156
164
|
private readonly exclusions: string[];
|
|
157
165
|
private readonly ctx: ExecutionContext;
|
|
158
166
|
private readonly useGit: boolean;
|
|
@@ -162,6 +170,7 @@ export class ProjectParser {
|
|
|
162
170
|
|
|
163
171
|
constructor(projectPath: string, options: ProjectParserOptions = {}) {
|
|
164
172
|
this.projectPath = path.resolve(projectPath);
|
|
173
|
+
this.relativeTo = options.relativeTo ? path.resolve(options.relativeTo) : this.projectPath;
|
|
165
174
|
this.exclusions = options.exclusions ?? DEFAULT_EXCLUSIONS;
|
|
166
175
|
this.ctx = options.ctx ?? new ExecutionContext();
|
|
167
176
|
this.useGit = options.useGit ?? this.isGitRepository();
|
|
@@ -230,7 +239,7 @@ export class ProjectParser {
|
|
|
230
239
|
this.log(`Parsing ${discovered.packageJsonFiles.length} package.json files...`);
|
|
231
240
|
const parser = Parsers.createParser("packageJson", {
|
|
232
241
|
ctx: this.ctx,
|
|
233
|
-
relativeTo: this.
|
|
242
|
+
relativeTo: this.relativeTo
|
|
234
243
|
});
|
|
235
244
|
for await (const sf of parser.parse(...discovered.packageJsonFiles)) {
|
|
236
245
|
current++;
|
|
@@ -244,7 +253,7 @@ export class ProjectParser {
|
|
|
244
253
|
this.log(`Parsing ${discovered.lockFiles.json.length} JSON lock files...`);
|
|
245
254
|
const parser = Parsers.createParser("json", {
|
|
246
255
|
ctx: this.ctx,
|
|
247
|
-
relativeTo: this.
|
|
256
|
+
relativeTo: this.relativeTo
|
|
248
257
|
});
|
|
249
258
|
for await (const sf of parser.parse(...discovered.lockFiles.json)) {
|
|
250
259
|
current++;
|
|
@@ -258,7 +267,7 @@ export class ProjectParser {
|
|
|
258
267
|
this.log(`Parsing ${discovered.lockFiles.yaml.length} YAML lock files...`);
|
|
259
268
|
const parser = Parsers.createParser("yaml", {
|
|
260
269
|
ctx: this.ctx,
|
|
261
|
-
relativeTo: this.
|
|
270
|
+
relativeTo: this.relativeTo
|
|
262
271
|
});
|
|
263
272
|
for await (const sf of parser.parse(...discovered.lockFiles.yaml)) {
|
|
264
273
|
current++;
|
|
@@ -272,7 +281,7 @@ export class ProjectParser {
|
|
|
272
281
|
this.log(`Parsing ${discovered.lockFiles.text.length} text lock files...`);
|
|
273
282
|
const parser = Parsers.createParser("plainText", {
|
|
274
283
|
ctx: this.ctx,
|
|
275
|
-
relativeTo: this.
|
|
284
|
+
relativeTo: this.relativeTo
|
|
276
285
|
});
|
|
277
286
|
for await (const sf of parser.parse(...discovered.lockFiles.text)) {
|
|
278
287
|
current++;
|
|
@@ -286,7 +295,7 @@ export class ProjectParser {
|
|
|
286
295
|
this.log(`Parsing ${discovered.jsFiles.length} JavaScript/TypeScript files...`);
|
|
287
296
|
const parser = Parsers.createParser("javascript", {
|
|
288
297
|
ctx: this.ctx,
|
|
289
|
-
relativeTo: this.
|
|
298
|
+
relativeTo: this.relativeTo
|
|
290
299
|
});
|
|
291
300
|
|
|
292
301
|
// Check if Prettier is available
|
|
@@ -299,7 +308,7 @@ export class ProjectParser {
|
|
|
299
308
|
this.onProgress?.("parsing", current, totalFiles, sf.sourcePath);
|
|
300
309
|
|
|
301
310
|
const prettierMarker = await prettierLoader.getConfigMarker(
|
|
302
|
-
path.join(this.
|
|
311
|
+
path.join(this.relativeTo, sf.sourcePath)
|
|
303
312
|
);
|
|
304
313
|
if (prettierMarker) {
|
|
305
314
|
yield produce(sf, draft => {
|
|
@@ -352,7 +361,7 @@ export class ProjectParser {
|
|
|
352
361
|
this.log(`Parsing ${discovered.yamlFiles.length} YAML files...`);
|
|
353
362
|
const parser = Parsers.createParser("yaml", {
|
|
354
363
|
ctx: this.ctx,
|
|
355
|
-
relativeTo: this.
|
|
364
|
+
relativeTo: this.relativeTo
|
|
356
365
|
});
|
|
357
366
|
for await (const sf of parser.parse(...discovered.yamlFiles)) {
|
|
358
367
|
current++;
|
|
@@ -366,7 +375,7 @@ export class ProjectParser {
|
|
|
366
375
|
this.log(`Parsing ${discovered.jsonFiles.length} JSON files...`);
|
|
367
376
|
const parser = Parsers.createParser("json", {
|
|
368
377
|
ctx: this.ctx,
|
|
369
|
-
relativeTo: this.
|
|
378
|
+
relativeTo: this.relativeTo
|
|
370
379
|
});
|
|
371
380
|
for await (const sf of parser.parse(...discovered.jsonFiles)) {
|
|
372
381
|
current++;
|
|
@@ -380,7 +389,7 @@ export class ProjectParser {
|
|
|
380
389
|
this.log(`Parsing ${discovered.textFiles.length} text config files...`);
|
|
381
390
|
const parser = Parsers.createParser("plainText", {
|
|
382
391
|
ctx: this.ctx,
|
|
383
|
-
relativeTo: this.
|
|
392
|
+
relativeTo: this.relativeTo
|
|
384
393
|
});
|
|
385
394
|
for await (const sf of parser.parse(...discovered.textFiles)) {
|
|
386
395
|
current++;
|
|
@@ -17,11 +17,15 @@
|
|
|
17
17
|
import {Option, ScanningRecipe} from "../../recipe";
|
|
18
18
|
import {ExecutionContext} from "../../execution";
|
|
19
19
|
import {TreeVisitor} from "../../visitor";
|
|
20
|
-
import {
|
|
20
|
+
import {Tree} from "../../tree";
|
|
21
|
+
import {detectIndent, getMemberKeyName, isJson, isObject, Json, JsonVisitor, rightPadded, space} from "../../json";
|
|
22
|
+
import {isDocuments, isYaml, Yaml} from "../../yaml";
|
|
23
|
+
import {isPlainText, PlainText} from "../../text";
|
|
21
24
|
import {
|
|
22
25
|
allDependencyScopes,
|
|
23
26
|
DependencyScope,
|
|
24
27
|
findNodeResolutionResult,
|
|
28
|
+
NpmrcScope,
|
|
25
29
|
PackageManager
|
|
26
30
|
} from "../node-resolution-result";
|
|
27
31
|
import {emptyMarkers, markupWarn} from "../../markers";
|
|
@@ -31,6 +35,7 @@ import {
|
|
|
31
35
|
createLockFileEditor,
|
|
32
36
|
DependencyRecipeAccumulator,
|
|
33
37
|
getAllLockFileNames,
|
|
38
|
+
getLockFileName,
|
|
34
39
|
parseLockFileContent,
|
|
35
40
|
runInstallIfNeeded,
|
|
36
41
|
runInstallInTempDir,
|
|
@@ -44,8 +49,6 @@ import * as path from "path";
|
|
|
44
49
|
* Information about a project that needs updating
|
|
45
50
|
*/
|
|
46
51
|
interface ProjectUpdateInfo {
|
|
47
|
-
/** Absolute path to the project directory */
|
|
48
|
-
projectDir: string;
|
|
49
52
|
/** Relative path to package.json (from source root) */
|
|
50
53
|
packageJsonPath: string;
|
|
51
54
|
/** Original package.json content */
|
|
@@ -56,9 +59,14 @@ interface ProjectUpdateInfo {
|
|
|
56
59
|
newVersion: string;
|
|
57
60
|
/** The package manager used by this project */
|
|
58
61
|
packageManager: PackageManager;
|
|
62
|
+
/** Config file contents extracted from the project (e.g., .npmrc) */
|
|
63
|
+
configFiles?: Record<string, string>;
|
|
59
64
|
}
|
|
60
65
|
|
|
61
|
-
|
|
66
|
+
interface Accumulator extends DependencyRecipeAccumulator<ProjectUpdateInfo> {
|
|
67
|
+
/** Original lock file content, keyed by lock file path */
|
|
68
|
+
originalLockFiles: Map<string, string>;
|
|
69
|
+
}
|
|
62
70
|
|
|
63
71
|
/**
|
|
64
72
|
* Adds a new dependency to package.json and updates the lock file.
|
|
@@ -100,7 +108,10 @@ export class AddDependency extends ScanningRecipe<Accumulator> {
|
|
|
100
108
|
scope?: DependencyScope;
|
|
101
109
|
|
|
102
110
|
initialValue(_ctx: ExecutionContext): Accumulator {
|
|
103
|
-
return
|
|
111
|
+
return {
|
|
112
|
+
...createDependencyRecipeAccumulator<ProjectUpdateInfo>(),
|
|
113
|
+
originalLockFiles: new Map()
|
|
114
|
+
};
|
|
104
115
|
}
|
|
105
116
|
|
|
106
117
|
private getTargetScope(): DependencyScope {
|
|
@@ -109,10 +120,38 @@ export class AddDependency extends ScanningRecipe<Accumulator> {
|
|
|
109
120
|
|
|
110
121
|
async scanner(acc: Accumulator): Promise<TreeVisitor<any, ExecutionContext>> {
|
|
111
122
|
const recipe = this;
|
|
123
|
+
const LOCK_FILE_NAMES = getAllLockFileNames();
|
|
124
|
+
|
|
125
|
+
return new class extends TreeVisitor<Tree, ExecutionContext> {
|
|
126
|
+
protected async accept(tree: Tree, ctx: ExecutionContext): Promise<Tree | undefined> {
|
|
127
|
+
// Handle JSON documents (package.json and JSON lock files)
|
|
128
|
+
if (isJson(tree) && tree.kind === Json.Kind.Document) {
|
|
129
|
+
return this.handleJsonDocument(tree as Json.Document, ctx);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Handle YAML documents (pnpm-lock.yaml)
|
|
133
|
+
if (isYaml(tree) && isDocuments(tree)) {
|
|
134
|
+
return this.handleYamlDocument(tree, ctx);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Handle PlainText files (yarn.lock for Yarn Classic)
|
|
138
|
+
if (isPlainText(tree)) {
|
|
139
|
+
return this.handlePlainTextDocument(tree as PlainText, ctx);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return tree;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private async handleJsonDocument(doc: Json.Document, _ctx: ExecutionContext): Promise<Json | undefined> {
|
|
146
|
+
const basename = path.basename(doc.sourcePath);
|
|
112
147
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
148
|
+
// Capture JSON lock file content (package-lock.json, bun.lock)
|
|
149
|
+
if (LOCK_FILE_NAMES.includes(basename)) {
|
|
150
|
+
acc.originalLockFiles.set(doc.sourcePath, await TreePrinters.print(doc));
|
|
151
|
+
return doc;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Only process package.json files for dependency analysis
|
|
116
155
|
if (!doc.sourcePath.endsWith('package.json')) {
|
|
117
156
|
return doc;
|
|
118
157
|
}
|
|
@@ -131,24 +170,43 @@ export class AddDependency extends ScanningRecipe<Accumulator> {
|
|
|
131
170
|
}
|
|
132
171
|
}
|
|
133
172
|
|
|
134
|
-
// Get the project directory and package manager
|
|
135
|
-
const projectDir = path.dirname(path.resolve(doc.sourcePath));
|
|
136
173
|
const pm = marker.packageManager ?? PackageManager.Npm;
|
|
137
174
|
|
|
175
|
+
// Extract project-level .npmrc config from marker
|
|
176
|
+
const configFiles: Record<string, string> = {};
|
|
177
|
+
const projectNpmrc = marker.npmrcConfigs?.find(c => c.scope === NpmrcScope.Project);
|
|
178
|
+
if (projectNpmrc) {
|
|
179
|
+
const lines = Object.entries(projectNpmrc.properties)
|
|
180
|
+
.map(([key, value]) => `${key}=${value}`);
|
|
181
|
+
configFiles['.npmrc'] = lines.join('\n');
|
|
182
|
+
}
|
|
183
|
+
|
|
138
184
|
acc.projectsToUpdate.set(doc.sourcePath, {
|
|
139
|
-
projectDir,
|
|
140
185
|
packageJsonPath: doc.sourcePath,
|
|
141
|
-
originalPackageJson: await
|
|
186
|
+
originalPackageJson: await TreePrinters.print(doc),
|
|
142
187
|
dependencyScope: recipe.getTargetScope(),
|
|
143
188
|
newVersion: recipe.version,
|
|
144
|
-
packageManager: pm
|
|
189
|
+
packageManager: pm,
|
|
190
|
+
configFiles: Object.keys(configFiles).length > 0 ? configFiles : undefined
|
|
145
191
|
});
|
|
146
192
|
|
|
147
193
|
return doc;
|
|
148
194
|
}
|
|
149
195
|
|
|
150
|
-
private async
|
|
151
|
-
|
|
196
|
+
private async handleYamlDocument(docs: Yaml.Documents, _ctx: ExecutionContext): Promise<Yaml.Documents | undefined> {
|
|
197
|
+
const basename = path.basename(docs.sourcePath);
|
|
198
|
+
if (LOCK_FILE_NAMES.includes(basename)) {
|
|
199
|
+
acc.originalLockFiles.set(docs.sourcePath, await TreePrinters.print(docs));
|
|
200
|
+
}
|
|
201
|
+
return docs;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private async handlePlainTextDocument(text: PlainText, _ctx: ExecutionContext): Promise<PlainText | undefined> {
|
|
205
|
+
const basename = path.basename(text.sourcePath);
|
|
206
|
+
if (LOCK_FILE_NAMES.includes(basename)) {
|
|
207
|
+
acc.originalLockFiles.set(text.sourcePath, await TreePrinters.print(text));
|
|
208
|
+
}
|
|
209
|
+
return text;
|
|
152
210
|
}
|
|
153
211
|
};
|
|
154
212
|
}
|
|
@@ -217,6 +275,7 @@ export class AddDependency extends ScanningRecipe<Accumulator> {
|
|
|
217
275
|
|
|
218
276
|
/**
|
|
219
277
|
* Runs the package manager in a temporary directory to update the lock file.
|
|
278
|
+
* All file contents are provided from in-memory sources (SourceFiles), not read from disk.
|
|
220
279
|
*/
|
|
221
280
|
private async runPackageManagerInstall(
|
|
222
281
|
acc: Accumulator,
|
|
@@ -229,10 +288,23 @@ export class AddDependency extends ScanningRecipe<Accumulator> {
|
|
|
229
288
|
updateInfo.dependencyScope
|
|
230
289
|
);
|
|
231
290
|
|
|
291
|
+
// Get the lock file path based on package manager
|
|
292
|
+
const lockFileName = getLockFileName(updateInfo.packageManager);
|
|
293
|
+
const packageJsonDir = path.dirname(updateInfo.packageJsonPath);
|
|
294
|
+
const lockFilePath = packageJsonDir === '.'
|
|
295
|
+
? lockFileName
|
|
296
|
+
: path.join(packageJsonDir, lockFileName);
|
|
297
|
+
|
|
298
|
+
// Look up the original lock file content from captured SourceFiles
|
|
299
|
+
const originalLockFileContent = acc.originalLockFiles.get(lockFilePath);
|
|
300
|
+
|
|
232
301
|
const result = await runInstallInTempDir(
|
|
233
|
-
updateInfo.projectDir,
|
|
234
302
|
updateInfo.packageManager,
|
|
235
|
-
modifiedPackageJson
|
|
303
|
+
modifiedPackageJson,
|
|
304
|
+
{
|
|
305
|
+
originalLockFileContent,
|
|
306
|
+
configFiles: updateInfo.configFiles
|
|
307
|
+
}
|
|
236
308
|
);
|
|
237
309
|
|
|
238
310
|
storeInstallResult(result, acc, updateInfo, modifiedPackageJson);
|