@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
|
@@ -49,6 +49,11 @@ interface PackageJsonWorkspaceOptions extends BaseWorkspaceOptions {
|
|
|
49
49
|
* - `npm ci` is used instead of `npm install` (faster, deterministic)
|
|
50
50
|
*/
|
|
51
51
|
packageLockContent?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Optional workspace member package.json files.
|
|
54
|
+
* Keys are relative paths (e.g., "packages/foo/package.json"), values are content.
|
|
55
|
+
*/
|
|
56
|
+
workspacePackages?: Record<string, string>;
|
|
52
57
|
}
|
|
53
58
|
|
|
54
59
|
/**
|
|
@@ -77,20 +82,44 @@ export class DependencyWorkspace {
|
|
|
77
82
|
// Extract dependencies from package.json content if provided
|
|
78
83
|
let dependencies: Record<string, string> | undefined = options.dependencies;
|
|
79
84
|
let parsedPackageJson: Record<string, any> | undefined;
|
|
85
|
+
let workspacePackages: Record<string, string> | undefined;
|
|
86
|
+
|
|
80
87
|
if (options.packageJsonContent) {
|
|
81
88
|
parsedPackageJson = JSON.parse(options.packageJsonContent);
|
|
82
89
|
dependencies = {
|
|
83
90
|
...parsedPackageJson?.dependencies,
|
|
84
91
|
...parsedPackageJson?.devDependencies
|
|
85
92
|
};
|
|
93
|
+
workspacePackages = options.workspacePackages;
|
|
94
|
+
|
|
95
|
+
// For workspaces, also collect dependencies from workspace members
|
|
96
|
+
if (workspacePackages) {
|
|
97
|
+
for (const content of Object.values(workspacePackages)) {
|
|
98
|
+
const memberPkg = JSON.parse(content);
|
|
99
|
+
dependencies = {
|
|
100
|
+
...dependencies,
|
|
101
|
+
...memberPkg?.dependencies,
|
|
102
|
+
...memberPkg?.devDependencies
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
86
106
|
}
|
|
87
107
|
|
|
88
|
-
|
|
108
|
+
// For workspaces without explicit dependencies in root, we still need to run install
|
|
109
|
+
const hasWorkspaces = parsedPackageJson?.workspaces && Array.isArray(parsedPackageJson.workspaces);
|
|
110
|
+
if ((!dependencies || Object.keys(dependencies).length === 0) && !hasWorkspaces) {
|
|
89
111
|
throw new Error('No dependencies provided');
|
|
90
112
|
}
|
|
91
113
|
|
|
92
114
|
// Use the refactored internal method
|
|
93
|
-
return this.createWorkspace(
|
|
115
|
+
return this.createWorkspace(
|
|
116
|
+
dependencies || {},
|
|
117
|
+
parsedPackageJson,
|
|
118
|
+
options.packageJsonContent,
|
|
119
|
+
options.packageLockContent,
|
|
120
|
+
options.targetDir,
|
|
121
|
+
workspacePackages
|
|
122
|
+
);
|
|
94
123
|
}
|
|
95
124
|
|
|
96
125
|
/**
|
|
@@ -101,14 +130,23 @@ export class DependencyWorkspace {
|
|
|
101
130
|
parsedPackageJson: Record<string, any> | undefined,
|
|
102
131
|
packageJsonContent: string | undefined,
|
|
103
132
|
packageLockContent: string | undefined,
|
|
104
|
-
targetDir: string | undefined
|
|
133
|
+
targetDir: string | undefined,
|
|
134
|
+
workspacePackages?: Record<string, string>
|
|
105
135
|
): Promise<string> {
|
|
106
136
|
// Determine hash based on lock file (most precise) or dependencies
|
|
107
137
|
// Note: We always hash dependencies (not packageJsonContent) because whitespace/formatting
|
|
108
138
|
// differences in package.json shouldn't create different workspaces
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
139
|
+
// For workspaces, include workspace package paths in the hash
|
|
140
|
+
let hash: string;
|
|
141
|
+
if (packageLockContent) {
|
|
142
|
+
hash = this.hashContent(packageLockContent);
|
|
143
|
+
} else if (workspacePackages) {
|
|
144
|
+
// Include workspace package paths in hash for workspace setups
|
|
145
|
+
const workspacePaths = Object.keys(workspacePackages).sort().join(',');
|
|
146
|
+
hash = this.hashContent(this.hashDependencies(dependencies) + ':' + workspacePaths);
|
|
147
|
+
} else {
|
|
148
|
+
hash = this.hashDependencies(dependencies);
|
|
149
|
+
}
|
|
112
150
|
|
|
113
151
|
// Determine npm command: use `npm ci` when lock file is provided (faster, deterministic)
|
|
114
152
|
const npmCommand = packageLockContent ? 'npm ci --silent' : 'npm install --silent';
|
|
@@ -134,11 +172,26 @@ export class DependencyWorkspace {
|
|
|
134
172
|
if (packageLockContent) {
|
|
135
173
|
fs.writeFileSync(path.join(dir, 'package-lock.json'), packageLockContent);
|
|
136
174
|
}
|
|
175
|
+
|
|
176
|
+
// Write workspace member package.json files
|
|
177
|
+
if (workspacePackages) {
|
|
178
|
+
for (const [relativePath, content] of Object.entries(workspacePackages)) {
|
|
179
|
+
const fullPath = path.join(dir, relativePath);
|
|
180
|
+
const memberDir = path.dirname(fullPath);
|
|
181
|
+
if (!fs.existsSync(memberDir)) {
|
|
182
|
+
fs.mkdirSync(memberDir, {recursive: true});
|
|
183
|
+
}
|
|
184
|
+
fs.writeFileSync(fullPath, content);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
137
187
|
};
|
|
138
188
|
|
|
189
|
+
// For workspaces, skip dependency validation (combined deps don't match root package.json)
|
|
190
|
+
const depsForValidation = workspacePackages ? undefined : dependencies;
|
|
191
|
+
|
|
139
192
|
if (targetDir) {
|
|
140
193
|
// Use provided directory - check if it's already valid
|
|
141
|
-
if (this.isWorkspaceValid(targetDir,
|
|
194
|
+
if (this.isWorkspaceValid(targetDir, depsForValidation)) {
|
|
142
195
|
return targetDir;
|
|
143
196
|
}
|
|
144
197
|
|
|
@@ -149,7 +202,7 @@ export class DependencyWorkspace {
|
|
|
149
202
|
const cachedWorkspaceDir = path.join(this.WORKSPACE_BASE, hash);
|
|
150
203
|
const cachedNodeModules = path.join(cachedWorkspaceDir, 'node_modules');
|
|
151
204
|
|
|
152
|
-
if (fs.existsSync(cachedNodeModules) && this.isWorkspaceValid(cachedWorkspaceDir,
|
|
205
|
+
if (fs.existsSync(cachedNodeModules) && this.isWorkspaceValid(cachedWorkspaceDir, depsForValidation)) {
|
|
153
206
|
// Symlink node_modules from cached workspace
|
|
154
207
|
try {
|
|
155
208
|
const targetNodeModules = path.join(targetDir, 'node_modules');
|
|
@@ -190,7 +243,7 @@ export class DependencyWorkspace {
|
|
|
190
243
|
|
|
191
244
|
// Check cache
|
|
192
245
|
const cached = this.cache.get(hash);
|
|
193
|
-
if (cached && fs.existsSync(cached) && this.isWorkspaceValid(cached,
|
|
246
|
+
if (cached && fs.existsSync(cached) && this.isWorkspaceValid(cached, depsForValidation)) {
|
|
194
247
|
return cached;
|
|
195
248
|
}
|
|
196
249
|
|
|
@@ -198,7 +251,7 @@ export class DependencyWorkspace {
|
|
|
198
251
|
const workspaceDir = path.join(this.WORKSPACE_BASE, hash);
|
|
199
252
|
|
|
200
253
|
// Check if valid workspace already exists on disk (cross-VM reuse)
|
|
201
|
-
if (fs.existsSync(workspaceDir) && this.isWorkspaceValid(workspaceDir,
|
|
254
|
+
if (fs.existsSync(workspaceDir) && this.isWorkspaceValid(workspaceDir, depsForValidation)) {
|
|
202
255
|
this.cache.set(hash, workspaceDir);
|
|
203
256
|
return workspaceDir;
|
|
204
257
|
}
|
|
@@ -241,7 +294,7 @@ export class DependencyWorkspace {
|
|
|
241
294
|
if (error.code === 'EEXIST' || error.code === 'ENOTEMPTY' || error.code === 'EISDIR' ||
|
|
242
295
|
(error.code === 'EPERM' && fs.existsSync(workspaceDir))) {
|
|
243
296
|
// Target exists - check if it's valid
|
|
244
|
-
if (this.isWorkspaceValid(workspaceDir,
|
|
297
|
+
if (this.isWorkspaceValid(workspaceDir, depsForValidation)) {
|
|
245
298
|
// Another process created a valid workspace - use theirs
|
|
246
299
|
moved = true; // Don't try again
|
|
247
300
|
} else {
|
|
@@ -261,7 +314,7 @@ export class DependencyWorkspace {
|
|
|
261
314
|
moved = true;
|
|
262
315
|
} catch (copyError) {
|
|
263
316
|
// Check if another process created it while we were copying
|
|
264
|
-
if (this.isWorkspaceValid(workspaceDir,
|
|
317
|
+
if (this.isWorkspaceValid(workspaceDir, depsForValidation)) {
|
|
265
318
|
moved = true;
|
|
266
319
|
} else {
|
|
267
320
|
throw error;
|
|
@@ -284,7 +337,7 @@ export class DependencyWorkspace {
|
|
|
284
337
|
}
|
|
285
338
|
|
|
286
339
|
// Verify final workspace is valid (might be from another process)
|
|
287
|
-
if (!this.isWorkspaceValid(workspaceDir,
|
|
340
|
+
if (!this.isWorkspaceValid(workspaceDir, depsForValidation)) {
|
|
288
341
|
throw new Error('Failed to create valid workspace due to concurrent modifications');
|
|
289
342
|
}
|
|
290
343
|
|
|
@@ -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
|
-
//
|
|
144
|
-
|
|
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
|
-
|
|
151
|
-
|
|
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
|
-
|
|
158
|
+
if (jsonResult.done || !jsonResult.value) {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
171
161
|
|
|
172
|
-
|
|
173
|
-
if (!marker) {
|
|
174
|
-
marker = await this.createMarker(input, dir);
|
|
175
|
-
}
|
|
162
|
+
const jsonDoc = jsonResult.value as Json.Document;
|
|
176
163
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
|
|
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
|
|
@@ -412,12 +542,21 @@ export class PackageJsonParser extends Parser {
|
|
|
412
542
|
}
|
|
413
543
|
|
|
414
544
|
// Parse name@version from directory name
|
|
545
|
+
// pnpm directory format: <name>@<version> or <name>@<version>_<peer-deps-context>
|
|
415
546
|
// Handle scoped packages: @scope+name@version
|
|
416
|
-
|
|
547
|
+
// Example: @babel+helper-module-transforms@7.28.3_@babel+core@7.28.5
|
|
548
|
+
// -> name: @babel/helper-module-transforms, version: 7.28.3
|
|
549
|
+
|
|
550
|
+
// First, strip peer dependency context (everything after first _)
|
|
551
|
+
const underscoreIndex = entry.name.indexOf('_');
|
|
552
|
+
const mainPart = underscoreIndex > 0 ? entry.name.substring(0, underscoreIndex) : entry.name;
|
|
553
|
+
|
|
554
|
+
// Now parse name@version from the main part
|
|
555
|
+
const atIndex = mainPart.lastIndexOf('@');
|
|
417
556
|
if (atIndex <= 0) return;
|
|
418
557
|
|
|
419
|
-
let name =
|
|
420
|
-
const version =
|
|
558
|
+
let name = mainPart.substring(0, atIndex);
|
|
559
|
+
const version = mainPart.substring(atIndex + 1);
|
|
421
560
|
|
|
422
561
|
// pnpm encodes @ as + in scoped packages: @scope+name -> @scope/name
|
|
423
562
|
if (name.startsWith('@') && name.includes('+')) {
|