react-doctor 0.0.17 → 0.0.19
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/README.md +41 -0
- package/dist/cli.js +470 -222
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +22 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +196 -27
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.d.ts
CHANGED
|
@@ -25,11 +25,32 @@ interface ScoreResult {
|
|
|
25
25
|
score: number;
|
|
26
26
|
label: string;
|
|
27
27
|
}
|
|
28
|
+
interface DiffInfo {
|
|
29
|
+
currentBranch: string;
|
|
30
|
+
baseBranch: string;
|
|
31
|
+
changedFiles: string[];
|
|
32
|
+
}
|
|
33
|
+
interface ReactDoctorIgnoreConfig {
|
|
34
|
+
rules?: string[];
|
|
35
|
+
files?: string[];
|
|
36
|
+
}
|
|
37
|
+
interface ReactDoctorConfig {
|
|
38
|
+
ignore?: ReactDoctorIgnoreConfig;
|
|
39
|
+
lint?: boolean;
|
|
40
|
+
deadCode?: boolean;
|
|
41
|
+
verbose?: boolean;
|
|
42
|
+
diff?: boolean | string;
|
|
43
|
+
}
|
|
44
|
+
//#endregion
|
|
45
|
+
//#region src/utils/get-diff-files.d.ts
|
|
46
|
+
declare const getDiffInfo: (directory: string, explicitBaseBranch?: string) => DiffInfo | null;
|
|
47
|
+
declare const filterSourceFiles: (filePaths: string[]) => string[];
|
|
28
48
|
//#endregion
|
|
29
49
|
//#region src/index.d.ts
|
|
30
50
|
interface DiagnoseOptions {
|
|
31
51
|
lint?: boolean;
|
|
32
52
|
deadCode?: boolean;
|
|
53
|
+
includePaths?: string[];
|
|
33
54
|
}
|
|
34
55
|
interface DiagnoseResult {
|
|
35
56
|
diagnostics: Diagnostic[];
|
|
@@ -39,5 +60,5 @@ interface DiagnoseResult {
|
|
|
39
60
|
}
|
|
40
61
|
declare const diagnose: (directory: string, options?: DiagnoseOptions) => Promise<DiagnoseResult>;
|
|
41
62
|
//#endregion
|
|
42
|
-
export { DiagnoseOptions, DiagnoseResult, type Diagnostic, type ProjectInfo, type ScoreResult, diagnose };
|
|
63
|
+
export { DiagnoseOptions, DiagnoseResult, type Diagnostic, type DiffInfo, type ProjectInfo, type ReactDoctorConfig, type ScoreResult, diagnose, filterSourceFiles, getDiffInfo };
|
|
43
64
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/index.ts"],"mappings":";KAAY,SAAA;AAAA,UAEK,WAAA;EACf,aAAA;EACA,WAAA;EACA,YAAA;EACA,SAAA,EAAW,SAAA;EACX,aAAA;EACA,gBAAA;EACA,eAAA;AAAA;AAAA,UAiCe,UAAA;EACf,QAAA;EACA,MAAA;EACA,IAAA;EACA,QAAA;EACA,OAAA;EACA,IAAA;EACA,IAAA;EACA,MAAA;EACA,QAAA;EACA,MAAA;AAAA;AAAA,
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/utils/get-diff-files.ts","../src/index.ts"],"mappings":";KAAY,SAAA;AAAA,UAEK,WAAA;EACf,aAAA;EACA,WAAA;EACA,YAAA;EACA,SAAA,EAAW,SAAA;EACX,aAAA;EACA,gBAAA;EACA,eAAA;AAAA;AAAA,UAiCe,UAAA;EACf,QAAA;EACA,MAAA;EACA,IAAA;EACA,QAAA;EACA,OAAA;EACA,IAAA;EACA,IAAA;EACA,MAAA;EACA,QAAA;EACA,MAAA;AAAA;AAAA,UA4Be,WAAA;EACf,KAAA;EACA,KAAA;AAAA;AAAA,UAWe,QAAA;EACf,aAAA;EACA,UAAA;EACA,YAAA;AAAA;AAAA,UAqDe,uBAAA;EACf,KAAA;EACA,KAAA;AAAA;AAAA,UAGe,iBAAA;EACf,MAAA,GAAS,uBAAA;EACT,IAAA;EACA,QAAA;EACA,OAAA;EACA,IAAA;AAAA;;;cC/FW,WAAA,GAAe,SAAA,UAAmB,kBAAA,cAA8B,QAAA;AAAA,cAYhE,iBAAA,GAAqB,SAAA;;;UC7DjB,eAAA;EACf,IAAA;EACA,QAAA;EACA,YAAA;AAAA;AAAA,UAGe,cAAA;EACf,WAAA,EAAa,UAAA;EACb,KAAA,EAAO,WAAA;EACP,OAAA,EAAS,WAAA;EACT,mBAAA;AAAA;AAAA,cAGW,QAAA,GACX,SAAA,UACA,OAAA,GAAS,eAAA,KACR,OAAA,CAAQ,cAAA"}
|
package/dist/index.js
CHANGED
|
@@ -14,6 +14,7 @@ const JSX_FILE_PATTERN = /\.(tsx|jsx)$/;
|
|
|
14
14
|
const ERROR_PREVIEW_LENGTH_CHARS = 200;
|
|
15
15
|
const SCORE_API_URL = "https://www.react.doctor/api/score";
|
|
16
16
|
const GIT_LS_FILES_MAX_BUFFER_BYTES = 50 * 1024 * 1024;
|
|
17
|
+
const DEFAULT_BRANCH_CANDIDATES = ["main", "master"];
|
|
17
18
|
|
|
18
19
|
//#endregion
|
|
19
20
|
//#region src/utils/calculate-score.ts
|
|
@@ -136,6 +137,7 @@ const countSourceFiles = (rootDirectory) => {
|
|
|
136
137
|
return result.stdout.split("\n").filter((filePath) => filePath.length > 0 && SOURCE_FILE_PATTERN.test(filePath)).length;
|
|
137
138
|
};
|
|
138
139
|
const collectAllDependencies = (packageJson) => ({
|
|
140
|
+
...packageJson.peerDependencies,
|
|
139
141
|
...packageJson.dependencies,
|
|
140
142
|
...packageJson.devDependencies
|
|
141
143
|
});
|
|
@@ -185,23 +187,34 @@ const resolveWorkspaceDirectories = (rootDirectory, pattern) => {
|
|
|
185
187
|
if (!fs.existsSync(baseDirectory) || !fs.statSync(baseDirectory).isDirectory()) return [];
|
|
186
188
|
return fs.readdirSync(baseDirectory).map((entry) => path.join(baseDirectory, entry)).filter((entryPath) => fs.statSync(entryPath).isDirectory() && fs.existsSync(path.join(entryPath, "package.json")));
|
|
187
189
|
};
|
|
188
|
-
const
|
|
190
|
+
const isMonorepoRoot = (directory) => {
|
|
191
|
+
if (fs.existsSync(path.join(directory, "pnpm-workspace.yaml"))) return true;
|
|
192
|
+
const packageJsonPath = path.join(directory, "package.json");
|
|
193
|
+
if (!fs.existsSync(packageJsonPath)) return false;
|
|
194
|
+
const packageJson = readPackageJson(packageJsonPath);
|
|
195
|
+
return Array.isArray(packageJson.workspaces) || Boolean(packageJson.workspaces?.packages);
|
|
196
|
+
};
|
|
197
|
+
const findMonorepoRoot$1 = (startDirectory) => {
|
|
189
198
|
let currentDirectory = path.dirname(startDirectory);
|
|
190
|
-
const result = {
|
|
191
|
-
reactVersion: null,
|
|
192
|
-
framework: "unknown"
|
|
193
|
-
};
|
|
194
199
|
while (currentDirectory !== path.dirname(currentDirectory)) {
|
|
195
|
-
|
|
196
|
-
if (fs.existsSync(packageJsonPath)) {
|
|
197
|
-
const info = extractDependencyInfo(readPackageJson(packageJsonPath));
|
|
198
|
-
if (!result.reactVersion && info.reactVersion) result.reactVersion = info.reactVersion;
|
|
199
|
-
if (result.framework === "unknown" && info.framework !== "unknown") result.framework = info.framework;
|
|
200
|
-
if (result.reactVersion && result.framework !== "unknown") return result;
|
|
201
|
-
}
|
|
200
|
+
if (isMonorepoRoot(currentDirectory)) return currentDirectory;
|
|
202
201
|
currentDirectory = path.dirname(currentDirectory);
|
|
203
202
|
}
|
|
204
|
-
return
|
|
203
|
+
return null;
|
|
204
|
+
};
|
|
205
|
+
const findDependencyInfoFromMonorepoRoot = (directory) => {
|
|
206
|
+
const monorepoRoot = findMonorepoRoot$1(directory);
|
|
207
|
+
if (!monorepoRoot) return {
|
|
208
|
+
reactVersion: null,
|
|
209
|
+
framework: "unknown"
|
|
210
|
+
};
|
|
211
|
+
const rootPackageJson = readPackageJson(path.join(monorepoRoot, "package.json"));
|
|
212
|
+
const rootInfo = extractDependencyInfo(rootPackageJson);
|
|
213
|
+
const workspaceInfo = findReactInWorkspaces(monorepoRoot, rootPackageJson);
|
|
214
|
+
return {
|
|
215
|
+
reactVersion: rootInfo.reactVersion ?? workspaceInfo.reactVersion,
|
|
216
|
+
framework: rootInfo.framework !== "unknown" ? rootInfo.framework : workspaceInfo.framework
|
|
217
|
+
};
|
|
205
218
|
};
|
|
206
219
|
const findReactInWorkspaces = (rootDirectory, packageJson) => {
|
|
207
220
|
const patterns = getWorkspacePatterns(rootDirectory, packageJson);
|
|
@@ -255,10 +268,10 @@ const discoverProject = (directory) => {
|
|
|
255
268
|
if (!reactVersion && workspaceInfo.reactVersion) reactVersion = workspaceInfo.reactVersion;
|
|
256
269
|
if (framework === "unknown" && workspaceInfo.framework !== "unknown") framework = workspaceInfo.framework;
|
|
257
270
|
}
|
|
258
|
-
if (!reactVersion || framework === "unknown") {
|
|
259
|
-
const
|
|
260
|
-
if (!reactVersion) reactVersion =
|
|
261
|
-
if (framework === "unknown") framework =
|
|
271
|
+
if ((!reactVersion || framework === "unknown") && !isMonorepoRoot(directory)) {
|
|
272
|
+
const monorepoInfo = findDependencyInfoFromMonorepoRoot(directory);
|
|
273
|
+
if (!reactVersion) reactVersion = monorepoInfo.reactVersion;
|
|
274
|
+
if (framework === "unknown") framework = monorepoInfo.framework;
|
|
262
275
|
}
|
|
263
276
|
const projectName = packageJson.name ?? path.basename(directory);
|
|
264
277
|
const hasTypeScript = fs.existsSync(path.join(directory, "tsconfig.json"));
|
|
@@ -275,6 +288,79 @@ const discoverProject = (directory) => {
|
|
|
275
288
|
};
|
|
276
289
|
};
|
|
277
290
|
|
|
291
|
+
//#endregion
|
|
292
|
+
//#region src/utils/match-glob-pattern.ts
|
|
293
|
+
const REGEX_SPECIAL_CHARACTERS = /[.+^${}()|[\]\\]/g;
|
|
294
|
+
const compileGlobPattern = (pattern) => {
|
|
295
|
+
const normalizedPattern = pattern.replace(/\\/g, "/");
|
|
296
|
+
let regexSource = "^";
|
|
297
|
+
let characterIndex = 0;
|
|
298
|
+
while (characterIndex < normalizedPattern.length) if (normalizedPattern[characterIndex] === "*" && normalizedPattern[characterIndex + 1] === "*") if (normalizedPattern[characterIndex + 2] === "/") {
|
|
299
|
+
regexSource += "(?:.+/)?";
|
|
300
|
+
characterIndex += 3;
|
|
301
|
+
} else {
|
|
302
|
+
regexSource += ".*";
|
|
303
|
+
characterIndex += 2;
|
|
304
|
+
}
|
|
305
|
+
else if (normalizedPattern[characterIndex] === "*") {
|
|
306
|
+
regexSource += "[^/]*";
|
|
307
|
+
characterIndex++;
|
|
308
|
+
} else if (normalizedPattern[characterIndex] === "?") {
|
|
309
|
+
regexSource += "[^/]";
|
|
310
|
+
characterIndex++;
|
|
311
|
+
} else {
|
|
312
|
+
regexSource += normalizedPattern[characterIndex].replace(REGEX_SPECIAL_CHARACTERS, "\\$&");
|
|
313
|
+
characterIndex++;
|
|
314
|
+
}
|
|
315
|
+
regexSource += "$";
|
|
316
|
+
return new RegExp(regexSource);
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
//#endregion
|
|
320
|
+
//#region src/utils/filter-diagnostics.ts
|
|
321
|
+
const filterIgnoredDiagnostics = (diagnostics, config) => {
|
|
322
|
+
const ignoredRules = new Set(Array.isArray(config.ignore?.rules) ? config.ignore.rules : []);
|
|
323
|
+
const ignoredFilePatterns = Array.isArray(config.ignore?.files) ? config.ignore.files.map(compileGlobPattern) : [];
|
|
324
|
+
if (ignoredRules.size === 0 && ignoredFilePatterns.length === 0) return diagnostics;
|
|
325
|
+
return diagnostics.filter((diagnostic) => {
|
|
326
|
+
const ruleIdentifier = `${diagnostic.plugin}/${diagnostic.rule}`;
|
|
327
|
+
if (ignoredRules.has(ruleIdentifier)) return false;
|
|
328
|
+
const normalizedPath = diagnostic.filePath.replace(/\\/g, "/");
|
|
329
|
+
if (ignoredFilePatterns.some((pattern) => pattern.test(normalizedPath))) return false;
|
|
330
|
+
return true;
|
|
331
|
+
});
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
//#endregion
|
|
335
|
+
//#region src/utils/load-config.ts
|
|
336
|
+
const CONFIG_FILENAME = "react-doctor.config.json";
|
|
337
|
+
const PACKAGE_JSON_CONFIG_KEY = "reactDoctor";
|
|
338
|
+
const isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
339
|
+
const loadConfig = (rootDirectory) => {
|
|
340
|
+
const configFilePath = path.join(rootDirectory, CONFIG_FILENAME);
|
|
341
|
+
if (fs.existsSync(configFilePath)) try {
|
|
342
|
+
const fileContent = fs.readFileSync(configFilePath, "utf-8");
|
|
343
|
+
const parsed = JSON.parse(fileContent);
|
|
344
|
+
if (!isPlainObject(parsed)) {
|
|
345
|
+
console.warn(`Warning: ${CONFIG_FILENAME} must be a JSON object, ignoring.`);
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
return parsed;
|
|
349
|
+
} catch (error) {
|
|
350
|
+
console.warn(`Warning: Failed to parse ${CONFIG_FILENAME}: ${error instanceof Error ? error.message : String(error)}`);
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
const packageJsonPath = path.join(rootDirectory, "package.json");
|
|
354
|
+
if (fs.existsSync(packageJsonPath)) try {
|
|
355
|
+
const fileContent = fs.readFileSync(packageJsonPath, "utf-8");
|
|
356
|
+
const embeddedConfig = JSON.parse(fileContent)[PACKAGE_JSON_CONFIG_KEY];
|
|
357
|
+
if (isPlainObject(embeddedConfig)) return embeddedConfig;
|
|
358
|
+
} catch {
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
return null;
|
|
362
|
+
};
|
|
363
|
+
|
|
278
364
|
//#endregion
|
|
279
365
|
//#region src/utils/run-knip.ts
|
|
280
366
|
const KNIP_CATEGORY_MAP = {
|
|
@@ -315,15 +401,18 @@ const silenced = async (fn) => {
|
|
|
315
401
|
const originalLog = console.log;
|
|
316
402
|
const originalInfo = console.info;
|
|
317
403
|
const originalWarn = console.warn;
|
|
404
|
+
const originalError = console.error;
|
|
318
405
|
console.log = () => {};
|
|
319
406
|
console.info = () => {};
|
|
320
407
|
console.warn = () => {};
|
|
408
|
+
console.error = () => {};
|
|
321
409
|
try {
|
|
322
410
|
return await fn();
|
|
323
411
|
} finally {
|
|
324
412
|
console.log = originalLog;
|
|
325
413
|
console.info = originalInfo;
|
|
326
414
|
console.warn = originalWarn;
|
|
415
|
+
console.error = originalError;
|
|
327
416
|
}
|
|
328
417
|
};
|
|
329
418
|
const findMonorepoRoot = (directory) => {
|
|
@@ -339,13 +428,26 @@ const findMonorepoRoot = (directory) => {
|
|
|
339
428
|
}
|
|
340
429
|
return null;
|
|
341
430
|
};
|
|
431
|
+
const CONFIG_LOADING_ERROR_PATTERN = /Error loading .*\/([a-z-]+)\.config\./;
|
|
432
|
+
const extractFailedPluginName = (error) => {
|
|
433
|
+
return String(error).match(CONFIG_LOADING_ERROR_PATTERN)?.[1] ?? null;
|
|
434
|
+
};
|
|
435
|
+
const MAX_KNIP_RETRIES = 5;
|
|
342
436
|
const runKnipWithOptions = async (knipCwd, workspaceName) => {
|
|
343
437
|
const options = await silenced(() => createOptions({
|
|
344
438
|
cwd: knipCwd,
|
|
345
439
|
isShowProgress: false,
|
|
346
440
|
...workspaceName ? { workspace: workspaceName } : {}
|
|
347
441
|
}));
|
|
348
|
-
|
|
442
|
+
const parsedConfig = options.parsedConfig;
|
|
443
|
+
for (let attempt = 0; attempt <= MAX_KNIP_RETRIES; attempt++) try {
|
|
444
|
+
return await silenced(() => main(options));
|
|
445
|
+
} catch (error) {
|
|
446
|
+
const failedPlugin = extractFailedPluginName(error);
|
|
447
|
+
if (!failedPlugin || attempt === MAX_KNIP_RETRIES) throw error;
|
|
448
|
+
parsedConfig[failedPlugin] = false;
|
|
449
|
+
}
|
|
450
|
+
throw new Error("Unreachable");
|
|
349
451
|
};
|
|
350
452
|
const hasNodeModules = (directory) => {
|
|
351
453
|
const nodeModulesPath = path.join(directory, "node_modules");
|
|
@@ -675,7 +777,8 @@ const resolvePluginPath = () => {
|
|
|
675
777
|
const resolveDiagnosticCategory = (plugin, rule) => {
|
|
676
778
|
return RULE_CATEGORY_MAP[`${plugin}/${rule}`] ?? PLUGIN_CATEGORY_MAP[plugin] ?? "Other";
|
|
677
779
|
};
|
|
678
|
-
const runOxlint = async (rootDirectory, hasTypeScript, framework, hasReactCompiler) => {
|
|
780
|
+
const runOxlint = async (rootDirectory, hasTypeScript, framework, hasReactCompiler, includePaths) => {
|
|
781
|
+
if (includePaths !== void 0 && includePaths.length === 0) return [];
|
|
679
782
|
const configPath = path.join(os.tmpdir(), `react-doctor-oxlintrc-${process.pid}.json`);
|
|
680
783
|
const config = createOxlintConfig({
|
|
681
784
|
pluginPath: resolvePluginPath(),
|
|
@@ -692,7 +795,8 @@ const runOxlint = async (rootDirectory, hasTypeScript, framework, hasReactCompil
|
|
|
692
795
|
"json"
|
|
693
796
|
];
|
|
694
797
|
if (hasTypeScript) args.push("--tsconfig", "./tsconfig.json");
|
|
695
|
-
args.push(
|
|
798
|
+
if (includePaths !== void 0) args.push(...includePaths);
|
|
799
|
+
else args.push(".");
|
|
696
800
|
const stdout = await new Promise((resolve, reject) => {
|
|
697
801
|
const child = spawn(process.execPath, args, { cwd: rootDirectory });
|
|
698
802
|
const stdoutBuffers = [];
|
|
@@ -719,7 +823,7 @@ const runOxlint = async (rootDirectory, hasTypeScript, framework, hasReactCompil
|
|
|
719
823
|
} catch {
|
|
720
824
|
throw new Error(`Failed to parse oxlint output: ${stdout.slice(0, ERROR_PREVIEW_LENGTH_CHARS)}`);
|
|
721
825
|
}
|
|
722
|
-
return output.diagnostics.filter((diagnostic) => JSX_FILE_PATTERN.test(diagnostic.filename)).map((diagnostic) => {
|
|
826
|
+
return output.diagnostics.filter((diagnostic) => diagnostic.code && JSX_FILE_PATTERN.test(diagnostic.filename)).map((diagnostic) => {
|
|
723
827
|
const { plugin, rule } = parseRuleCode(diagnostic.code);
|
|
724
828
|
const primaryLabel = diagnostic.labels[0];
|
|
725
829
|
const cleaned = cleanDiagnosticMessage(diagnostic.message, diagnostic.help, plugin, rule);
|
|
@@ -740,28 +844,93 @@ const runOxlint = async (rootDirectory, hasTypeScript, framework, hasReactCompil
|
|
|
740
844
|
}
|
|
741
845
|
};
|
|
742
846
|
|
|
847
|
+
//#endregion
|
|
848
|
+
//#region src/utils/get-diff-files.ts
|
|
849
|
+
const getCurrentBranch = (directory) => {
|
|
850
|
+
try {
|
|
851
|
+
const branch = execSync("git rev-parse --abbrev-ref HEAD", {
|
|
852
|
+
cwd: directory,
|
|
853
|
+
stdio: "pipe"
|
|
854
|
+
}).toString().trim();
|
|
855
|
+
return branch === "HEAD" ? null : branch;
|
|
856
|
+
} catch {
|
|
857
|
+
return null;
|
|
858
|
+
}
|
|
859
|
+
};
|
|
860
|
+
const detectDefaultBranch = (directory) => {
|
|
861
|
+
try {
|
|
862
|
+
return execSync("git symbolic-ref refs/remotes/origin/HEAD", {
|
|
863
|
+
cwd: directory,
|
|
864
|
+
stdio: "pipe"
|
|
865
|
+
}).toString().trim().replace("refs/remotes/origin/", "");
|
|
866
|
+
} catch {
|
|
867
|
+
for (const candidate of DEFAULT_BRANCH_CANDIDATES) try {
|
|
868
|
+
execSync(`git rev-parse --verify ${candidate}`, {
|
|
869
|
+
cwd: directory,
|
|
870
|
+
stdio: "pipe"
|
|
871
|
+
});
|
|
872
|
+
return candidate;
|
|
873
|
+
} catch {}
|
|
874
|
+
return null;
|
|
875
|
+
}
|
|
876
|
+
};
|
|
877
|
+
const getChangedFilesSinceBranch = (directory, baseBranch) => {
|
|
878
|
+
try {
|
|
879
|
+
const output = execSync(`git diff --name-only --diff-filter=ACMR --relative ${execSync(`git merge-base ${baseBranch} HEAD`, {
|
|
880
|
+
cwd: directory,
|
|
881
|
+
stdio: "pipe"
|
|
882
|
+
}).toString().trim()}`, {
|
|
883
|
+
cwd: directory,
|
|
884
|
+
stdio: "pipe"
|
|
885
|
+
}).toString().trim();
|
|
886
|
+
if (!output) return [];
|
|
887
|
+
return output.split("\n").filter(Boolean);
|
|
888
|
+
} catch {
|
|
889
|
+
return [];
|
|
890
|
+
}
|
|
891
|
+
};
|
|
892
|
+
const getDiffInfo = (directory, explicitBaseBranch) => {
|
|
893
|
+
const currentBranch = getCurrentBranch(directory);
|
|
894
|
+
if (!currentBranch) return null;
|
|
895
|
+
const baseBranch = explicitBaseBranch ?? detectDefaultBranch(directory);
|
|
896
|
+
if (!baseBranch) return null;
|
|
897
|
+
if (currentBranch === baseBranch) return null;
|
|
898
|
+
return {
|
|
899
|
+
currentBranch,
|
|
900
|
+
baseBranch,
|
|
901
|
+
changedFiles: getChangedFilesSinceBranch(directory, baseBranch)
|
|
902
|
+
};
|
|
903
|
+
};
|
|
904
|
+
const filterSourceFiles = (filePaths) => filePaths.filter((filePath) => SOURCE_FILE_PATTERN.test(filePath));
|
|
905
|
+
|
|
743
906
|
//#endregion
|
|
744
907
|
//#region src/index.ts
|
|
745
908
|
const diagnose = async (directory, options = {}) => {
|
|
746
|
-
const {
|
|
909
|
+
const { includePaths = [] } = options;
|
|
910
|
+
const isDiffMode = includePaths.length > 0;
|
|
747
911
|
const startTime = performance.now();
|
|
748
912
|
const resolvedDirectory = path.resolve(directory);
|
|
749
913
|
const projectInfo = discoverProject(resolvedDirectory);
|
|
914
|
+
const userConfig = loadConfig(resolvedDirectory);
|
|
915
|
+
const effectiveLint = options.lint ?? userConfig?.lint ?? true;
|
|
916
|
+
const effectiveDeadCode = options.deadCode ?? userConfig?.deadCode ?? true;
|
|
750
917
|
if (!projectInfo.reactVersion) throw new Error("No React dependency found in package.json");
|
|
751
|
-
const
|
|
918
|
+
const jsxIncludePaths = isDiffMode ? includePaths.filter((filePath) => JSX_FILE_PATTERN.test(filePath)) : void 0;
|
|
919
|
+
const lintPromise = effectiveLint ? runOxlint(resolvedDirectory, projectInfo.hasTypeScript, projectInfo.framework, projectInfo.hasReactCompiler, jsxIncludePaths).catch((error) => {
|
|
752
920
|
console.error("Lint failed:", error);
|
|
753
921
|
return [];
|
|
754
922
|
}) : Promise.resolve([]);
|
|
755
|
-
const deadCodePromise =
|
|
923
|
+
const deadCodePromise = effectiveDeadCode && !isDiffMode ? runKnip(resolvedDirectory).catch((error) => {
|
|
756
924
|
console.error("Dead code analysis failed:", error);
|
|
757
925
|
return [];
|
|
758
926
|
}) : Promise.resolve([]);
|
|
759
927
|
const [lintDiagnostics, deadCodeDiagnostics] = await Promise.all([lintPromise, deadCodePromise]);
|
|
760
|
-
const
|
|
928
|
+
const allDiagnostics = [
|
|
761
929
|
...lintDiagnostics,
|
|
762
930
|
...deadCodeDiagnostics,
|
|
763
|
-
...checkReducedMotion(resolvedDirectory)
|
|
931
|
+
...isDiffMode ? [] : checkReducedMotion(resolvedDirectory)
|
|
764
932
|
];
|
|
933
|
+
const diagnostics = userConfig ? filterIgnoredDiagnostics(allDiagnostics, userConfig) : allDiagnostics;
|
|
765
934
|
const elapsedMilliseconds = performance.now() - startTime;
|
|
766
935
|
return {
|
|
767
936
|
diagnostics,
|
|
@@ -772,5 +941,5 @@ const diagnose = async (directory, options = {}) => {
|
|
|
772
941
|
};
|
|
773
942
|
|
|
774
943
|
//#endregion
|
|
775
|
-
export { diagnose };
|
|
944
|
+
export { diagnose, filterSourceFiles, getDiffInfo };
|
|
776
945
|
//# sourceMappingURL=index.js.map
|