react-doctor 0.7.6-dev.82e10cd → 0.7.6-dev.88ef7fd
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/cli.js +226 -60
- package/dist/index.d.ts +5 -4
- package/dist/index.js +87 -37
- package/dist/lsp.js +86 -36
- package/package.json +4 -4
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
2
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="58e54b02-7ba2-5f13-a535-98c236467a86")}catch(e){}}();
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import * as NodeChildProcess from "node:child_process";
|
|
5
5
|
import { execFile, execFileSync, spawn, spawnSync } from "node:child_process";
|
|
@@ -36341,10 +36341,12 @@ const listSourceFilesViaFilesystem = (rootDirectory) => {
|
|
|
36341
36341
|
const listSourceFilesWithSize = (rootDirectory) => collectSizedSourceFiles(rootDirectory, (listSourceFilesViaGit(rootDirectory) ?? listSourceFilesViaFilesystem(rootDirectory)).sort());
|
|
36342
36342
|
const listSourceFiles = (rootDirectory) => listSourceFilesWithSize(rootDirectory).map((entry) => entry.path);
|
|
36343
36343
|
const countSourceFiles = (rootDirectory) => listSourceFilesWithSize(rootDirectory).length;
|
|
36344
|
+
const UTF8_BOM_CHARACTER = "";
|
|
36345
|
+
const stripUtf8Bom = (content) => content.startsWith(UTF8_BOM_CHARACTER) ? content.slice(1) : content;
|
|
36344
36346
|
const cachedPackageJsons = /* @__PURE__ */ new Map();
|
|
36345
36347
|
const readPackageJsonUncached = (packageJsonPath) => {
|
|
36346
36348
|
try {
|
|
36347
|
-
return JSON.parse(NFS.readFileSync(packageJsonPath, "utf-8"));
|
|
36349
|
+
return JSON.parse(stripUtf8Bom(NFS.readFileSync(packageJsonPath, "utf-8")));
|
|
36348
36350
|
} catch (error) {
|
|
36349
36351
|
if (error instanceof SyntaxError) return {};
|
|
36350
36352
|
if (isErrnoException(error)) {
|
|
@@ -40017,7 +40019,6 @@ const PNPM_WORKSPACE_FILE = "pnpm-workspace.yaml";
|
|
|
40017
40019
|
const PNPM_LOCKFILE = "pnpm-lock.yaml";
|
|
40018
40020
|
const PACKAGE_JSON_FILE = "package.json";
|
|
40019
40021
|
const PNPM_HARDENING_RULE_KEY = "require-pnpm-hardening";
|
|
40020
|
-
const UTF8_BOM_CHAR = "";
|
|
40021
40022
|
const HARDENING_SETTING_KEYS = new Set([
|
|
40022
40023
|
"minimumReleaseAge",
|
|
40023
40024
|
"blockExoticSubdeps",
|
|
@@ -40042,12 +40043,11 @@ const stripInlineComment = (rawValue) => {
|
|
|
40042
40043
|
return rawValue;
|
|
40043
40044
|
};
|
|
40044
40045
|
const unquote = (rawValue) => rawValue.replace(/^["']|["']$/g, "");
|
|
40045
|
-
const stripBom = (rawContent) => rawContent.startsWith(UTF8_BOM_CHAR) ? rawContent.slice(1) : rawContent;
|
|
40046
40046
|
const parseHardeningSettings = (content) => {
|
|
40047
40047
|
let minimumReleaseAge = null;
|
|
40048
40048
|
let blockExoticSubdeps = null;
|
|
40049
40049
|
let trustPolicy = null;
|
|
40050
|
-
const lines =
|
|
40050
|
+
const lines = stripUtf8Bom(content).split(/\r?\n/);
|
|
40051
40051
|
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
40052
40052
|
const lineText = lines[lineIndex];
|
|
40053
40053
|
if (lineText === void 0) continue;
|
|
@@ -40081,7 +40081,7 @@ const isPnpmManagedProject = (rootDirectory) => {
|
|
|
40081
40081
|
const packageJsonPath = Path.join(rootDirectory, PACKAGE_JSON_FILE);
|
|
40082
40082
|
if (!isFile(packageJsonPath)) return false;
|
|
40083
40083
|
try {
|
|
40084
|
-
const packageJsonRaw = NFS.readFileSync(packageJsonPath, "utf-8");
|
|
40084
|
+
const packageJsonRaw = stripUtf8Bom(NFS.readFileSync(packageJsonPath, "utf-8"));
|
|
40085
40085
|
const packageJson = JSON.parse(packageJsonRaw);
|
|
40086
40086
|
if (packageJson !== null && typeof packageJson === "object" && "packageManager" in packageJson && typeof packageJson.packageManager === "string" && packageJson.packageManager.startsWith("pnpm@")) return true;
|
|
40087
40087
|
} catch {
|
|
@@ -40762,9 +40762,7 @@ const setColorEnabled = (enabled) => {
|
|
|
40762
40762
|
highlighter.gray = colors.gray;
|
|
40763
40763
|
highlighter.bold = colors.bold;
|
|
40764
40764
|
};
|
|
40765
|
-
const
|
|
40766
|
-
const shouldIncludeExplicitLintPath = (filePath, project) => JSX_FILE_PATTERN.test(filePath) || project?.framework === "nextjs" && NEXT_ENTRY_FILE_PATTERN.test(filePath);
|
|
40767
|
-
const computeExplicitLintIncludePaths = (includePaths, project) => includePaths.length > 0 ? includePaths.filter((filePath) => shouldIncludeExplicitLintPath(filePath, project)) : void 0;
|
|
40765
|
+
const computeExplicitLintIncludePaths = (includePaths) => includePaths.length > 0 ? includePaths.filter(isLintableSourceFile) : void 0;
|
|
40768
40766
|
const LINGUIST_ATTRIBUTE_PATTERN = /^linguist-(?:vendored|generated)(?:=([a-zA-Z0-9]+))?$/i;
|
|
40769
40767
|
const FALSY_VALUES = new Set([
|
|
40770
40768
|
"false",
|
|
@@ -41603,11 +41601,11 @@ const resolveDeadCodeTimeout = (input) => {
|
|
|
41603
41601
|
phaseTimeoutMs: workerTimeoutMs + DEAD_CODE_PHASE_TIMEOUT_OVER_WORKER_MS
|
|
41604
41602
|
};
|
|
41605
41603
|
};
|
|
41606
|
-
const resolveLintIncludePaths = (rootDirectory, userConfig
|
|
41604
|
+
const resolveLintIncludePaths = (rootDirectory, userConfig) => {
|
|
41607
41605
|
if (!Array.isArray(userConfig?.ignore?.files) || userConfig.ignore.files.length === 0) return;
|
|
41608
41606
|
const ignoredPatterns = compileIgnoredFilePatterns(userConfig);
|
|
41609
41607
|
return listSourceFiles(rootDirectory).filter((filePath) => {
|
|
41610
|
-
if (!
|
|
41608
|
+
if (!isLintableSourceFile(filePath)) return false;
|
|
41611
41609
|
return !isFileIgnoredByPatterns(filePath, rootDirectory, ignoredPatterns);
|
|
41612
41610
|
});
|
|
41613
41611
|
};
|
|
@@ -41825,6 +41823,7 @@ const parseGithubViewerPermission = (stdout) => {
|
|
|
41825
41823
|
return /^[A-Z_]+$/.test(value) ? value.toLowerCase() : null;
|
|
41826
41824
|
};
|
|
41827
41825
|
const splitNullSeparated = (value) => value.split("\0").filter((entry) => entry.length > 0);
|
|
41826
|
+
const UNTRACKED_FILE_LAST_LINE = Number.MAX_SAFE_INTEGER;
|
|
41828
41827
|
/**
|
|
41829
41828
|
* `Git` wraps every `git`-via-subprocess call react-doctor makes
|
|
41830
41829
|
* behind a `Context.Service`. The production layer (`layerNode`)
|
|
@@ -41895,6 +41894,14 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
41895
41894
|
args,
|
|
41896
41895
|
directory
|
|
41897
41896
|
});
|
|
41897
|
+
const listUntrackedFilePaths = (directory, includePaths = []) => runGit(directory, [
|
|
41898
|
+
"ls-files",
|
|
41899
|
+
"-z",
|
|
41900
|
+
"--others",
|
|
41901
|
+
"--exclude-standard",
|
|
41902
|
+
...includePaths.length > 0 ? ["--", ...includePaths] : []
|
|
41903
|
+
]).pipe(map$3((result) => result.status === 0 ? splitNullSeparated(result.stdout) : null));
|
|
41904
|
+
const mergeUntracked = (directory, trackedFilePaths, includeUntracked) => includeUntracked ? listUntrackedFilePaths(directory).pipe(map$3((untracked) => untracked === null ? trackedFilePaths : [...new Set([...trackedFilePaths, ...untracked])])) : succeed$2(trackedFilePaths);
|
|
41898
41905
|
const currentBranch = (directory) => runGit(directory, [
|
|
41899
41906
|
"rev-parse",
|
|
41900
41907
|
"--abbrev-ref",
|
|
@@ -42017,7 +42024,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
42017
42024
|
githubViewerPermission,
|
|
42018
42025
|
branchExists,
|
|
42019
42026
|
mergeBase,
|
|
42020
|
-
diffSelection: ({ directory, explicitBaseBranch }) => gen(function* () {
|
|
42027
|
+
diffSelection: ({ directory, explicitBaseBranch, includeUntracked = false }) => gen(function* () {
|
|
42021
42028
|
if (explicitBaseBranch !== void 0 && explicitBaseBranch.trim().length === 0) return yield* fail$4(new ReactDoctorError({ reason: new GitBaseBranchInvalid({ detail: "Diff base branch cannot be empty." }) }));
|
|
42022
42029
|
if (explicitBaseBranch !== void 0) {
|
|
42023
42030
|
const range = parseGitDiffRange(explicitBaseBranch);
|
|
@@ -42046,7 +42053,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
42046
42053
|
"HEAD"
|
|
42047
42054
|
]);
|
|
42048
42055
|
if (uncommitted.status !== 0) return null;
|
|
42049
|
-
const files = splitNullSeparated(uncommitted.stdout);
|
|
42056
|
+
const files = yield* mergeUntracked(directory, splitNullSeparated(uncommitted.stdout), includeUntracked);
|
|
42050
42057
|
if (files.length === 0) return null;
|
|
42051
42058
|
return {
|
|
42052
42059
|
currentBranch: resolvedCurrentBranch,
|
|
@@ -42076,7 +42083,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
42076
42083
|
currentBranch: resolvedCurrentBranch,
|
|
42077
42084
|
baseBranch,
|
|
42078
42085
|
diffBaseRef: mergeBaseRef,
|
|
42079
|
-
changedFiles: splitNullSeparated(diff.stdout),
|
|
42086
|
+
changedFiles: yield* mergeUntracked(directory, splitNullSeparated(diff.stdout), includeUntracked),
|
|
42080
42087
|
isCurrentChanges: false
|
|
42081
42088
|
};
|
|
42082
42089
|
}).pipe(withSpan("Git.diffSelection")),
|
|
@@ -42122,7 +42129,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
42122
42129
|
stdout: result.stdout
|
|
42123
42130
|
};
|
|
42124
42131
|
}).pipe(withSpan("Git.grep")),
|
|
42125
|
-
changedLineRanges: ({ directory, baseRef, cached, files }) => gen(function* () {
|
|
42132
|
+
changedLineRanges: ({ directory, baseRef, cached, files, includeUntracked = false }) => gen(function* () {
|
|
42126
42133
|
if (files.length === 0) return [];
|
|
42127
42134
|
if (baseRef !== void 0 && !isSafeGitRevision(baseRef)) return null;
|
|
42128
42135
|
const result = yield* runGit(directory, [
|
|
@@ -42136,7 +42143,14 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
42136
42143
|
...files
|
|
42137
42144
|
]);
|
|
42138
42145
|
if (result.status !== 0) return null;
|
|
42139
|
-
|
|
42146
|
+
const changedLineRanges = parseChangedLineRanges(result.stdout);
|
|
42147
|
+
if (cached || !includeUntracked) return changedLineRanges;
|
|
42148
|
+
const untrackedFilePaths = yield* listUntrackedFilePaths(directory, files);
|
|
42149
|
+
if (untrackedFilePaths === null) return changedLineRanges;
|
|
42150
|
+
return [...changedLineRanges, ...untrackedFilePaths.map((file) => ({
|
|
42151
|
+
file,
|
|
42152
|
+
ranges: [[1, UNTRACKED_FILE_LAST_LINE]]
|
|
42153
|
+
}))];
|
|
42140
42154
|
}).pipe(catch_$1((error) => error.reason._tag === "GitInvocationFailed" ? succeed$2(null) : fail$4(error)), withSpan("Git.changedLineRanges"))
|
|
42141
42155
|
});
|
|
42142
42156
|
})).pipe(provide$2(layer$3.pipe(provide$2(mergeAll$1(layer$2, layer$1)))));
|
|
@@ -42888,6 +42902,12 @@ const mapWithConcurrency = async (items, concurrency, task) => {
|
|
|
42888
42902
|
if (errors.length > 0) throw errors[0];
|
|
42889
42903
|
return results;
|
|
42890
42904
|
};
|
|
42905
|
+
const lineOfUtf8Offset = (sourceBuffer, utf8Offset) => {
|
|
42906
|
+
let lineNumber = 1;
|
|
42907
|
+
const scanEnd = Math.min(utf8Offset, sourceBuffer.length);
|
|
42908
|
+
for (let byteIndex = 0; byteIndex < scanEnd; byteIndex++) if (sourceBuffer[byteIndex] === 10) lineNumber++;
|
|
42909
|
+
return lineNumber;
|
|
42910
|
+
};
|
|
42891
42911
|
const REANIMATED_SHARED_VALUE_HINT = "If this is a Reanimated shared value, prefer its React Compiler-compatible `.get()` / `.set()` accessors over `.value` — https://docs.swmansion.com/react-native-reanimated/docs/core/useSharedValue/#react-compiler-support";
|
|
42892
42912
|
const ACCESSORS_MIN_VERSION = "3.15.0";
|
|
42893
42913
|
const supportsAccessors = (reanimatedVersion) => {
|
|
@@ -43459,13 +43479,6 @@ const findOutermostEnclosingFunctionRange = (sourceFile, targetLine) => {
|
|
|
43459
43479
|
visit(sourceFile);
|
|
43460
43480
|
return foundRange;
|
|
43461
43481
|
};
|
|
43462
|
-
const NEWLINE_BYTE = 10;
|
|
43463
|
-
const lineOfUtf8Offset = (sourceBuffer, utf8Offset) => {
|
|
43464
|
-
let lineNumber = 1;
|
|
43465
|
-
const scanEnd = Math.min(utf8Offset, sourceBuffer.length);
|
|
43466
|
-
for (let byteIndex = 0; byteIndex < scanEnd; byteIndex++) if (sourceBuffer[byteIndex] === NEWLINE_BYTE) lineNumber++;
|
|
43467
|
-
return lineNumber;
|
|
43468
|
-
};
|
|
43469
43482
|
/**
|
|
43470
43483
|
* Drops `react-doctor/react-compiler-no-manual-memoization` diagnostics
|
|
43471
43484
|
* whose enclosing function also carries a `react-hooks-js` (React
|
|
@@ -43646,11 +43659,11 @@ const buildRelatedLocations = (labels, filePath) => {
|
|
|
43646
43659
|
}
|
|
43647
43660
|
return related;
|
|
43648
43661
|
};
|
|
43649
|
-
const
|
|
43650
|
-
|
|
43651
|
-
|
|
43652
|
-
|
|
43653
|
-
|
|
43662
|
+
const isOxlintSpan = (value) => isRecord$1(value) && typeof value.offset === "number" && typeof value.length === "number" && typeof value.line === "number" && typeof value.column === "number";
|
|
43663
|
+
const isOxlintLabel = (value) => isRecord$1(value) && isOxlintSpan(value.span);
|
|
43664
|
+
const isMappableOxlintDiagnostic = (value) => isRecord$1(value) && typeof value.code === "string" && value.code.length > 0 && typeof value.filename === "string" && value.filename.length > 0 && (value.severity === "warning" || value.severity === "error") && Array.isArray(value.labels) && value.labels.every(isOxlintLabel);
|
|
43665
|
+
const isEngineFailureDiagnostic = (value) => !isRecord$1(value) || typeof value.filename !== "string" || value.filename.length === 0;
|
|
43666
|
+
const isOxlintOutput = (value) => isRecord$1(value) && Array.isArray(value.diagnostics);
|
|
43654
43667
|
/**
|
|
43655
43668
|
* Parses one oxlint subprocess's stdout into a flat `Diagnostic[]`.
|
|
43656
43669
|
* Tolerates the leading-notice-line shape oxlint sometimes prints
|
|
@@ -43669,23 +43682,59 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
43669
43682
|
throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
|
|
43670
43683
|
}
|
|
43671
43684
|
if (!isOxlintOutput(parsed)) throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: stdout.slice(0, 600) }) });
|
|
43685
|
+
const engineFailureDiagnostic = parsed.diagnostics.find(isEngineFailureDiagnostic);
|
|
43686
|
+
if (engineFailureDiagnostic !== void 0) throw new ReactDoctorError({ reason: new OxlintOutputUnparseable({ preview: JSON.stringify(engineFailureDiagnostic).slice(0, 600) }) });
|
|
43672
43687
|
const minifiedFileCache = /* @__PURE__ */ new Map();
|
|
43688
|
+
const sourceBufferCache = /* @__PURE__ */ new Map();
|
|
43689
|
+
const sourceLinesCache = /* @__PURE__ */ new Map();
|
|
43690
|
+
const resolveAbsolutePath = (filename) => Path.isAbsolute(filename) ? filename : Path.resolve(rootDirectory || ".", filename);
|
|
43691
|
+
const readSourceBuffer = (filename) => {
|
|
43692
|
+
const absolutePath = resolveAbsolutePath(filename);
|
|
43693
|
+
const cached = sourceBufferCache.get(absolutePath);
|
|
43694
|
+
if (cached !== void 0) return cached;
|
|
43695
|
+
let sourceBuffer;
|
|
43696
|
+
try {
|
|
43697
|
+
sourceBuffer = NFS.readFileSync(absolutePath);
|
|
43698
|
+
} catch {
|
|
43699
|
+
sourceBuffer = null;
|
|
43700
|
+
}
|
|
43701
|
+
sourceBufferCache.set(absolutePath, sourceBuffer);
|
|
43702
|
+
return sourceBuffer;
|
|
43703
|
+
};
|
|
43704
|
+
const readSourceLines = (filename) => {
|
|
43705
|
+
const absolutePath = resolveAbsolutePath(filename);
|
|
43706
|
+
const cached = sourceLinesCache.get(absolutePath);
|
|
43707
|
+
if (cached !== void 0) return cached;
|
|
43708
|
+
const sourceBuffer = readSourceBuffer(filename);
|
|
43709
|
+
const sourceLines = sourceBuffer ? sourceBuffer.toString("utf8").split("\n") : null;
|
|
43710
|
+
sourceLinesCache.set(absolutePath, sourceLines);
|
|
43711
|
+
return sourceLines;
|
|
43712
|
+
};
|
|
43673
43713
|
const isMinifiedDiagnosticFile = (filename) => {
|
|
43674
|
-
const absolutePath =
|
|
43714
|
+
const absolutePath = resolveAbsolutePath(filename);
|
|
43675
43715
|
const cached = minifiedFileCache.get(absolutePath);
|
|
43676
43716
|
if (cached !== void 0) return cached;
|
|
43677
43717
|
const minified = isMinifiedSource(absolutePath);
|
|
43678
43718
|
minifiedFileCache.set(absolutePath, minified);
|
|
43679
43719
|
return minified;
|
|
43680
43720
|
};
|
|
43681
|
-
return suppressMemoizationInBailedOutFunctions(parsed.diagnostics.filter((diagnostic) => diagnostic
|
|
43721
|
+
return suppressMemoizationInBailedOutFunctions(parsed.diagnostics.filter((diagnostic) => isMappableOxlintDiagnostic(diagnostic) && isLintableSourceFile(diagnostic.filename) && !isMinifiedDiagnosticFile(diagnostic.filename) && !shouldSuppressLocalUseHookDiagnostic(diagnostic, rootDirectory) && !shouldSuppressCompilerFindingInWorklet(diagnostic, project, rootDirectory)).map((diagnostic) => {
|
|
43682
43722
|
const { plugin, rule } = parseRuleCode(diagnostic.code);
|
|
43683
43723
|
const primaryLabel = diagnostic.labels[0];
|
|
43684
43724
|
const cleaned = cleanDiagnosticMessage(diagnostic.message, diagnostic.help, plugin, rule, project);
|
|
43685
43725
|
const normalizedFilePath = diagnostic.filename.replaceAll("\\", "/");
|
|
43686
43726
|
const primarySpan = primaryLabel?.span;
|
|
43727
|
+
const sourceBuffer = primarySpan ? readSourceBuffer(diagnostic.filename) : null;
|
|
43687
43728
|
const relatedLocations = buildRelatedLocations(diagnostic.labels, normalizedFilePath);
|
|
43688
43729
|
const category = resolveDiagnosticCategory(plugin, rule);
|
|
43730
|
+
const matchByOccurrence = resolveMatchByOccurrence(rule, category);
|
|
43731
|
+
const sourceLines = primarySpan ? readSourceLines(diagnostic.filename) : null;
|
|
43732
|
+
const primaryLineIndex = primarySpan ? primarySpan.line - 1 : -1;
|
|
43733
|
+
const primaryLine = sourceLines?.[primaryLineIndex];
|
|
43734
|
+
const primaryColumnIndex = primarySpan ? primarySpan.column - 1 : -1;
|
|
43735
|
+
const isJsxTagLabel = primaryLine !== void 0 && primaryColumnIndex >= 0 && (primaryLine[primaryColumnIndex] === "<" || primaryLine[primaryColumnIndex - 1] === "<");
|
|
43736
|
+
const jsxOpenerEndLineIndex = sourceLines && isJsxTagLabel ? findJsxOpenerSpan(sourceLines, primaryLineIndex) : null;
|
|
43737
|
+
const primarySpanEndLine = jsxOpenerEndLineIndex !== null ? jsxOpenerEndLineIndex + 1 : primarySpan && sourceBuffer ? lineOfUtf8Offset(sourceBuffer, primarySpan.offset + Math.max(primarySpan.length - 1, 0)) : void 0;
|
|
43689
43738
|
return {
|
|
43690
43739
|
filePath: normalizedFilePath,
|
|
43691
43740
|
plugin,
|
|
@@ -43699,10 +43748,11 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
43699
43748
|
column: primarySpan?.column ?? 0,
|
|
43700
43749
|
...primarySpan ? {
|
|
43701
43750
|
offset: primarySpan.offset,
|
|
43702
|
-
length: primarySpan.length
|
|
43751
|
+
length: primarySpan.length,
|
|
43752
|
+
...primarySpanEndLine !== void 0 ? { endLine: primarySpanEndLine } : {}
|
|
43703
43753
|
} : {},
|
|
43704
43754
|
category,
|
|
43705
|
-
...
|
|
43755
|
+
...matchByOccurrence ? { matchByOccurrence: true } : {},
|
|
43706
43756
|
...relatedLocations.length > 0 ? { relatedLocations } : {}
|
|
43707
43757
|
};
|
|
43708
43758
|
}), rootDirectory);
|
|
@@ -45489,7 +45539,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
45489
45539
|
directory: scanDirectory,
|
|
45490
45540
|
repo
|
|
45491
45541
|
}).pipe(orElseSucceed(() => null)) : succeed$2(null));
|
|
45492
|
-
const lintIncludePaths = (input.
|
|
45542
|
+
const lintIncludePaths = (input.skipExplicitIncludePathFilter ? input.includePaths.length > 0 ? [...input.includePaths] : void 0 : computeExplicitLintIncludePaths([...input.includePaths])) ?? resolveLintIncludePaths(scanDirectory, resolvedConfig.config);
|
|
45493
45543
|
const fallbackScannedFilePaths = input.suppressScanSummary ? (lintIncludePaths ?? (yield* filesService.listSourceFiles(scanDirectory))).map((relativePath) => Path.resolve(scanDirectory, relativePath)) : [];
|
|
45494
45544
|
const beforeLint = hooks.beforeLint ?? NO_HOOKS.beforeLint;
|
|
45495
45545
|
const afterLint = hooks.afterLint ?? NO_HOOKS.afterLint;
|
|
@@ -46234,10 +46284,11 @@ const computeDiagnosticDelta = (input) => {
|
|
|
46234
46284
|
* render diff-base mistakes (`GitBaseBranchInvalid` /
|
|
46235
46285
|
* `GitBaseBranchMissing`) as clean user errors instead of crashes.
|
|
46236
46286
|
*/
|
|
46237
|
-
const getDiffInfo = (directory, explicitBaseBranch) => runPromise(gen(function* () {
|
|
46287
|
+
const getDiffInfo = (directory, explicitBaseBranch, includeUntracked) => runPromise(gen(function* () {
|
|
46238
46288
|
const selection = yield* (yield* Git).diffSelection({
|
|
46239
46289
|
directory,
|
|
46240
|
-
explicitBaseBranch
|
|
46290
|
+
explicitBaseBranch,
|
|
46291
|
+
includeUntracked
|
|
46241
46292
|
});
|
|
46242
46293
|
if (selection === null) return null;
|
|
46243
46294
|
return {
|
|
@@ -46573,6 +46624,43 @@ const isNonInteractiveEnvironment = () => NON_INTERACTIVE_ENVIRONMENT_VARIABLES.
|
|
|
46573
46624
|
//#region src/cli/utils/constants.ts
|
|
46574
46625
|
const REACT_DOCTOR_CONFIG_PROJECT_NAME = "react-doctor";
|
|
46575
46626
|
const STAGED_FILES_TEMP_DIR_PREFIX = "react-doctor-staged-";
|
|
46627
|
+
const STAGED_SNAPSHOT_ADDITIONAL_CONFIG_FILENAMES = [
|
|
46628
|
+
".babelrc",
|
|
46629
|
+
".babelrc.json",
|
|
46630
|
+
".eslintignore",
|
|
46631
|
+
".eslintrc.json",
|
|
46632
|
+
".oxlintignore",
|
|
46633
|
+
"app.config.cjs",
|
|
46634
|
+
"app.config.js",
|
|
46635
|
+
"app.config.json",
|
|
46636
|
+
"app.config.mjs",
|
|
46637
|
+
"app.config.ts",
|
|
46638
|
+
"app.json",
|
|
46639
|
+
"babel.config.cjs",
|
|
46640
|
+
"babel.config.js",
|
|
46641
|
+
"babel.config.json",
|
|
46642
|
+
"babel.config.mjs",
|
|
46643
|
+
"eslint.config.cjs",
|
|
46644
|
+
"eslint.config.cts",
|
|
46645
|
+
"eslint.config.js",
|
|
46646
|
+
"eslint.config.mjs",
|
|
46647
|
+
"eslint.config.mts",
|
|
46648
|
+
"eslint.config.ts",
|
|
46649
|
+
"next.config.cjs",
|
|
46650
|
+
"next.config.js",
|
|
46651
|
+
"next.config.mjs",
|
|
46652
|
+
"next.config.ts",
|
|
46653
|
+
"oxlint.config.json",
|
|
46654
|
+
"react-doctor.config.json",
|
|
46655
|
+
"vite.config.cjs",
|
|
46656
|
+
"vite.config.cts",
|
|
46657
|
+
"vite.config.js",
|
|
46658
|
+
"vite.config.mjs",
|
|
46659
|
+
"vite.config.mts",
|
|
46660
|
+
"vite.config.ts",
|
|
46661
|
+
"vitest.config.js",
|
|
46662
|
+
"vitest.config.ts"
|
|
46663
|
+
];
|
|
46576
46664
|
const BASELINE_FILES_TEMP_DIR_PREFIX = "react-doctor-baseline-";
|
|
46577
46665
|
const SCAN_RESULT_CACHE_FILENAME = "scan-cache.json";
|
|
46578
46666
|
const RUN_GIT_MAX_BUFFER_BYTES = 64 * 1024 * 1024;
|
|
@@ -46587,6 +46675,7 @@ const METRIC = {
|
|
|
46587
46675
|
cliInvoked: "cli.invoked",
|
|
46588
46676
|
cliError: "cli.error",
|
|
46589
46677
|
cliEnvironmentError: "cli.env_error",
|
|
46678
|
+
stagedSnapshotDivergence: "staged.snapshot_divergence",
|
|
46590
46679
|
projectDetected: "project.detected",
|
|
46591
46680
|
projectPathSelected: "project.path_selected",
|
|
46592
46681
|
projectConfigSelected: "project.config_selected",
|
|
@@ -46667,7 +46756,7 @@ const makeNoopConsole = () => ({
|
|
|
46667
46756
|
});
|
|
46668
46757
|
//#endregion
|
|
46669
46758
|
//#region src/cli/utils/version.ts
|
|
46670
|
-
const VERSION = "0.7.6-dev.
|
|
46759
|
+
const VERSION = "0.7.6-dev.88ef7fd";
|
|
46671
46760
|
//#endregion
|
|
46672
46761
|
//#region src/cli/utils/json-mode.ts
|
|
46673
46762
|
let context = null;
|
|
@@ -47036,13 +47125,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
|
|
|
47036
47125
|
* uploads source-map artifacts under, so stack frames symbolicate. Honors the
|
|
47037
47126
|
* standard `SENTRY_RELEASE` override.
|
|
47038
47127
|
*/
|
|
47039
|
-
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.7.6-dev.
|
|
47128
|
+
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.7.6-dev.88ef7fd`;
|
|
47040
47129
|
/**
|
|
47041
47130
|
* Deployment environment shown in Sentry's environment filter. Defaults to
|
|
47042
47131
|
* `production` for tagged releases and `development` for dev/unbuilt versions,
|
|
47043
47132
|
* overridable via the standard `SENTRY_ENVIRONMENT` env var.
|
|
47044
47133
|
*/
|
|
47045
|
-
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.7.6-dev.
|
|
47134
|
+
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.7.6-dev.88ef7fd") ? "development" : "production");
|
|
47046
47135
|
/**
|
|
47047
47136
|
* Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
|
|
47048
47137
|
* (set to `0` to disable tracing) and falls back to
|
|
@@ -47234,7 +47323,7 @@ const NON_BLOCKING_REACT_DOCTOR_COMMAND = [
|
|
|
47234
47323
|
"fi"
|
|
47235
47324
|
].join(" ");
|
|
47236
47325
|
const PACKAGE_JSON_FILE_NAME = "package.json";
|
|
47237
|
-
const
|
|
47326
|
+
const runGitRaw = (projectRoot, args) => {
|
|
47238
47327
|
try {
|
|
47239
47328
|
return execFileSync("git", [...args], {
|
|
47240
47329
|
cwd: projectRoot,
|
|
@@ -47245,11 +47334,12 @@ const runGit = (projectRoot, args) => {
|
|
|
47245
47334
|
"ignore"
|
|
47246
47335
|
],
|
|
47247
47336
|
maxBuffer: RUN_GIT_MAX_BUFFER_BYTES
|
|
47248
|
-
})
|
|
47337
|
+
});
|
|
47249
47338
|
} catch {
|
|
47250
47339
|
return null;
|
|
47251
47340
|
}
|
|
47252
47341
|
};
|
|
47342
|
+
const runGit = (projectRoot, args) => runGitRaw(projectRoot, args)?.trim() ?? null;
|
|
47253
47343
|
const resolveGitPath = (baseDirectory, value) => Path.isAbsolute(value) ? value : Path.resolve(baseDirectory, value);
|
|
47254
47344
|
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
47255
47345
|
const getPackageJsonPath = (projectRoot) => Path.join(projectRoot, PACKAGE_JSON_FILE_NAME);
|
|
@@ -50807,7 +50897,7 @@ const BLOCKING_SUMMARY = {
|
|
|
50807
50897
|
const SCOPE_SUMMARY = {
|
|
50808
50898
|
full: "Scan the whole project on every run",
|
|
50809
50899
|
files: "Report every issue in changed files",
|
|
50810
|
-
lines: "Report issues
|
|
50900
|
+
lines: "Report issues whose source spans touch changed lines",
|
|
50811
50901
|
changed: "Report only the issues a change introduces"
|
|
50812
50902
|
};
|
|
50813
50903
|
const gatesEqual = (left, right) => left.blocking === right.blocking && left.scope === right.scope && left.comment === right.comment && left.reviewComments === right.reviewComments && left.commitStatus === right.commitStatus;
|
|
@@ -59120,6 +59210,17 @@ const buildRuleBlastRadii = (diagnostics) => buildSortedRuleGroups(diagnostics).
|
|
|
59120
59210
|
})).toSorted((left, right) => right.fileCount - left.fileCount);
|
|
59121
59211
|
const findMigrationScaleBuckets = (diagnostics) => buildRuleBlastRadii(diagnostics).filter((bucket) => bucket.fileCount >= 40);
|
|
59122
59212
|
//#endregion
|
|
59213
|
+
//#region src/cli/utils/has-lint-hard-failure.ts
|
|
59214
|
+
const hasLintHardFailure = (result) => result.skippedChecks.includes("lint");
|
|
59215
|
+
//#endregion
|
|
59216
|
+
//#region src/cli/utils/is-inspect-result-complete.ts
|
|
59217
|
+
const isInspectResultComplete = (result) => isScanComplete({
|
|
59218
|
+
analyzedFileCount: result.analyzedFiles?.length,
|
|
59219
|
+
scannedFileCount: result.scannedFileCount,
|
|
59220
|
+
skippedCheckCount: result.skippedChecks.length,
|
|
59221
|
+
skippedCheckReasonCount: Object.keys(result.skippedCheckReasons ?? {}).length
|
|
59222
|
+
});
|
|
59223
|
+
//#endregion
|
|
59123
59224
|
//#region src/cli/utils/scan-result-cache.ts
|
|
59124
59225
|
const CACHE_DISABLED_VALUES = new Set(["1", "true"]);
|
|
59125
59226
|
const TOOLCHAIN_PACKAGE_SPECIFIERS = [
|
|
@@ -59525,15 +59626,12 @@ const buildOutcomeAttributes = (input) => {
|
|
|
59525
59626
|
const summary = summarizeDiagnostics(result.diagnostics);
|
|
59526
59627
|
const blockingLevel = resolveTelemetryBlocking(input.userConfig);
|
|
59527
59628
|
const gateDiagnostics = filterDiagnosticsForSurface(result.diagnostics, "ciFailure", input.userConfig);
|
|
59629
|
+
const complete = isInspectResultComplete(result);
|
|
59630
|
+
const didLintHardFail = hasLintHardFailure(result);
|
|
59631
|
+
const failsOnHardFailure = didLintHardFail && blockingLevel !== "none";
|
|
59528
59632
|
const wouldBlock = !input.scoreOnly && !input.gateExempt && shouldBlockCi(gateDiagnostics, blockingLevel);
|
|
59529
|
-
const complete = isScanComplete({
|
|
59530
|
-
analyzedFileCount: result.analyzedFiles?.length,
|
|
59531
|
-
scannedFileCount: result.scannedFileCount,
|
|
59532
|
-
skippedCheckCount: result.skippedChecks.length,
|
|
59533
|
-
skippedCheckReasonCount: Object.keys(result.skippedCheckReasons ?? {}).length
|
|
59534
|
-
});
|
|
59535
59633
|
const isClean = result.diagnostics.length === 0 && complete;
|
|
59536
|
-
const outcome = wouldBlock ? "blocked" : isClean ? "clean" : "ok";
|
|
59634
|
+
const outcome = didLintHardFail ? "error" : wouldBlock ? "blocked" : isClean ? "clean" : "ok";
|
|
59537
59635
|
const firings = summarizeRuleFirings(result.diagnostics);
|
|
59538
59636
|
const countByRule = /* @__PURE__ */ new Map();
|
|
59539
59637
|
const countByCategory = /* @__PURE__ */ new Map();
|
|
@@ -59578,7 +59676,7 @@ const buildOutcomeAttributes = (input) => {
|
|
|
59578
59676
|
const attributes = {
|
|
59579
59677
|
...withNamespace("outcome", {
|
|
59580
59678
|
status: outcome,
|
|
59581
|
-
exitCode: wouldBlock ? 1 : 0,
|
|
59679
|
+
exitCode: failsOnHardFailure || wouldBlock ? 1 : 0,
|
|
59582
59680
|
wouldBlock,
|
|
59583
59681
|
blocking: blockingLevel,
|
|
59584
59682
|
clean: isClean,
|
|
@@ -59676,7 +59774,9 @@ const buildScanAttributes = (input) => {
|
|
|
59676
59774
|
hasCustomConfig: input.hasCustomConfig,
|
|
59677
59775
|
rulesConfigured: ruleKeys.length,
|
|
59678
59776
|
rulesDisabled: ruleKeys.filter((key) => ruleOverrides[key] === "off").length,
|
|
59679
|
-
fileCount: input.result?.scannedFileCount ?? null
|
|
59777
|
+
fileCount: input.result?.scannedFileCount ?? null,
|
|
59778
|
+
nonJsxFileCount: input.result?.analyzedFiles?.filter((filePath) => !JSX_FILE_PATTERN.test(filePath)).length ?? null,
|
|
59779
|
+
multilineDiagnosticCount: input.result?.diagnostics.filter((diagnostic) => (diagnostic.endLine ?? diagnostic.line) > diagnostic.line).length ?? null
|
|
59680
59780
|
});
|
|
59681
59781
|
};
|
|
59682
59782
|
/**
|
|
@@ -59745,6 +59845,12 @@ const countDeadlineSkippedFiles = (lintPartialFailures) => sumFileCountsMatching
|
|
|
59745
59845
|
const DROPPED_FILES_MESSAGE_PATTERN = /^(\d+) file\(s\) failed to lint and were skipped/;
|
|
59746
59846
|
const countDroppedLintFiles = (lintPartialFailures) => sumFileCountsMatching(lintPartialFailures, DROPPED_FILES_MESSAGE_PATTERN);
|
|
59747
59847
|
//#endregion
|
|
59848
|
+
//#region src/cli/utils/diagnostic-intersects-line-ranges.ts
|
|
59849
|
+
const diagnosticIntersectsLineRanges = (diagnostic, lineRanges) => {
|
|
59850
|
+
const diagnosticEndLine = Math.max(diagnostic.line, diagnostic.endLine ?? diagnostic.line);
|
|
59851
|
+
return lineRanges.some(([rangeStartLine, rangeEndLine]) => diagnostic.line <= rangeEndLine && diagnosticEndLine >= rangeStartLine);
|
|
59852
|
+
};
|
|
59853
|
+
//#endregion
|
|
59748
59854
|
//#region src/cli/utils/materialize-baseline-files.ts
|
|
59749
59855
|
/**
|
|
59750
59856
|
* Materializes the `ref` version of `files` into `tempDirectory` (via
|
|
@@ -60930,7 +61036,7 @@ const buildChangedLineMatcher = (directory, changedLineRanges) => {
|
|
|
60930
61036
|
const relativePath = toForwardSlashes(path.isAbsolute(diagnostic.filePath) ? path.relative(directory, diagnostic.filePath) : diagnostic.filePath);
|
|
60931
61037
|
const ranges = rangesByFile.get(relativePath);
|
|
60932
61038
|
if (ranges === void 0) return false;
|
|
60933
|
-
return
|
|
61039
|
+
return diagnosticIntersectsLineRanges(diagnostic, ranges);
|
|
60934
61040
|
};
|
|
60935
61041
|
};
|
|
60936
61042
|
const buildIgnoredTags = (userConfig) => {
|
|
@@ -63752,10 +63858,15 @@ const warnIfAiTrainingEnvironment = () => {
|
|
|
63752
63858
|
//#region src/cli/utils/validate-mode-flags.ts
|
|
63753
63859
|
const usedDiffAlias = (flags) => flags.diff !== void 0 && flags.diff !== false && flags.diff !== "false" && flags.diff !== "";
|
|
63754
63860
|
const usedScope = (flags) => typeof flags.scope === "string" && flags.scope.length > 0;
|
|
63861
|
+
const validateIncludeUntrackedScope = (includeUntracked, scope) => {
|
|
63862
|
+
if (!includeUntracked || scope !== void 0 && scope !== "full") return;
|
|
63863
|
+
throw new CliInputError("--include-untracked requires the files, changed, or lines scope (via --scope, --diff, or config).");
|
|
63864
|
+
};
|
|
63755
63865
|
const validateModeFlags = (flags) => {
|
|
63756
63866
|
if (usedScope(flags) && usedDiffAlias(flags)) throw new CliInputError("Cannot combine --scope and --diff; --diff is the deprecated alias.");
|
|
63757
63867
|
if (flags.staged && usedDiffAlias(flags)) throw new CliInputError("Cannot combine --staged and --diff; pick one mode.");
|
|
63758
63868
|
if (flags.staged && (flags.scope === "full" || flags.scope === "changed")) throw new CliInputError(`Cannot combine --staged with --scope ${flags.scope}; use --scope files or --scope lines, or drop --scope.`);
|
|
63869
|
+
if (flags.includeUntracked && flags.staged) throw new CliInputError("Cannot combine --include-untracked with --staged; the git index never holds untracked files.");
|
|
63759
63870
|
if (flags.score && flags.json) throw new CliInputError("Cannot combine --score and --json; pick one output mode.");
|
|
63760
63871
|
if (flags.score && flags.telemetry === false) throw new CliInputError("Cannot combine --score with --no-telemetry; --score prints the score that --no-telemetry disables.");
|
|
63761
63872
|
if (flags.debug && (flags.score === false || flags.telemetry === false)) {
|
|
@@ -63764,6 +63875,39 @@ const validateModeFlags = (flags) => {
|
|
|
63764
63875
|
}
|
|
63765
63876
|
};
|
|
63766
63877
|
//#endregion
|
|
63878
|
+
//#region src/cli/utils/parse-staged-snapshot-divergences.ts
|
|
63879
|
+
const SNAPSHOT_CONFIG_FILENAMES = new Set([...STAGED_FILES_PROJECT_CONFIG_FILENAMES, ...STAGED_SNAPSHOT_ADDITIONAL_CONFIG_FILENAMES]);
|
|
63880
|
+
const parseStagedSnapshotDivergences = (statusOutput) => {
|
|
63881
|
+
const divergentConfigFiles = /* @__PURE__ */ new Set();
|
|
63882
|
+
const statusEntries = statusOutput.split("\0").filter((entry) => entry.length > 0);
|
|
63883
|
+
for (let entryIndex = 0; entryIndex < statusEntries.length; entryIndex += 1) {
|
|
63884
|
+
const entry = statusEntries[entryIndex];
|
|
63885
|
+
const [indexStatus, worktreeStatus] = entry;
|
|
63886
|
+
const filePath = entry.slice(3);
|
|
63887
|
+
const hasRenameOrCopySource = indexStatus === "R" || indexStatus === "C" || worktreeStatus === "R" || worktreeStatus === "C";
|
|
63888
|
+
const sourcePath = hasRenameOrCopySource ? statusEntries[entryIndex + 1] : void 0;
|
|
63889
|
+
const worktreePaths = worktreeStatus === "R" || worktreeStatus === "C" ? [filePath, sourcePath] : [filePath];
|
|
63890
|
+
if (worktreeStatus !== " ") {
|
|
63891
|
+
for (const worktreePath of worktreePaths) if (worktreePath && SNAPSHOT_CONFIG_FILENAMES.has(Path.basename(worktreePath))) divergentConfigFiles.add(worktreePath);
|
|
63892
|
+
}
|
|
63893
|
+
if (hasRenameOrCopySource) entryIndex += 1;
|
|
63894
|
+
}
|
|
63895
|
+
return [...divergentConfigFiles].sort();
|
|
63896
|
+
};
|
|
63897
|
+
//#endregion
|
|
63898
|
+
//#region src/cli/utils/find-staged-snapshot-divergences.ts
|
|
63899
|
+
const findStagedSnapshotDivergences = (directory) => {
|
|
63900
|
+
const statusOutput = runGitRaw(directory, [
|
|
63901
|
+
"status",
|
|
63902
|
+
"--porcelain=v1",
|
|
63903
|
+
"-z",
|
|
63904
|
+
"--untracked-files=all",
|
|
63905
|
+
"--ignored=matching"
|
|
63906
|
+
]);
|
|
63907
|
+
if (statusOutput === null) return null;
|
|
63908
|
+
return parseStagedSnapshotDivergences(statusOutput);
|
|
63909
|
+
};
|
|
63910
|
+
//#endregion
|
|
63767
63911
|
//#region src/cli/commands/inspect.ts
|
|
63768
63912
|
const filterCompletedScansByCategories = (completedScans, categoryFilters) => {
|
|
63769
63913
|
if (categoryFilters.size === 0) return [...completedScans];
|
|
@@ -63778,9 +63922,14 @@ const filterCompletedScansByCategories = (completedScans, categoryFilters) => {
|
|
|
63778
63922
|
/**
|
|
63779
63923
|
* Post-scan finalization shared by the staged-arm and project-loop
|
|
63780
63924
|
* paths of `inspectAction`: emit the JSON report (when in JSON mode)
|
|
63781
|
-
* and set `process.exitCode = 1` when
|
|
63782
|
-
*
|
|
63783
|
-
*
|
|
63925
|
+
* and set `process.exitCode = 1` when any scan's lint pass hard-failed
|
|
63926
|
+
* (an engine/plugin/binding failure destroys the findings, so success
|
|
63927
|
+
* would be a false clean) or a diagnostic at or above the `--blocking`
|
|
63928
|
+
* threshold (default `"error"`) reaches the `ciFailure` surface.
|
|
63929
|
+
* `--blocking none` keeps the scan advisory (always exits 0), and
|
|
63930
|
+
* fail-open degradations — `--no-lint`, `--max-duration` truncation,
|
|
63931
|
+
* supply-chain/security skips — stay advisory too, surfaced through
|
|
63932
|
+
* `complete: false` in the JSON report.
|
|
63784
63933
|
*/
|
|
63785
63934
|
const finalizeScans = (input) => {
|
|
63786
63935
|
const baselineDeltas = input.completedScans.flatMap((scan) => scan.result.baselineDelta ? [scan.result.baselineDelta] : []);
|
|
@@ -63810,8 +63959,13 @@ const finalizeScans = (input) => {
|
|
|
63810
63959
|
baselineDegraded
|
|
63811
63960
|
}));
|
|
63812
63961
|
}
|
|
63962
|
+
const blockingLevel = resolveBlockingLevel(input.flags, input.userConfig);
|
|
63963
|
+
if (input.completedScans.some(({ result }) => hasLintHardFailure(result)) && blockingLevel !== "none") {
|
|
63964
|
+
process.exitCode = 1;
|
|
63965
|
+
return;
|
|
63966
|
+
}
|
|
63813
63967
|
if (input.isScoreOnly || baselineDegraded) return;
|
|
63814
|
-
if (shouldBlockCi(filterScansForSurface(input.completedScans, "ciFailure"),
|
|
63968
|
+
if (shouldBlockCi(filterScansForSurface(input.completedScans, "ciFailure"), blockingLevel)) process.exitCode = 1;
|
|
63815
63969
|
};
|
|
63816
63970
|
const buildChangedFilesDiffInfo = (changedFiles) => ({
|
|
63817
63971
|
currentBranch: process.env.GITHUB_HEAD_REF?.trim() || null,
|
|
@@ -63849,6 +64003,7 @@ const inspectAction = async (directory, flags) => {
|
|
|
63849
64003
|
recordCount(METRIC.cliInvoked, 1, { command: "inspect" });
|
|
63850
64004
|
try {
|
|
63851
64005
|
validateModeFlags(flags);
|
|
64006
|
+
if (flags.staged) setJsonReportMode("staged");
|
|
63852
64007
|
await maybeMigrateLegacyConfig(requestedDirectory, {
|
|
63853
64008
|
isQuiet,
|
|
63854
64009
|
isStaged: Boolean(flags.staged)
|
|
@@ -63864,6 +64019,14 @@ const inspectAction = async (directory, flags) => {
|
|
|
63864
64019
|
cliLogger.dim(`Redirected to ${highlighter.info(toRelativePath(resolvedDirectory, requestedDirectory))} via react-doctor config "rootDir".`);
|
|
63865
64020
|
cliLogger.break();
|
|
63866
64021
|
}
|
|
64022
|
+
if (flags.staged) {
|
|
64023
|
+
const divergentConfigFiles = findStagedSnapshotDivergences(resolvedDirectory);
|
|
64024
|
+
if (divergentConfigFiles === null) throw new CliInputError("Could not verify that staged configuration matches the worktree. Run the command from a Git worktree with Git available.");
|
|
64025
|
+
if (divergentConfigFiles.length > 0) {
|
|
64026
|
+
recordCount(METRIC.stagedSnapshotDivergence, 1, { divergentInputCount: divergentConfigFiles.length });
|
|
64027
|
+
throw new CliInputError(`Cannot scan staged files while configuration differs between the index and worktree: ${divergentConfigFiles.join(", ")}. Stage or restore those files, then rerun react-doctor --staged.`);
|
|
64028
|
+
}
|
|
64029
|
+
}
|
|
63867
64030
|
const explainArgument = flags.explain;
|
|
63868
64031
|
if (explainArgument !== void 0) {
|
|
63869
64032
|
await runExplain(explainArgument, {
|
|
@@ -63884,7 +64047,6 @@ const inspectAction = async (directory, flags) => {
|
|
|
63884
64047
|
json: flags.json
|
|
63885
64048
|
});
|
|
63886
64049
|
if (flags.staged) {
|
|
63887
|
-
setJsonReportMode("staged");
|
|
63888
64050
|
const stagedFiles = await getStagedSourceFiles(resolvedDirectory);
|
|
63889
64051
|
if (stagedFiles.length === 0) {
|
|
63890
64052
|
if (isJsonMode) writeJsonReport(buildJsonReport({
|
|
@@ -63965,13 +64127,15 @@ const inspectAction = async (directory, flags) => {
|
|
|
63965
64127
|
const projectDirectories = await selectProjects(resolvedDirectory, flags.project, skipPrompts, userConfig?.projects);
|
|
63966
64128
|
const changedFilesDiffInfo = flags.changedFilesFrom ? buildChangedFilesDiffInfo(readChangedFilesFrom(Path.resolve(flags.changedFilesFrom))) : null;
|
|
63967
64129
|
const requestedScope = resolveScope(flags, userConfig);
|
|
64130
|
+
const includeUntracked = flags.includeUntracked ?? false;
|
|
63968
64131
|
const scopeRequest = requestedScope.scope === void 0 && changedFilesDiffInfo !== null ? {
|
|
63969
64132
|
...requestedScope,
|
|
63970
64133
|
scope: "changed"
|
|
63971
64134
|
} : requestedScope;
|
|
64135
|
+
validateIncludeUntrackedScope(includeUntracked, scopeRequest.scope);
|
|
63972
64136
|
const wantsDiffMode = scopeRequest.scope !== void 0 && scopeRequest.scope !== "full";
|
|
63973
64137
|
const shouldDetectDiff = changedFilesDiffInfo === null && (wantsDiffMode || scopeRequest.scope === void 0 && !skipPrompts && !isQuiet);
|
|
63974
|
-
const diffInfo = changedFilesDiffInfo ?? (shouldDetectDiff ? await getDiffInfo(resolvedDirectory, scopeRequest.base) : null);
|
|
64138
|
+
const diffInfo = changedFilesDiffInfo ?? (shouldDetectDiff ? await getDiffInfo(resolvedDirectory, scopeRequest.base, includeUntracked) : null);
|
|
63975
64139
|
const scope = await finalizeScope({
|
|
63976
64140
|
requested: scopeRequest,
|
|
63977
64141
|
diffInfo,
|
|
@@ -63985,7 +64149,8 @@ const inspectAction = async (directory, flags) => {
|
|
|
63985
64149
|
const changedLineRanges = scope === "lines" && diffInfo !== null && (diffInfo.isCurrentChanges || linesBaseRef !== null) && diffInfo !== null ? await getChangedLineRanges({
|
|
63986
64150
|
directory: resolvedDirectory,
|
|
63987
64151
|
baseRef: linesBaseRef ?? void 0,
|
|
63988
|
-
files: [...diffInfo.changedFiles]
|
|
64152
|
+
files: [...diffInfo.changedFiles],
|
|
64153
|
+
includeUntracked
|
|
63989
64154
|
}) : null;
|
|
63990
64155
|
if (scope === "lines" && changedLineRanges === null && !isQuiet) {
|
|
63991
64156
|
cliLogger.warn("Could not determine changed lines (no base ref or git diff failed); reporting all issues in changed files.");
|
|
@@ -64918,6 +65083,7 @@ const ROOT_FLAG_SPEC = {
|
|
|
64918
65083
|
"--dead-code",
|
|
64919
65084
|
"--debug",
|
|
64920
65085
|
"--help",
|
|
65086
|
+
"--include-untracked",
|
|
64921
65087
|
"--json",
|
|
64922
65088
|
"--json-compact",
|
|
64923
65089
|
"--lint",
|
|
@@ -65181,7 +65347,7 @@ ${highlighter.dim("Learn more:")}
|
|
|
65181
65347
|
${highlighter.info(CI_URL)}
|
|
65182
65348
|
`;
|
|
65183
65349
|
const collectCategoryOption = (value, previousValues) => [...previousValues ?? [], value];
|
|
65184
|
-
const program = new Command().name("react-doctor").description("Diagnose React codebase health").version(VERSION, "-v, --version", "display the version number").argument("[directory]", "project directory to scan", ".").option("--lint", "enable linting").option("--no-lint", "skip linting").option("--dead-code", "enable dead-code analysis (default)").option("--no-dead-code", "skip dead-code analysis (unused files / exports / dependencies, circular imports)").option("--supply-chain", "enable the dependency supply-chain scan (default)").option("--no-supply-chain", "skip the dependency supply-chain scan (Socket.dev dependency health checks)").option("--verbose", "show every rule and per-file details (default shows top 3 rules)").option("--debug", "force a Sentry trace and print its id at the end (paste it into a bug report)").option("--output-dir <dir>", "directory for the full diagnostics dump (default: a temp folder)").option("--score", "output only the score").option("--json", "output a single structured JSON report (suppresses other output)").option("--json-compact", "with --json, emit compact JSON (no indentation)").option("--json-out <path>", "with --json, write the report to a file instead of stdout").option("-y, --yes", "skip prompts, scan all workspace projects").option("--no-parallel", "lint serially with one worker (default: parallel across CPU cores; set the worker count with REACT_DOCTOR_PARALLEL)").option("--project <name>", "select projects: workspace names or directory paths (comma-separated for multiple); overrides the `projects` config field").option("--scope <value>", "how much to scan/report: full (default), files, changed (only new issues vs base), or lines (
|
|
65350
|
+
const program = new Command().name("react-doctor").description("Diagnose React codebase health").version(VERSION, "-v, --version", "display the version number").argument("[directory]", "project directory to scan", ".").option("--lint", "enable linting").option("--no-lint", "skip linting").option("--dead-code", "enable dead-code analysis (default)").option("--no-dead-code", "skip dead-code analysis (unused files / exports / dependencies, circular imports)").option("--supply-chain", "enable the dependency supply-chain scan (default)").option("--no-supply-chain", "skip the dependency supply-chain scan (Socket.dev dependency health checks)").option("--verbose", "show every rule and per-file details (default shows top 3 rules)").option("--debug", "force a Sentry trace and print its id at the end (paste it into a bug report)").option("--output-dir <dir>", "directory for the full diagnostics dump (default: a temp folder)").option("--score", "output only the score").option("--json", "output a single structured JSON report (suppresses other output)").option("--json-compact", "with --json, emit compact JSON (no indentation)").option("--json-out <path>", "with --json, write the report to a file instead of stdout").option("-y, --yes", "skip prompts, scan all workspace projects").option("--no-parallel", "lint serially with one worker (default: parallel across CPU cores; set the worker count with REACT_DOCTOR_PARALLEL)").option("--project <name>", "select projects: workspace names or directory paths (comma-separated for multiple); overrides the `projects` config field").option("--scope <value>", "how much supported JS/TS source to scan/report: full (default), files, changed (only new issues vs base), or lines (issues whose source spans touch changed lines)").option("--base <ref>", "base git ref for files/changed/lines scope (auto-detected when omitted)").option("--include-untracked", "with --scope files/changed/lines, also scan ordinary untracked files (respects .gitignore)").addOption(new Option("--diff [base]", "[deprecated] alias for --scope changed (pass `false` to force a full scan)").hideHelp()).addOption(new Option("--changed-files-from <file>", "scan source files listed in a newline-delimited changed-files file").hideHelp()).option("--no-score", "skip the score API, the share URL, and crash reporting").addOption(new Option("--category <category>", "only show diagnostics in a category (repeatable; e.g. Security)").argParser(collectCategoryOption)).option("--no-telemetry", "alias for --no-score (skip the score API, share URL, and crash reporting)").option("--staged", "scan only staged (git index) files for pre-commit hooks").option("--max-duration <seconds>", "scan time budget for the whole run, shared across workspace projects: past it, remaining lint batches and dead-code are skipped and partial results are reported (skipped files are listed in the JSON report)").option("--blocking <level>", "severity that fails CI: error (default), warning, or none (advisory)").addOption(new Option("--fail-on <level>", "[deprecated] alias for --blocking <level>").hideHelp()).option("--no-respect-inline-disables", "audit mode: neutralize inline lint suppressions before scanning").option("--warnings", "show warning-severity diagnostics (default)").option("--no-warnings", "hide warning-severity diagnostics (errors only)").option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").addHelpText("after", renderRootHelpEpilog);
|
|
65185
65351
|
program.action(inspectAction);
|
|
65186
65352
|
program.command("why <location>").description("Explain why a rule fired (or why a suppression didn't apply) at a file:line").option("--project <name>", "select projects: workspace names or directory paths (comma-separated for multiple)").option("-c, --cwd <cwd>", "working directory", process.cwd()).option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").action((location, options) => whyAction(location, options));
|
|
65187
65353
|
program.command("install").alias("setup").description("Install the react-doctor skill into your coding agents and optional git hook").option("-y, --yes", "skip prompts, install for all detected agents").option("--dry-run", "show what would be installed without writing files").option("--agent-hooks", "install native non-blocking agent hooks for Claude Code and Cursor").option("-c, --cwd <cwd>", "working directory", process.cwd()).option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").addHelpText("after", renderInstallHelpEpilog).action(installAction);
|
|
@@ -65231,4 +65397,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
|
|
|
65231
65397
|
export {};
|
|
65232
65398
|
|
|
65233
65399
|
//# sourceMappingURL=cli.js.map
|
|
65234
|
-
//# debugId=
|
|
65400
|
+
//# debugId=58e54b02-7ba2-5f13-a535-98c236467a86
|