@wise/wds-codemods 0.0.1-experimental-2eb5228 → 0.0.1-experimental-0d8d466
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/.changeset/better-impalas-drop.md +1 -1
- package/.changeset/quick-mails-joke.md +128 -0
- package/.github/actions/test/action.yml +23 -0
- package/DEVELOPER.md +783 -0
- package/README.md +157 -14
- package/dist/index.js +10 -377
- package/dist/index.js.map +1 -1
- package/dist/transforms/button.d.ts +1 -5
- package/dist/transforms/button.js +96 -147
- package/dist/transforms/button.js.map +1 -1
- package/package.json +5 -7
- package/src/__tests__/runCodemod.test.ts +15 -57
- package/src/runCodemod.ts +1 -65
- package/src/transforms/button/__tests__/button.test.tsx +3 -25
- package/src/transforms/button/button.ts +126 -161
- package/src/transforms/helpers/createTestTransform.ts +2 -43
- package/src/transforms/helpers/index.ts +1 -1
- package/src/utils/__tests__/getOptions.test.ts +20 -69
- package/src/utils/getOptions.ts +2 -17
- package/src/transforms/helpers/__tests__/packageValidation.test.ts +0 -45
- package/src/transforms/helpers/packageValidation.ts +0 -53
- package/src/utils/__tests__/hasPackageVersion.test.ts +0 -190
- package/src/utils/hasPackageVersion.ts +0 -459
|
@@ -1,459 +0,0 @@
|
|
|
1
|
-
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
|
|
4
|
-
import { execSync } from 'child_process';
|
|
5
|
-
import semver from 'semver';
|
|
6
|
-
import { parse as parseYaml } from 'yaml';
|
|
7
|
-
|
|
8
|
-
interface PackageJson {
|
|
9
|
-
name?: string;
|
|
10
|
-
version?: string;
|
|
11
|
-
dependencies?: Record<string, string>;
|
|
12
|
-
devDependencies?: Record<string, string>;
|
|
13
|
-
peerDependencies?: Record<string, string>;
|
|
14
|
-
optionalDependencies?: Record<string, string>;
|
|
15
|
-
workspaces?: string[] | { packages: string[] };
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
interface CheckOptions {
|
|
19
|
-
silent: boolean;
|
|
20
|
-
limitedScope: boolean;
|
|
21
|
-
checkType?: 'dependencies' | 'nodeModules';
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export const packageVersionCache = new Map<string, boolean>();
|
|
25
|
-
|
|
26
|
-
export default function hasPackageVersion(
|
|
27
|
-
packageName: string,
|
|
28
|
-
versionRequirement: string,
|
|
29
|
-
): boolean {
|
|
30
|
-
return hasPackageVersionFromPath(packageName, versionRequirement, process.cwd(), false);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function hasPackageVersionFromPath(
|
|
34
|
-
packageName: string,
|
|
35
|
-
versionRequirement: string,
|
|
36
|
-
startPath: string,
|
|
37
|
-
isMonorepo = false,
|
|
38
|
-
): boolean {
|
|
39
|
-
const cacheKey = `${packageName}@${versionRequirement}@${startPath}@${isMonorepo}`;
|
|
40
|
-
|
|
41
|
-
if (packageVersionCache.has(cacheKey)) {
|
|
42
|
-
return packageVersionCache.get(cacheKey)!;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
let result: boolean;
|
|
46
|
-
|
|
47
|
-
if (isMonorepo) {
|
|
48
|
-
const projectRoot = findProjectRoot(startPath);
|
|
49
|
-
const allWorkspacePackages = projectRoot ? getWorkspacePackages(projectRoot) : [];
|
|
50
|
-
|
|
51
|
-
// Filter workspace packages to only include those within the target directory
|
|
52
|
-
const resolvedStartPath = path.resolve(startPath);
|
|
53
|
-
const targetWorkspacePackages = allWorkspacePackages.filter((packagePath) =>
|
|
54
|
-
path.resolve(packagePath).startsWith(resolvedStartPath),
|
|
55
|
-
);
|
|
56
|
-
|
|
57
|
-
if (targetWorkspacePackages.length > 0) {
|
|
58
|
-
result = checkWorkspacePackages(
|
|
59
|
-
projectRoot!,
|
|
60
|
-
packageName,
|
|
61
|
-
versionRequirement,
|
|
62
|
-
targetWorkspacePackages,
|
|
63
|
-
);
|
|
64
|
-
} else {
|
|
65
|
-
// Fallback to checking the target directory directly for packages
|
|
66
|
-
result = checkMonorepoPackages(startPath, packageName, versionRequirement);
|
|
67
|
-
}
|
|
68
|
-
} else {
|
|
69
|
-
result = checkSinglePackage(startPath, packageName, versionRequirement);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
packageVersionCache.set(cacheKey, result);
|
|
73
|
-
return result;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function getWorkspacePackages(projectRoot: string): string[] {
|
|
77
|
-
return [
|
|
78
|
-
...new Set([
|
|
79
|
-
...extractWorkspacesFromPackageJson(projectRoot),
|
|
80
|
-
...extractWorkspacesFromPnpmWorkspace(projectRoot),
|
|
81
|
-
]),
|
|
82
|
-
];
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function extractWorkspacesFromPackageJson(projectRoot: string): string[] {
|
|
86
|
-
const packageJsonPath = path.join(projectRoot, 'package.json');
|
|
87
|
-
|
|
88
|
-
try {
|
|
89
|
-
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')) as PackageJson;
|
|
90
|
-
const patterns = Array.isArray(packageJson.workspaces)
|
|
91
|
-
? packageJson.workspaces
|
|
92
|
-
: (packageJson.workspaces?.packages ?? []);
|
|
93
|
-
|
|
94
|
-
return expandWorkspacePatterns(projectRoot, patterns);
|
|
95
|
-
} catch {
|
|
96
|
-
return [];
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function extractWorkspacesFromPnpmWorkspace(projectRoot: string): string[] {
|
|
101
|
-
const pnpmWorkspacePath = path.join(projectRoot, 'pnpm-workspace.yaml');
|
|
102
|
-
|
|
103
|
-
try {
|
|
104
|
-
const content = readFileSync(pnpmWorkspacePath, 'utf8');
|
|
105
|
-
|
|
106
|
-
const { packages = [] } = parseYaml(content) as { packages?: string[] };
|
|
107
|
-
return expandWorkspacePatterns(projectRoot, packages);
|
|
108
|
-
} catch {
|
|
109
|
-
return [];
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function expandWorkspacePatterns(projectRoot: string, patterns: string[]): string[] {
|
|
114
|
-
return patterns.flatMap((pattern) => {
|
|
115
|
-
if (pattern.includes('**')) {
|
|
116
|
-
return findPackagesRecursively(projectRoot, pattern);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const parts = pattern.split('/');
|
|
120
|
-
const packages: string[] = [];
|
|
121
|
-
|
|
122
|
-
function searchPath(currentPath: string, remainingParts: string[]): void {
|
|
123
|
-
if (remainingParts.length === 0) {
|
|
124
|
-
if (isPackageDirectory(currentPath)) packages.push(currentPath);
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const [part, ...rest] = remainingParts;
|
|
129
|
-
|
|
130
|
-
if (part === '*') {
|
|
131
|
-
try {
|
|
132
|
-
readdirSync(currentPath, { withFileTypes: true })
|
|
133
|
-
.filter((entry) => entry.isDirectory())
|
|
134
|
-
.forEach((entry) => searchPath(path.join(currentPath, entry.name), rest));
|
|
135
|
-
} catch {}
|
|
136
|
-
} else {
|
|
137
|
-
const nextPath = path.join(currentPath, part);
|
|
138
|
-
if (existsSync(nextPath)) searchPath(nextPath, rest);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
searchPath(projectRoot, parts);
|
|
143
|
-
return packages;
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function findPackagesRecursively(projectRoot: string, pattern: string): string[] {
|
|
148
|
-
const packages: string[] = [];
|
|
149
|
-
const prefix = pattern.split('**')[0].replace(/\/$/u, '');
|
|
150
|
-
const startPath = path.join(projectRoot, prefix);
|
|
151
|
-
|
|
152
|
-
function walk(currentPath: string, depth = 0): void {
|
|
153
|
-
if (depth > 8 || !existsSync(currentPath)) return;
|
|
154
|
-
|
|
155
|
-
try {
|
|
156
|
-
readdirSync(currentPath, { withFileTypes: true })
|
|
157
|
-
.filter((entry) => entry.isDirectory())
|
|
158
|
-
.forEach((entry) => {
|
|
159
|
-
const fullPath = path.join(currentPath, entry.name);
|
|
160
|
-
if (isPackageDirectory(fullPath)) packages.push(fullPath);
|
|
161
|
-
walk(fullPath, depth + 1);
|
|
162
|
-
});
|
|
163
|
-
} catch {}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
walk(startPath);
|
|
167
|
-
return packages;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function isPackageDirectory(dirPath: string): boolean {
|
|
171
|
-
try {
|
|
172
|
-
return statSync(dirPath).isDirectory() && existsSync(path.join(dirPath, 'package.json'));
|
|
173
|
-
} catch {
|
|
174
|
-
return false;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function checkWorkspacePackages(
|
|
179
|
-
projectRoot: string,
|
|
180
|
-
packageName: string,
|
|
181
|
-
versionRequirement: string,
|
|
182
|
-
workspacePaths: string[],
|
|
183
|
-
): boolean {
|
|
184
|
-
const foundPackages: string[] = [];
|
|
185
|
-
const notFoundPackages: string[] = [];
|
|
186
|
-
|
|
187
|
-
for (const workspacePath of workspacePaths) {
|
|
188
|
-
const packageDir = path.basename(workspacePath);
|
|
189
|
-
const found = checkPackageInDirectory(workspacePath, packageName, versionRequirement, {
|
|
190
|
-
silent: true,
|
|
191
|
-
limitedScope: true,
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
(found ? foundPackages : notFoundPackages).push(packageDir);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
console.log(`📁 Found ${workspacePaths.length} packages to check`);
|
|
198
|
-
|
|
199
|
-
if (foundPackages.length > 0) {
|
|
200
|
-
console.log(
|
|
201
|
-
`✅ Found ${packageName} in ${foundPackages.length}/${workspacePaths.length} packages:`,
|
|
202
|
-
);
|
|
203
|
-
console.log(` 📦 ${foundPackages.join(', ')}`);
|
|
204
|
-
|
|
205
|
-
if (notFoundPackages.length > 0) {
|
|
206
|
-
console.log(`❌ Not found in ${notFoundPackages.length} packages:`);
|
|
207
|
-
console.log(` 📦 ${notFoundPackages.join(', ')}`);
|
|
208
|
-
}
|
|
209
|
-
} else {
|
|
210
|
-
console.log(`❌ Package ${packageName} not found in any of ${workspacePaths.length} packages`);
|
|
211
|
-
if (notFoundPackages.length > 0) {
|
|
212
|
-
console.log(` 📦 Checked: ${notFoundPackages.join(', ')}`);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
return foundPackages.length > 0;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
function checkMonorepoPackages(
|
|
220
|
-
packagesPath: string,
|
|
221
|
-
packageName: string,
|
|
222
|
-
versionRequirement: string,
|
|
223
|
-
): boolean {
|
|
224
|
-
console.log(`📦 Checking monorepo packages in: ${packagesPath}`);
|
|
225
|
-
try {
|
|
226
|
-
if (!existsSync(packagesPath)) {
|
|
227
|
-
return false;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
const packageDirs = readdirSync(packagesPath).filter((entry) =>
|
|
231
|
-
statSync(path.join(packagesPath, entry)).isDirectory(),
|
|
232
|
-
);
|
|
233
|
-
|
|
234
|
-
console.log(`📁 Found ${packageDirs.length} packages to check`);
|
|
235
|
-
|
|
236
|
-
const foundPackages: string[] = [];
|
|
237
|
-
const notFoundPackages: string[] = [];
|
|
238
|
-
|
|
239
|
-
for (const entry of packageDirs) {
|
|
240
|
-
const packageDir = path.join(packagesPath, entry);
|
|
241
|
-
const found = checkPackageInDirectory(packageDir, packageName, versionRequirement, {
|
|
242
|
-
silent: true,
|
|
243
|
-
limitedScope: true,
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
(found ? foundPackages : notFoundPackages).push(entry);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
if (foundPackages.length > 0) {
|
|
250
|
-
console.log(
|
|
251
|
-
`✅ Found ${packageName} in ${foundPackages.length}/${packageDirs.length} packages:`,
|
|
252
|
-
);
|
|
253
|
-
console.log(` 📦 ${foundPackages.join(', ')}`);
|
|
254
|
-
|
|
255
|
-
if (notFoundPackages.length > 0) {
|
|
256
|
-
console.log(`❌ Not found in ${notFoundPackages.length} packages:`);
|
|
257
|
-
console.log(` 📦 ${notFoundPackages.join(', ')}`);
|
|
258
|
-
}
|
|
259
|
-
} else {
|
|
260
|
-
console.log(`❌ Package ${packageName} not found in any of ${packageDirs.length} packages`);
|
|
261
|
-
console.log(` 📦 Checked: ${notFoundPackages.join(', ')}`);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
return foundPackages.length > 0;
|
|
265
|
-
} catch (error) {
|
|
266
|
-
console.log(`⚠️ Error checking monorepo packages:`, error);
|
|
267
|
-
return false;
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
function checkSinglePackage(
|
|
272
|
-
packagePath: string,
|
|
273
|
-
packageName: string,
|
|
274
|
-
versionRequirement: string,
|
|
275
|
-
): boolean {
|
|
276
|
-
return checkPackageInDirectory(packagePath, packageName, versionRequirement, {
|
|
277
|
-
silent: true,
|
|
278
|
-
limitedScope: true,
|
|
279
|
-
});
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
function checkPackageInDirectory(
|
|
283
|
-
startPath: string,
|
|
284
|
-
packageName: string,
|
|
285
|
-
versionRequirement: string,
|
|
286
|
-
options: CheckOptions,
|
|
287
|
-
): boolean {
|
|
288
|
-
const { checkType } = options;
|
|
289
|
-
const packageJsonResult = checkDirectDependencies(
|
|
290
|
-
startPath,
|
|
291
|
-
packageName,
|
|
292
|
-
versionRequirement,
|
|
293
|
-
options,
|
|
294
|
-
);
|
|
295
|
-
|
|
296
|
-
if (checkType === 'dependencies') return packageJsonResult;
|
|
297
|
-
if (packageJsonResult) return true;
|
|
298
|
-
if (checkType === 'nodeModules')
|
|
299
|
-
return checkNodeModules(startPath, packageName, versionRequirement, options);
|
|
300
|
-
|
|
301
|
-
return checkNodeModules(startPath, packageName, versionRequirement, options);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
function checkDirectDependencies(
|
|
305
|
-
startPath: string,
|
|
306
|
-
packageName: string,
|
|
307
|
-
versionRequirement: string,
|
|
308
|
-
{ limitedScope }: CheckOptions,
|
|
309
|
-
): boolean {
|
|
310
|
-
const checker = (dir: string) =>
|
|
311
|
-
checkPackageJsonInDirectory(dir, packageName, versionRequirement);
|
|
312
|
-
return limitedScope ? checker(startPath) : walkDirectoryTree(startPath, checker);
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
function checkNodeModules(
|
|
316
|
-
startPath: string,
|
|
317
|
-
packageName: string,
|
|
318
|
-
versionRequirement: string,
|
|
319
|
-
{ limitedScope }: CheckOptions,
|
|
320
|
-
): boolean {
|
|
321
|
-
const checker = (dir: string) =>
|
|
322
|
-
checkStandardNodeModules(dir, packageName, versionRequirement) ||
|
|
323
|
-
checkPnpmNodeModules(dir, packageName, versionRequirement);
|
|
324
|
-
|
|
325
|
-
return limitedScope ? checker(startPath) : walkDirectoryTree(startPath, checker);
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
function walkDirectoryTree(startPath: string, checkFunction: (dir: string) => boolean): boolean {
|
|
329
|
-
let currentDir =
|
|
330
|
-
existsSync(startPath) && statSync(startPath).isFile() ? path.dirname(startPath) : startPath;
|
|
331
|
-
|
|
332
|
-
currentDir = path.resolve(currentDir);
|
|
333
|
-
|
|
334
|
-
const { root } = path.parse(currentDir);
|
|
335
|
-
let dirCount = 0;
|
|
336
|
-
let previousDir = '';
|
|
337
|
-
|
|
338
|
-
while (currentDir !== root && currentDir !== previousDir && dirCount < 10) {
|
|
339
|
-
dirCount += 1;
|
|
340
|
-
|
|
341
|
-
if (checkFunction(currentDir)) {
|
|
342
|
-
return true;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
previousDir = currentDir;
|
|
346
|
-
currentDir = path.dirname(currentDir);
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
return false;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
function checkPackageJsonInDirectory(
|
|
353
|
-
dir: string,
|
|
354
|
-
packageName: string,
|
|
355
|
-
versionRequirement: string,
|
|
356
|
-
): boolean {
|
|
357
|
-
const packageJsonPath = path.join(dir, 'package.json');
|
|
358
|
-
|
|
359
|
-
if (!existsSync(packageJsonPath)) return false;
|
|
360
|
-
|
|
361
|
-
try {
|
|
362
|
-
const packageJson: PackageJson = JSON.parse(
|
|
363
|
-
readFileSync(packageJsonPath, 'utf8'),
|
|
364
|
-
) as PackageJson;
|
|
365
|
-
const allDeps = {
|
|
366
|
-
...(packageJson.dependencies ?? {}),
|
|
367
|
-
...(packageJson.devDependencies ?? {}),
|
|
368
|
-
...(packageJson.peerDependencies ?? {}),
|
|
369
|
-
...(packageJson.optionalDependencies ?? {}),
|
|
370
|
-
};
|
|
371
|
-
|
|
372
|
-
const installedVersion = allDeps[packageName];
|
|
373
|
-
return installedVersion ? isVersionSatisfied(installedVersion, versionRequirement) : false;
|
|
374
|
-
} catch {
|
|
375
|
-
return false;
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
function isVersionSatisfied(installedVersion: string, versionRequirement: string): boolean {
|
|
380
|
-
if (semver.valid(installedVersion) && semver.satisfies(installedVersion, versionRequirement)) {
|
|
381
|
-
return true;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
const cleanVersion = semver.coerce(installedVersion);
|
|
385
|
-
return cleanVersion ? semver.satisfies(cleanVersion.version, versionRequirement) : false;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
function checkStandardNodeModules(
|
|
389
|
-
baseDir: string,
|
|
390
|
-
packageName: string,
|
|
391
|
-
versionRequirement: string,
|
|
392
|
-
): boolean {
|
|
393
|
-
const packagePath = path.join(baseDir, 'node_modules', packageName, 'package.json');
|
|
394
|
-
if (!existsSync(packagePath)) return false;
|
|
395
|
-
|
|
396
|
-
try {
|
|
397
|
-
const packageJson: PackageJson = JSON.parse(readFileSync(packagePath, 'utf8')) as PackageJson;
|
|
398
|
-
return packageJson.version ? semver.satisfies(packageJson.version, versionRequirement) : false;
|
|
399
|
-
} catch {
|
|
400
|
-
return false;
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
function checkPnpmNodeModules(
|
|
405
|
-
baseDir: string,
|
|
406
|
-
packageName: string,
|
|
407
|
-
versionRequirement: string,
|
|
408
|
-
): boolean {
|
|
409
|
-
const pnpmDir = path.join(baseDir, 'node_modules', '.pnpm');
|
|
410
|
-
if (!existsSync(pnpmDir)) return false;
|
|
411
|
-
|
|
412
|
-
try {
|
|
413
|
-
const entries = readdirSync(pnpmDir);
|
|
414
|
-
|
|
415
|
-
for (const entry of entries) {
|
|
416
|
-
if (entry.startsWith(packageName.replace('/', '+')) || entry.includes(`${packageName}@`)) {
|
|
417
|
-
const packagePath = path.join(pnpmDir, entry, 'node_modules', packageName, 'package.json');
|
|
418
|
-
|
|
419
|
-
if (existsSync(packagePath)) {
|
|
420
|
-
const packageJson: PackageJson = JSON.parse(
|
|
421
|
-
readFileSync(packagePath, 'utf8'),
|
|
422
|
-
) as PackageJson;
|
|
423
|
-
if (packageJson.version && semver.satisfies(packageJson.version, versionRequirement)) {
|
|
424
|
-
return true;
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
} catch {
|
|
430
|
-
// Silent
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
return false;
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
export function findProjectRoot(startPath: string): string | null {
|
|
437
|
-
try {
|
|
438
|
-
const gitRoot = execSync('git rev-parse --show-toplevel', {
|
|
439
|
-
cwd: startPath,
|
|
440
|
-
encoding: 'utf8',
|
|
441
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
442
|
-
}).trim();
|
|
443
|
-
|
|
444
|
-
return gitRoot && existsSync(gitRoot) ? gitRoot : null;
|
|
445
|
-
} catch {
|
|
446
|
-
return null;
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
export function findClosestGitignore(startPath: string): string | null {
|
|
451
|
-
const projectRoot = findProjectRoot(startPath);
|
|
452
|
-
|
|
453
|
-
if (!projectRoot) {
|
|
454
|
-
return null;
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
const gitignorePath = path.join(projectRoot, '.gitignore');
|
|
458
|
-
return existsSync(gitignorePath) ? gitignorePath : null;
|
|
459
|
-
}
|