react-doctor 0.5.8-dev.849580c → 0.5.8-dev.88a5c3c
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 +9 -3
- package/dist/cli.js +9337 -1520
- package/dist/index.d.ts +21 -0
- package/dist/index.js +174 -89
- package/dist/lsp.js +180 -90
- package/package.json +6 -5
package/dist/lsp.js
CHANGED
|
@@ -6,7 +6,7 @@ import os from "node:os";
|
|
|
6
6
|
import * as Path from "node:path";
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
import * as NodeChildProcess from "node:child_process";
|
|
9
|
-
import { spawn, spawnSync } from "node:child_process";
|
|
9
|
+
import { execFileSync, spawn, spawnSync } from "node:child_process";
|
|
10
10
|
import * as ts from "typescript";
|
|
11
11
|
import reactDoctorPlugin, { ALL_REACT_DOCTOR_RULE_KEYS, CROSS_FILE_RULE_IDS, FRAMEWORK_SPECIFIC_RULE_KEYS, MOTION_LIBRARY_PACKAGES, REACT_COMPILER_RULES, REACT_DOCTOR_RULES, classifySecurityScanFile, shouldReadSecurityScanContent } from "oxlint-plugin-react-doctor";
|
|
12
12
|
import { parseJSON5 } from "confbox";
|
|
@@ -19400,6 +19400,15 @@ var JsonReportV1 = class extends Class("JsonReportV1")({
|
|
|
19400
19400
|
ok: Boolean$1,
|
|
19401
19401
|
directory: String$1,
|
|
19402
19402
|
mode: JsonReportMode,
|
|
19403
|
+
/**
|
|
19404
|
+
* True when a `changed` (compare) run was intended but the base couldn't be
|
|
19405
|
+
* resolved — a shallow CI checkout with no merge base, or a failed base/head
|
|
19406
|
+
* lint. The report then lists every finding in the changed files (mode
|
|
19407
|
+
* downgrades to `diff`, the `baseline` block is dropped, the CI gate is
|
|
19408
|
+
* skipped) rather than only the ones the change introduced. Absent on a
|
|
19409
|
+
* successful `baseline` run (`schemaVersion: 2`) and on non-compare scopes.
|
|
19410
|
+
*/
|
|
19411
|
+
baselineDegraded: optional(Boolean$1),
|
|
19403
19412
|
diff: NullOr(JsonReportDiffInfo),
|
|
19404
19413
|
projects: ArraySchema(JsonReportProjectEntry),
|
|
19405
19414
|
diagnostics: ArraySchema(Diagnostic),
|
|
@@ -33731,6 +33740,9 @@ const SCORE_API_URL = "https://www.react.doctor/api/score";
|
|
|
33731
33740
|
const FETCH_TIMEOUT_MS = 1e4;
|
|
33732
33741
|
const GITHUB_VIEWER_PERMISSION_TIMEOUT_MS = 2e3;
|
|
33733
33742
|
const SPAWN_ARGS_MAX_LENGTH_CHARS = 24e3;
|
|
33743
|
+
const SPAWN_ARGS_MAX_LENGTH_CHARS_POSIX = 15e5;
|
|
33744
|
+
const SPAWN_ARGS_MAX_LENGTH_CHARS_DARWIN = 8e5;
|
|
33745
|
+
const NODE_VERSION_PROBE_TIMEOUT_MS = 5e3;
|
|
33734
33746
|
const PER_WORKER_MEM_BUDGET_BYTES = 1024 * 1024 * 1024;
|
|
33735
33747
|
const DEFAULT_BRANCH_CANDIDATES = ["main", "master"];
|
|
33736
33748
|
const ADOPTABLE_LINT_CONFIG_FILENAMES = [".oxlintrc.json", ".eslintrc.json"];
|
|
@@ -34578,7 +34590,7 @@ const tokenizeRuleList = (ruleList) => {
|
|
|
34578
34590
|
const FOREIGN_INLINE_DISABLE_PATTERN = /(?:\/\/|\/\*)[ \t]*(eslint|oxlint)-disable-(next-line|line)(?![\w-])([^\r\n]*)/;
|
|
34579
34591
|
const FOREIGN_BLOCK_DISABLE_PATTERN = /\/\*[ \t]*(eslint|oxlint)-disable(?![\w-])([^*\r\n]*)/;
|
|
34580
34592
|
const FOREIGN_BLOCK_ENABLE_PATTERN = /\/\*[ \t]*(?:eslint|oxlint)-enable(?![\w-])([^*\r\n]*)/;
|
|
34581
|
-
const buildHint = (tool, token, ruleId) => `oxlint matches plugin rules only by their full name, so \`${token}\` in your ${tool}-disable comment does not silence \`${ruleId}\` —
|
|
34593
|
+
const buildHint = (tool, token, ruleId) => `oxlint matches plugin rules only by their full name, so \`${token}\` in your ${tool}-disable comment does not silence \`${ruleId}\` — add \`${ruleId}\` to the comment (e.g. \`${token}, ${ruleId}\`).`;
|
|
34582
34594
|
const tokenMisnamesRule = (token, ruleId) => token !== ruleId && isSameRuleKey(token, ruleId);
|
|
34583
34595
|
const detectInlineNearMiss = (lines, diagnosticLineIndex, ruleId) => {
|
|
34584
34596
|
const candidates = [{
|
|
@@ -35054,6 +35066,7 @@ const collectStringSet = (values) => {
|
|
|
35054
35066
|
if (!Array.isArray(values)) return /* @__PURE__ */ new Set();
|
|
35055
35067
|
return new Set(values.filter((value) => typeof value === "string"));
|
|
35056
35068
|
};
|
|
35069
|
+
const JSX_NO_UNDEF_IDENTIFIER_PATTERN = /^`([^`]+)`/;
|
|
35057
35070
|
/**
|
|
35058
35071
|
* Pre-compiles every stateful filter and returns a single
|
|
35059
35072
|
* `apply(diagnostic)` closure that runs (in order):
|
|
@@ -35087,11 +35100,28 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
35087
35100
|
const compiledOverrides = compileIgnoreOverrides(userConfig);
|
|
35088
35101
|
const textComponentNames = collectStringSet(userConfig?.textComponents);
|
|
35089
35102
|
const rawTextWrapperComponentNames = collectStringSet(userConfig?.rawTextWrapperComponents);
|
|
35103
|
+
const runtimeGlobalNames = collectStringSet(userConfig?.runtimeGlobals);
|
|
35090
35104
|
const hasTextComponents = textComponentNames.size > 0;
|
|
35091
35105
|
const hasRawTextWrappers = rawTextWrapperComponentNames.size > 0;
|
|
35106
|
+
const hasRuntimeGlobals = runtimeGlobalNames.size > 0;
|
|
35092
35107
|
const fileLinesCache = /* @__PURE__ */ new Map();
|
|
35093
35108
|
const fileContextCache = /* @__PURE__ */ new Map();
|
|
35094
35109
|
const libraryFileCache = /* @__PURE__ */ new Map();
|
|
35110
|
+
const suppressions = /* @__PURE__ */ new Map();
|
|
35111
|
+
const suppress = (diagnostic, source) => {
|
|
35112
|
+
const { ruleKey } = getDiagnosticRuleIdentity(diagnostic);
|
|
35113
|
+
const suppressionKey = `${ruleKey}\u0000${source}`;
|
|
35114
|
+
const existing = suppressions.get(suppressionKey);
|
|
35115
|
+
suppressions.set(suppressionKey, existing ? {
|
|
35116
|
+
...existing,
|
|
35117
|
+
count: existing.count + 1
|
|
35118
|
+
} : {
|
|
35119
|
+
rule: ruleKey,
|
|
35120
|
+
source,
|
|
35121
|
+
count: 1
|
|
35122
|
+
});
|
|
35123
|
+
return null;
|
|
35124
|
+
};
|
|
35095
35125
|
const isLibraryFile = (filePath) => {
|
|
35096
35126
|
let cached = libraryFileCache.get(filePath);
|
|
35097
35127
|
if (cached === void 0) {
|
|
@@ -35140,50 +35170,60 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
35140
35170
|
if (hasRawTextWrappers && isInsideStringOnlyWrapper(lines, diagnostic.line, diagnostic.column, rawTextWrapperComponentNames)) return true;
|
|
35141
35171
|
return false;
|
|
35142
35172
|
};
|
|
35143
|
-
|
|
35144
|
-
if (
|
|
35145
|
-
|
|
35146
|
-
|
|
35147
|
-
|
|
35148
|
-
|
|
35149
|
-
|
|
35150
|
-
|
|
35151
|
-
|
|
35152
|
-
|
|
35153
|
-
|
|
35154
|
-
|
|
35155
|
-
if (
|
|
35156
|
-
|
|
35157
|
-
|
|
35158
|
-
|
|
35159
|
-
|
|
35160
|
-
|
|
35161
|
-
|
|
35162
|
-
|
|
35163
|
-
|
|
35164
|
-
if (isFileIgnoredByPatterns(current.filePath, rootDirectory, ignoredFilePatterns)) return null;
|
|
35165
|
-
if (isDiagnosticIgnoredByOverrides(current, rootDirectory, compiledOverrides)) return null;
|
|
35166
|
-
if (isRnRawTextSuppressedByConfig(current)) return null;
|
|
35167
|
-
}
|
|
35168
|
-
if (respectInlineDisables && current.line > 0) {
|
|
35169
|
-
const lines = getFileLines(current.filePath);
|
|
35170
|
-
if (lines) {
|
|
35171
|
-
const ruleIdentifier = `${current.plugin}/${current.rule}`;
|
|
35172
|
-
const evaluation = evaluateSuppression(lines, current.line - 1, ruleIdentifier);
|
|
35173
|
-
if (evaluation.isSuppressed) return null;
|
|
35174
|
-
if (evaluation.nearMissHint) current = {
|
|
35175
|
-
...current,
|
|
35176
|
-
suppressionHint: evaluation.nearMissHint
|
|
35177
|
-
};
|
|
35173
|
+
const isJsxNoUndefSuppressedByConfig = (diagnostic) => {
|
|
35174
|
+
if (!hasRuntimeGlobals) return false;
|
|
35175
|
+
if (diagnostic.rule !== "jsx-no-undef") return false;
|
|
35176
|
+
const identifierMatch = JSX_NO_UNDEF_IDENTIFIER_PATTERN.exec(diagnostic.message);
|
|
35177
|
+
return identifierMatch !== null && runtimeGlobalNames.has(identifierMatch[1]);
|
|
35178
|
+
};
|
|
35179
|
+
return {
|
|
35180
|
+
apply: (diagnostic) => {
|
|
35181
|
+
if (shouldAutoSuppress(diagnostic)) return null;
|
|
35182
|
+
let current = diagnostic;
|
|
35183
|
+
let explicitSeverityOverride;
|
|
35184
|
+
let explicitRuleOverride;
|
|
35185
|
+
if (severityControls) {
|
|
35186
|
+
const { ruleKey, category } = getDiagnosticRuleIdentity(current);
|
|
35187
|
+
explicitRuleOverride = resolveRuleSeverityOverride({ ruleKey }, severityControls);
|
|
35188
|
+
explicitSeverityOverride = resolveRuleSeverityOverride({
|
|
35189
|
+
ruleKey,
|
|
35190
|
+
category
|
|
35191
|
+
}, severityControls);
|
|
35192
|
+
if (explicitSeverityOverride === "off") return suppress(current, "config");
|
|
35193
|
+
if (explicitSeverityOverride !== void 0) current = restampSeverity(current, explicitSeverityOverride);
|
|
35178
35194
|
}
|
|
35179
|
-
|
|
35180
|
-
|
|
35181
|
-
|
|
35182
|
-
|
|
35183
|
-
|
|
35184
|
-
|
|
35185
|
-
|
|
35186
|
-
|
|
35195
|
+
if (explicitRuleOverride === void 0) {
|
|
35196
|
+
if (isAppOnlyRule(`${current.plugin}/${current.rule}`) && isLibraryFile(current.filePath)) return null;
|
|
35197
|
+
}
|
|
35198
|
+
if (!showWarnings && current.severity === "warning" && explicitSeverityOverride !== "warn") return null;
|
|
35199
|
+
if (userConfig) {
|
|
35200
|
+
if (isRuleIgnored(`${current.plugin}/${current.rule}`)) return suppress(current, "config");
|
|
35201
|
+
if (isFileIgnoredByPatterns(current.filePath, rootDirectory, ignoredFilePatterns)) return null;
|
|
35202
|
+
if (isDiagnosticIgnoredByOverrides(current, rootDirectory, compiledOverrides)) return suppress(current, "override");
|
|
35203
|
+
if (isRnRawTextSuppressedByConfig(current)) return null;
|
|
35204
|
+
if (isJsxNoUndefSuppressedByConfig(current)) return null;
|
|
35205
|
+
}
|
|
35206
|
+
if (respectInlineDisables && current.line > 0) {
|
|
35207
|
+
const lines = getFileLines(current.filePath);
|
|
35208
|
+
if (lines) {
|
|
35209
|
+
const ruleIdentifier = `${current.plugin}/${current.rule}`;
|
|
35210
|
+
const evaluation = evaluateSuppression(lines, current.line - 1, ruleIdentifier);
|
|
35211
|
+
if (evaluation.isSuppressed) return suppress(current, "inline");
|
|
35212
|
+
if (evaluation.nearMissHint) current = {
|
|
35213
|
+
...current,
|
|
35214
|
+
suppressionHint: evaluation.nearMissHint
|
|
35215
|
+
};
|
|
35216
|
+
}
|
|
35217
|
+
}
|
|
35218
|
+
const fileContext = getFileContext(current.filePath);
|
|
35219
|
+
if (fileContext !== "production") current = {
|
|
35220
|
+
...current,
|
|
35221
|
+
fileContext
|
|
35222
|
+
};
|
|
35223
|
+
return current;
|
|
35224
|
+
},
|
|
35225
|
+
summarizeSuppressions: () => [...suppressions.values()]
|
|
35226
|
+
};
|
|
35187
35227
|
};
|
|
35188
35228
|
const OxlintUnavailableKind = Literals(["binary-not-found", "native-binding-missing"]);
|
|
35189
35229
|
var OxlintUnavailable = class extends TaggedErrorClass()("OxlintUnavailable", {
|
|
@@ -35465,13 +35505,7 @@ var ScanDeadlineMs = class extends Reference("react-doctor/ScanDeadlineMs", { de
|
|
|
35465
35505
|
* without recompiling react-doctor. Tests override via
|
|
35466
35506
|
* `Layer.succeed(SupplyChainOverlapTimeoutMs, ...)`.
|
|
35467
35507
|
*/
|
|
35468
|
-
var SupplyChainOverlapTimeoutMs = class extends Reference("react-doctor/SupplyChainOverlapTimeoutMs", { defaultValue: () => {
|
|
35469
|
-
const raw = process.env["REACT_DOCTOR_SUPPLY_CHAIN_TIMEOUT_MS"];
|
|
35470
|
-
if (raw === void 0) return SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS;
|
|
35471
|
-
const parsed = Number(raw);
|
|
35472
|
-
if (!Number.isFinite(parsed) || parsed <= 0) return SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS;
|
|
35473
|
-
return parsed;
|
|
35474
|
-
} }) {};
|
|
35508
|
+
var SupplyChainOverlapTimeoutMs = class extends Reference("react-doctor/SupplyChainOverlapTimeoutMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_SUPPLY_CHAIN_TIMEOUT_MS", SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS) }) {};
|
|
35475
35509
|
/**
|
|
35476
35510
|
* Hard cap on combined stdout+stderr bytes per oxlint batch. The
|
|
35477
35511
|
* subprocess gets SIGKILL'd if it produces more; the recovery path
|
|
@@ -35619,6 +35653,7 @@ const STRING_ARRAY_FIELD_NAMES = [
|
|
|
35619
35653
|
"projects",
|
|
35620
35654
|
"textComponents",
|
|
35621
35655
|
"rawTextWrapperComponents",
|
|
35656
|
+
"runtimeGlobals",
|
|
35622
35657
|
"serverAuthFunctionNames"
|
|
35623
35658
|
];
|
|
35624
35659
|
const SURFACE_CONTROL_FIELD_NAMES = [
|
|
@@ -37275,6 +37310,11 @@ const toCanonicalPath = (filePath) => {
|
|
|
37275
37310
|
};
|
|
37276
37311
|
const DEAD_CODE_PLUGIN = "deslop";
|
|
37277
37312
|
const DEAD_CODE_CATEGORY = "Maintainability";
|
|
37313
|
+
const REACT_DOCTOR_TOOLCHAIN_PACKAGES = new Set([
|
|
37314
|
+
"react-doctor",
|
|
37315
|
+
"eslint-plugin-react-doctor",
|
|
37316
|
+
"oxlint-plugin-react-doctor"
|
|
37317
|
+
]);
|
|
37278
37318
|
const DEAD_CODE_WORKER_SCRIPT = `
|
|
37279
37319
|
const inputChunks = [];
|
|
37280
37320
|
process.stdin.on("data", (chunk) => inputChunks.push(chunk));
|
|
@@ -37580,6 +37620,7 @@ const checkDeadCode = async (options) => {
|
|
|
37580
37620
|
});
|
|
37581
37621
|
}
|
|
37582
37622
|
for (const unusedDependency of result.unusedDependencies) {
|
|
37623
|
+
if (REACT_DOCTOR_TOOLCHAIN_PACKAGES.has(unusedDependency.name)) continue;
|
|
37583
37624
|
const label = unusedDependency.isDevDependency ? "devDependency" : "dependency";
|
|
37584
37625
|
diagnostics.push({
|
|
37585
37626
|
filePath: "package.json",
|
|
@@ -37758,7 +37799,6 @@ var DeadCode = class DeadCode extends Service()("react-doctor/DeadCode") {
|
|
|
37758
37799
|
return yield* tryPromise({
|
|
37759
37800
|
try: (signal) => checkDeadCode({
|
|
37760
37801
|
rootDirectory: input.rootDirectory,
|
|
37761
|
-
userConfig: input.userConfig,
|
|
37762
37802
|
parseConcurrency: input.parseConcurrency,
|
|
37763
37803
|
workerTimeoutMs: input.workerTimeoutMs,
|
|
37764
37804
|
abortSignal: signal
|
|
@@ -37881,6 +37921,11 @@ const isSafeGitRevision = (candidate) => {
|
|
|
37881
37921
|
if (candidate.includes("..") || candidate.includes("@{")) return false;
|
|
37882
37922
|
return SAFE_GIT_REVISION_PATTERN.test(candidate);
|
|
37883
37923
|
};
|
|
37924
|
+
const resolveSpawnArgsLengthCap = () => {
|
|
37925
|
+
if (process.platform === "win32") return SPAWN_ARGS_MAX_LENGTH_CHARS;
|
|
37926
|
+
if (process.platform === "darwin") return SPAWN_ARGS_MAX_LENGTH_CHARS_DARWIN;
|
|
37927
|
+
return SPAWN_ARGS_MAX_LENGTH_CHARS_POSIX;
|
|
37928
|
+
};
|
|
37884
37929
|
/**
|
|
37885
37930
|
* Splits a git revision range into its endpoints: three-dot `A...B`
|
|
37886
37931
|
* (symmetric, merge-base) or two-dot `A..B` (direct). Returns `null`
|
|
@@ -37965,7 +38010,8 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
37965
38010
|
return scoped(gen(function* () {
|
|
37966
38011
|
if (!isDirectory(input.directory)) return yield* foldSpawnFailure(`spawn ENOTDIR (cwd is not a directory: ${input.directory})`);
|
|
37967
38012
|
const argvLengthChars = input.command.length + 1 + input.args.reduce((total, arg) => total + arg.length + 1, 0);
|
|
37968
|
-
|
|
38013
|
+
const spawnArgsLengthCap = resolveSpawnArgsLengthCap();
|
|
38014
|
+
if (argvLengthChars > spawnArgsLengthCap) return yield* foldSpawnFailure(`spawn ENAMETOOLONG (${argvLengthChars} argv chars exceed ${spawnArgsLengthCap})`);
|
|
37969
38015
|
const handle = yield* spawner.spawn(make$1(input.command, [...input.args], {
|
|
37970
38016
|
cwd: input.directory,
|
|
37971
38017
|
env: input.env,
|
|
@@ -38134,6 +38180,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
38134
38180
|
if (resolvedCurrentBranch === null && explicitBaseBranch === void 0) return null;
|
|
38135
38181
|
const baseBranch = explicitBaseBranch ?? (yield* defaultBranch(directory));
|
|
38136
38182
|
if (baseBranch === null) return null;
|
|
38183
|
+
if (!isSafeGitRevision(baseBranch)) return null;
|
|
38137
38184
|
if (explicitBaseBranch !== void 0) {
|
|
38138
38185
|
if (!(yield* branchExists(directory, explicitBaseBranch))) return yield* fail$4(new ReactDoctorError({ reason: new GitBaseBranchMissing({ branch: explicitBaseBranch }) }));
|
|
38139
38186
|
}
|
|
@@ -38484,7 +38531,7 @@ const normalizeConfigForHash = (config) => {
|
|
|
38484
38531
|
if (Array.isArray(clone?.jsPlugins)) clone.jsPlugins = clone.jsPlugins.map((_, index) => `<plugin:${index}>`);
|
|
38485
38532
|
return clone;
|
|
38486
38533
|
};
|
|
38487
|
-
const computeRulesetHash = (input) => crypto.createHash("sha1").update(JSON.stringify(normalizeConfigForHash(input.config))).update("\0").update([...input.toolchainVersions].join("\0")).update("\0").update([...input.ignorePatterns].join("\n")).update("
|
|
38534
|
+
const computeRulesetHash = (input) => crypto.createHash("sha1").update(JSON.stringify(normalizeConfigForHash(input.config))).update("\0").update([...input.toolchainVersions].join("\0")).update("\0").update([...input.ignorePatterns].join("\n")).update("\0").update(input.tsconfigContent ?? "").digest("hex");
|
|
38488
38535
|
/**
|
|
38489
38536
|
* Loads a plugin module via the local require resolver and extracts
|
|
38490
38537
|
* `(name, ruleNames)` from either `module.exports.meta + rules` or
|
|
@@ -38655,12 +38702,12 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
38655
38702
|
if (customRulesOnly && registryEntry.originallyExternal) continue;
|
|
38656
38703
|
if (rule.framework !== "global" && !rule.requires) continue;
|
|
38657
38704
|
if (!shouldEnableRule(rule.requires, rule.tags, capabilities, ignoredTags, rule.disabledBy)) continue;
|
|
38658
|
-
const
|
|
38705
|
+
const explicitRuleOverride = resolveRuleSeverityOverride({ ruleKey: registryEntry.key }, severityControls);
|
|
38706
|
+
if (rule.defaultEnabled === false && explicitRuleOverride === void 0) continue;
|
|
38707
|
+
const severity = resolveRuleSeverityOverride({
|
|
38659
38708
|
ruleKey: registryEntry.key,
|
|
38660
38709
|
category: rule.category
|
|
38661
|
-
}, severityControls);
|
|
38662
|
-
if (rule.defaultEnabled === false && explicitSeverity === void 0) continue;
|
|
38663
|
-
const severity = explicitSeverity ?? resolveCompilerCleanupBucketSeverity(registryEntry.key, severityControls) ?? rule.severity;
|
|
38710
|
+
}, severityControls) ?? resolveCompilerCleanupBucketSeverity(registryEntry.key, severityControls) ?? rule.severity;
|
|
38664
38711
|
if (severity === "off") continue;
|
|
38665
38712
|
enabledReactDoctorRules[registryEntry.key] = severity;
|
|
38666
38713
|
}
|
|
@@ -38762,24 +38809,38 @@ const createFileLintCache = (cacheDirectory, rulesetHash) => {
|
|
|
38762
38809
|
updatedAtMs: Date.now(),
|
|
38763
38810
|
files: Object.fromEntries(cappedEntries)
|
|
38764
38811
|
};
|
|
38765
|
-
const keptHashes = Object.entries(rulesets).sort(([, first], [, second]) => second.updatedAtMs - first.updatedAtMs).slice(0, 8).map(([hash]) => hash);
|
|
38766
|
-
const prunedRulesets = {};
|
|
38767
|
-
for (const hash of keptHashes) prunedRulesets[hash] = rulesets[hash];
|
|
38768
38812
|
atomicWriteJson(cacheFilePath, {
|
|
38769
38813
|
version: 1,
|
|
38770
|
-
rulesets:
|
|
38814
|
+
rulesets: Object.fromEntries(Object.entries(rulesets).filter(([, bucket]) => isRecord(bucket) && typeof bucket.updatedAtMs === "number").sort(([, first], [, second]) => second.updatedAtMs - first.updatedAtMs).slice(0, 8))
|
|
38771
38815
|
});
|
|
38772
38816
|
}
|
|
38773
38817
|
};
|
|
38774
38818
|
};
|
|
38775
38819
|
const bundledRequire = createRequire(import.meta.url);
|
|
38820
|
+
const childNodeVersionByBinaryPath = /* @__PURE__ */ new Map();
|
|
38821
|
+
const resolveChildNodeVersion = (nodeBinaryPath) => {
|
|
38822
|
+
if (nodeBinaryPath === process.execPath) return process.version;
|
|
38823
|
+
const cachedVersion = childNodeVersionByBinaryPath.get(nodeBinaryPath);
|
|
38824
|
+
if (cachedVersion !== void 0) return cachedVersion;
|
|
38825
|
+
let version;
|
|
38826
|
+
try {
|
|
38827
|
+
version = execFileSync(nodeBinaryPath, ["--version"], {
|
|
38828
|
+
encoding: "utf8",
|
|
38829
|
+
timeout: NODE_VERSION_PROBE_TIMEOUT_MS
|
|
38830
|
+
}).trim();
|
|
38831
|
+
} catch {
|
|
38832
|
+
version = `unknown:${nodeBinaryPath}`;
|
|
38833
|
+
}
|
|
38834
|
+
childNodeVersionByBinaryPath.set(nodeBinaryPath, version);
|
|
38835
|
+
return version;
|
|
38836
|
+
};
|
|
38776
38837
|
const TOOLCHAIN_PACKAGE_SPECIFIERS = [
|
|
38777
38838
|
"oxlint/package.json",
|
|
38778
38839
|
"oxlint-plugin-react-doctor/package.json",
|
|
38779
38840
|
"eslint-plugin-react-hooks/package.json"
|
|
38780
38841
|
];
|
|
38781
|
-
const resolveOxlintToolchainVersions = () => {
|
|
38782
|
-
const versions = [`node=${
|
|
38842
|
+
const resolveOxlintToolchainVersions = (nodeBinaryPath = process.execPath) => {
|
|
38843
|
+
const versions = [`node=${resolveChildNodeVersion(nodeBinaryPath)}`];
|
|
38783
38844
|
for (const specifier of TOOLCHAIN_PACKAGE_SPECIFIERS) try {
|
|
38784
38845
|
const packageJson = bundledRequire(specifier);
|
|
38785
38846
|
const version = typeof packageJson.version === "string" ? packageJson.version : "unknown";
|
|
@@ -39313,11 +39374,12 @@ const REACT_COMPILER_TITLE = "React Compiler can't optimize this";
|
|
|
39313
39374
|
const REACT_COMPILER_TODO_TITLE = "React Compiler doesn't support this syntax";
|
|
39314
39375
|
const REACT_COMPILER_IMPACT = "This component misses React Compiler's automatic memoization & re-renders more than it should";
|
|
39315
39376
|
const REACT_COMPILER_ACTION = "Rewrite the flagged code so the compiler can optimize it.";
|
|
39377
|
+
const REACT_COMPILER_INCOMPATIBLE_LIBRARY_ACTION = "It's how the library works, not a bug in your code. Memoize values you pass from it into other memoized components, or suppress it with `// react-doctor-disable-next-line react-hooks-js/incompatible-library`.";
|
|
39316
39378
|
const REACT_COMPILER_GENERIC_MESSAGE = `${REACT_COMPILER_IMPACT}. ${REACT_COMPILER_ACTION}`;
|
|
39317
|
-
const buildReactCompilerMessage = (reasonSummary) => {
|
|
39379
|
+
const buildReactCompilerMessage = (reasonSummary, action = REACT_COMPILER_ACTION) => {
|
|
39318
39380
|
const normalizedSummary = reasonSummary.replace(TRAILING_PERIOD_PATTERN, "");
|
|
39319
|
-
if (!normalizedSummary) return
|
|
39320
|
-
return `${REACT_COMPILER_IMPACT}: ${normalizedSummary}. ${
|
|
39381
|
+
if (!normalizedSummary) return `${REACT_COMPILER_IMPACT}. ${action}`;
|
|
39382
|
+
return `${REACT_COMPILER_IMPACT}: ${normalizedSummary}. ${action}`;
|
|
39321
39383
|
};
|
|
39322
39384
|
const PLUGIN_CATEGORY_MAP = {
|
|
39323
39385
|
react: "Bugs",
|
|
@@ -39366,7 +39428,7 @@ const resolveCleanedDiagnostic = (message, help, plugin, rule, project) => {
|
|
|
39366
39428
|
const [reasonSummary = "", ...reasonDetailLines] = bailoutReason.split("\n");
|
|
39367
39429
|
const reasonDetail = reasonDetailLines.join("\n").trim();
|
|
39368
39430
|
return {
|
|
39369
|
-
message: buildReactCompilerMessage(reasonSummary.trim()),
|
|
39431
|
+
message: buildReactCompilerMessage(reasonSummary.trim(), rule === "incompatible-library" ? REACT_COMPILER_INCOMPATIBLE_LIBRARY_ACTION : REACT_COMPILER_ACTION),
|
|
39370
39432
|
help: appendReanimatedSharedValueHint(reasonDetail || help, rule, project)
|
|
39371
39433
|
};
|
|
39372
39434
|
}
|
|
@@ -39634,21 +39696,27 @@ const spawnLintBatches = async (input) => {
|
|
|
39634
39696
|
const allDiagnostics = [];
|
|
39635
39697
|
const droppedFiles = [];
|
|
39636
39698
|
let firstDropReason = null;
|
|
39637
|
-
const
|
|
39638
|
-
const spawnLintBatch = async (batch, depth) => {
|
|
39699
|
+
const spawnLintBatch = async (batch, depth, splitBudget) => {
|
|
39639
39700
|
const batchArgs = [...baseArgs, ...batch];
|
|
39640
39701
|
try {
|
|
39641
39702
|
return parseOxlintOutput(await spawnOxlint(batchArgs, rootDirectory, nodeBinaryPath, spawnTimeoutMs, outputMaxBytes, signal), project, rootDirectory);
|
|
39642
39703
|
} catch (error) {
|
|
39643
39704
|
if (!isSplittableReactDoctorError(error)) throw error;
|
|
39644
|
-
|
|
39645
|
-
|
|
39705
|
+
splitBudget.deadlineMs ??= Date.now() + splitTotalBudgetMs;
|
|
39706
|
+
const isBudgetElapsed = Date.now() >= splitBudget.deadlineMs;
|
|
39707
|
+
const isDepthCapReached = depth >= splitMaxDepth;
|
|
39708
|
+
if (batch.length <= 1 || isBudgetElapsed || isDepthCapReached) {
|
|
39646
39709
|
droppedFiles.push(...batch);
|
|
39647
|
-
if (firstDropReason === null)
|
|
39710
|
+
if (firstDropReason === null) {
|
|
39711
|
+
let limitHint = "";
|
|
39712
|
+
if (isDepthCapReached) limitHint = ` (split depth cap of ${splitMaxDepth} levels reached)`;
|
|
39713
|
+
else if (isBudgetElapsed) limitHint = ` (split budget of ${splitTotalBudgetMs / MILLISECONDS_PER_SECOND}s exhausted at depth ${depth})`;
|
|
39714
|
+
firstDropReason = batch.length > 1 ? `${error.message}${limitHint}` : error.message;
|
|
39715
|
+
}
|
|
39648
39716
|
return [];
|
|
39649
39717
|
}
|
|
39650
39718
|
const splitIndex = Math.ceil(batch.length / 2);
|
|
39651
|
-
return [...await spawnLintBatch(batch.slice(0, splitIndex), depth + 1), ...await spawnLintBatch(batch.slice(splitIndex), depth + 1)];
|
|
39719
|
+
return [...await spawnLintBatch(batch.slice(0, splitIndex), depth + 1, splitBudget), ...await spawnLintBatch(batch.slice(splitIndex), depth + 1, splitBudget)];
|
|
39652
39720
|
}
|
|
39653
39721
|
};
|
|
39654
39722
|
let startedFileCount = 0;
|
|
@@ -39665,7 +39733,7 @@ const spawnLintBatches = async (input) => {
|
|
|
39665
39733
|
try {
|
|
39666
39734
|
const batchResults = await mapWithConcurrency(fileBatches, concurrency, async (batch) => {
|
|
39667
39735
|
startedFileCount += batch.length;
|
|
39668
|
-
const batchDiagnostics = await spawnLintBatch(batch, 0);
|
|
39736
|
+
const batchDiagnostics = await spawnLintBatch(batch, 0, { deadlineMs: null });
|
|
39669
39737
|
scannedFileCount += batch.length;
|
|
39670
39738
|
if (onFileProgress) {
|
|
39671
39739
|
displayedFileCount = Math.min(Math.max(displayedFileCount, scannedFileCount), totalFileCount);
|
|
@@ -39739,7 +39807,14 @@ const resolveReactDoctorCacheDir = (projectDirectory) => {
|
|
|
39739
39807
|
if (cacheDirOverride) return Path.join(cacheDirOverride, projectCacheSubdir(projectDirectory));
|
|
39740
39808
|
const nodeModulesDirectory = Path.join(projectDirectory, "node_modules");
|
|
39741
39809
|
if (NFS.existsSync(nodeModulesDirectory)) return Path.join(nodeModulesDirectory, ".cache", "react-doctor");
|
|
39742
|
-
return Path.join(os.tmpdir(),
|
|
39810
|
+
return Path.join(os.tmpdir(), `react-doctor-cache-${resolveUserCacheScope()}`, projectCacheSubdir(projectDirectory));
|
|
39811
|
+
};
|
|
39812
|
+
const resolveUserCacheScope = () => {
|
|
39813
|
+
try {
|
|
39814
|
+
return String(process.getuid?.() ?? os.userInfo().username);
|
|
39815
|
+
} catch {
|
|
39816
|
+
return "shared";
|
|
39817
|
+
}
|
|
39743
39818
|
};
|
|
39744
39819
|
const sortSourceFilesByCost = (entries) => [...entries].sort((left, right) => right.sizeBytes - left.sizeBytes).map((entry) => entry.path);
|
|
39745
39820
|
/**
|
|
@@ -39907,7 +39982,7 @@ const runOxlint = async (options) => {
|
|
|
39907
39982
|
extendsPaths: [],
|
|
39908
39983
|
ruleSelection: "cacheable"
|
|
39909
39984
|
}),
|
|
39910
|
-
toolchainVersions: resolveOxlintToolchainVersions(),
|
|
39985
|
+
toolchainVersions: resolveOxlintToolchainVersions(nodeBinaryPath),
|
|
39911
39986
|
ignorePatterns: combinedPatterns,
|
|
39912
39987
|
tsconfigContent
|
|
39913
39988
|
});
|
|
@@ -39921,7 +39996,7 @@ const runOxlint = async (options) => {
|
|
|
39921
39996
|
missFiles.push(candidateFile);
|
|
39922
39997
|
continue;
|
|
39923
39998
|
}
|
|
39924
|
-
const cacheKey = `${candidateFile.replaceAll("\\", "/")}
|
|
39999
|
+
const cacheKey = `${candidateFile.replaceAll("\\", "/")}\u0000${contentHash}`;
|
|
39925
40000
|
cacheKeyByFile.set(candidateFile, cacheKey);
|
|
39926
40001
|
const cachedDiagnostics = cache.lookup(cacheKey);
|
|
39927
40002
|
if (cachedDiagnostics === null) missFiles.push(candidateFile);
|
|
@@ -40237,7 +40312,7 @@ const ScoreApiResponseSchema = Struct({
|
|
|
40237
40312
|
})))
|
|
40238
40313
|
});
|
|
40239
40314
|
const parseScoreResult = (value) => getOrNull(decodeUnknownOption(ScoreApiResponseSchema)(value));
|
|
40240
|
-
const stripLocalFileFields = (diagnostics) => diagnostics.map(({ filePath: _filePath, fileContext: _fileContext, ...rest }) => rest);
|
|
40315
|
+
const stripLocalFileFields = (diagnostics) => diagnostics.map(({ filePath: _filePath, fileContext: _fileContext, fixGroupId: _fixGroupId, ...rest }) => rest);
|
|
40241
40316
|
const isAbortError = (error) => error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError");
|
|
40242
40317
|
const describeFailure = (error) => {
|
|
40243
40318
|
if (isAbortError(error)) return `timed out after ${FETCH_TIMEOUT_MS / 1e3}s`;
|
|
@@ -40541,7 +40616,10 @@ const ALERT_SEVERITY_RANK = {
|
|
|
40541
40616
|
medium: 2,
|
|
40542
40617
|
low: 1
|
|
40543
40618
|
};
|
|
40544
|
-
const severityRank = (severity) =>
|
|
40619
|
+
const severityRank = (severity) => {
|
|
40620
|
+
const normalized = severity.toLowerCase();
|
|
40621
|
+
return Object.hasOwn(ALERT_SEVERITY_RANK, normalized) ? ALERT_SEVERITY_RANK[normalized] : 0;
|
|
40622
|
+
};
|
|
40545
40623
|
const displaySeverity = (severity) => {
|
|
40546
40624
|
const normalized = sanitizeTerminalText(severity.toLowerCase());
|
|
40547
40625
|
return normalized === "middle" ? "medium" : normalized;
|
|
@@ -40558,7 +40636,7 @@ const ALERT_TYPE_LABELS = {
|
|
|
40558
40636
|
typosquat: "possible typosquat"
|
|
40559
40637
|
};
|
|
40560
40638
|
const humanizeAlertType = (type) => type.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").toLowerCase().trim();
|
|
40561
|
-
const friendlyAlertType = (type) => ALERT_TYPE_LABELS[type]
|
|
40639
|
+
const friendlyAlertType = (type) => Object.hasOwn(ALERT_TYPE_LABELS, type) ? ALERT_TYPE_LABELS[type] : sanitizeTerminalText(humanizeAlertType(type));
|
|
40562
40640
|
const summarizeAlertNote = (note) => {
|
|
40563
40641
|
const collapsed = sanitizeTerminalText(note.replace(/\s+/g, " ").trim());
|
|
40564
40642
|
const firstSentence = collapsed.split(/(?<=\.)\s/)[0] || collapsed;
|
|
@@ -40653,7 +40731,10 @@ const checkSupplyChain = (input) => gen(function* () {
|
|
|
40653
40731
|
* outage.
|
|
40654
40732
|
*/
|
|
40655
40733
|
var SupplyChain = class SupplyChain extends Service()("react-doctor/SupplyChain") {
|
|
40656
|
-
static layerNode = succeed$3(SupplyChain, SupplyChain.of({ run: (input) => unwrap(
|
|
40734
|
+
static layerNode = succeed$3(SupplyChain, SupplyChain.of({ run: (input) => unwrap(flatMap$2(SupplyChainOverlapTimeoutMs, (totalTimeoutMs) => checkSupplyChain({
|
|
40735
|
+
...input,
|
|
40736
|
+
totalTimeoutMs
|
|
40737
|
+
})).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)), withSpan("SupplyChain.run"))) }));
|
|
40657
40738
|
static layerOf = (diagnostics) => succeed$3(SupplyChain, SupplyChain.of({ run: () => fromIterable$1(diagnostics) }));
|
|
40658
40739
|
};
|
|
40659
40740
|
const getObjectProperty = (value, propertyName) => {
|
|
@@ -40801,10 +40882,11 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40801
40882
|
...checkExpoProject(scanDirectory, project),
|
|
40802
40883
|
...checkReactNativeProject(scanDirectory, project)
|
|
40803
40884
|
])));
|
|
40804
|
-
const
|
|
40885
|
+
const securityScanFailedRef = yield* make$13(false);
|
|
40886
|
+
const securityScanFiber = yield* forkChild(runCollect(applyPerElementPipeline(isDiffMode ? empty$4 : unwrap(tryPromise(() => checkSecurityScanCooperative(scanDirectory, {
|
|
40805
40887
|
project,
|
|
40806
40888
|
ignoredTags: input.ignoredTags
|
|
40807
|
-
})).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)))))).pipe(withSpan("SecurityScan.run")));
|
|
40889
|
+
})).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)), catch_$1(() => set(securityScanFailedRef, true).pipe(as(empty$4))))))).pipe(withSpan("SecurityScan.run")));
|
|
40808
40890
|
const shouldRunSupplyChain = !isDiffMode || (input.supplyChainManifestChanged ?? false);
|
|
40809
40891
|
const supplyChainOverlapTimeout = yield* SupplyChainOverlapTimeoutMs;
|
|
40810
40892
|
const supplyChainFiber = yield* forkChild(shouldRunSupplyChain ? runCollect(applyPerElementPipeline(supplyChainService.run({
|
|
@@ -40842,7 +40924,6 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40842
40924
|
const lintConcurrency = deadCodeParseConcurrency === void 0 ? scanConcurrency : Math.max(1, scanConcurrency - deadCodeParseConcurrency);
|
|
40843
40925
|
const buildCollectDeadCode = (deadCodeTimeout) => runCollect(applyPerElementPipeline(deadCodeService.run({
|
|
40844
40926
|
rootDirectory: scanDirectory,
|
|
40845
|
-
userConfig: resolvedConfig.config,
|
|
40846
40927
|
parseConcurrency: deadCodeParseConcurrency,
|
|
40847
40928
|
workerTimeoutMs: deadCodeTimeout.workerTimeoutMs
|
|
40848
40929
|
}).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
|
|
@@ -40970,6 +41051,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40970
41051
|
metadata: scoreMetadata
|
|
40971
41052
|
});
|
|
40972
41053
|
const lintPartialFailures = yield* get$2(partialFailuresRef);
|
|
41054
|
+
const securityScanFailed = yield* get$2(securityScanFailedRef);
|
|
40973
41055
|
return {
|
|
40974
41056
|
project,
|
|
40975
41057
|
userConfig: resolvedConfig.config,
|
|
@@ -40990,8 +41072,10 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40990
41072
|
scanElapsedMilliseconds,
|
|
40991
41073
|
scanConcurrency,
|
|
40992
41074
|
supplyChainOverlapTimedOut: supplyChainResult.timedOut,
|
|
41075
|
+
securityScanFailed,
|
|
40993
41076
|
lintCacheHitFileCount,
|
|
40994
|
-
lintCacheTotalFileCount
|
|
41077
|
+
lintCacheTotalFileCount,
|
|
41078
|
+
suppressedRuleCounts: transform.summarizeSuppressions()
|
|
40995
41079
|
};
|
|
40996
41080
|
}).pipe(withSpan("runInspect", { attributes: {
|
|
40997
41081
|
"inspect.directory": input.directory,
|
|
@@ -43029,6 +43113,8 @@ const METRIC = {
|
|
|
43029
43113
|
scanCheckSkipped: "scan.check_skipped",
|
|
43030
43114
|
baselineDegraded: "baseline.degraded",
|
|
43031
43115
|
ruleFired: "rule.fired",
|
|
43116
|
+
ruleDisabled: "rule.disabled",
|
|
43117
|
+
ruleSuppressed: "rule.suppressed",
|
|
43032
43118
|
lintFailed: "lint.failed",
|
|
43033
43119
|
deadCodeFailed: "deadcode.failed",
|
|
43034
43120
|
scoreUnavailable: "score.unavailable",
|
|
@@ -43041,13 +43127,17 @@ const METRIC = {
|
|
|
43041
43127
|
installWorkflow: "install.workflow",
|
|
43042
43128
|
installAgentHooks: "install.agent_hooks",
|
|
43043
43129
|
installDependency: "install.dependency",
|
|
43130
|
+
ciScaffolded: "ci.scaffolded",
|
|
43131
|
+
ciUpgraded: "ci.upgraded",
|
|
43132
|
+
ciConfigured: "ci.configured",
|
|
43044
43133
|
rulesChanged: "rules.changed",
|
|
43045
43134
|
rulesQueried: "rules.queried",
|
|
43046
43135
|
lspSessionStarted: "lsp.session.started",
|
|
43047
43136
|
lspScanCompleted: "lsp.scan.completed",
|
|
43048
43137
|
lspScanDuration: "lsp.scan.duration",
|
|
43049
43138
|
lspScanDiagnostics: "lsp.scan.diagnostics",
|
|
43050
|
-
aiTrainingWarningShown: "ai.training.warning_shown"
|
|
43139
|
+
aiTrainingWarningShown: "ai.training.warning_shown",
|
|
43140
|
+
jsonOutUsed: "json.out_used"
|
|
43051
43141
|
};
|
|
43052
43142
|
//#endregion
|
|
43053
43143
|
//#region src/cli/utils/to-category-key.ts
|
|
@@ -43364,5 +43454,5 @@ const startLanguageServer = () => {
|
|
|
43364
43454
|
};
|
|
43365
43455
|
//#endregion
|
|
43366
43456
|
export { startLanguageServer };
|
|
43367
|
-
!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]="
|
|
43368
|
-
//# debugId=
|
|
43457
|
+
!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]="f7d16df6-f4da-5a95-ab64-acd6d3a0f5ca")}catch(e){}}();
|
|
43458
|
+
//# debugId=f7d16df6-f4da-5a95-ab64-acd6d3a0f5ca
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-doctor",
|
|
3
|
-
"version": "0.5.8-dev.
|
|
3
|
+
"version": "0.5.8-dev.88a5c3c",
|
|
4
4
|
"description": "Your agent writes bad React. This catches it",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"accessibility",
|
|
@@ -60,12 +60,13 @@
|
|
|
60
60
|
"magicast": "^0.5.3",
|
|
61
61
|
"oxlint": ">=1.66.0 <1.67.0",
|
|
62
62
|
"prompts": "^2.4.2",
|
|
63
|
-
"typescript": ">=5.0.4 <
|
|
63
|
+
"typescript": ">=5.0.4 <6",
|
|
64
64
|
"vscode-languageserver": "^9.0.1",
|
|
65
65
|
"vscode-languageserver-textdocument": "^1.0.12",
|
|
66
66
|
"vscode-uri": "^3.1.0",
|
|
67
|
+
"yaml": "^2.9.0",
|
|
67
68
|
"deslop-js": "0.5.8",
|
|
68
|
-
"oxlint-plugin-react-doctor": "0.5.8-dev.
|
|
69
|
+
"oxlint-plugin-react-doctor": "0.5.8-dev.88a5c3c"
|
|
69
70
|
},
|
|
70
71
|
"devDependencies": {
|
|
71
72
|
"@types/babel__code-frame": "^7.27.0",
|
|
@@ -73,9 +74,9 @@
|
|
|
73
74
|
"@xterm/headless": "^6.0.0",
|
|
74
75
|
"commander": "^14.0.3",
|
|
75
76
|
"ora": "^9.4.0",
|
|
76
|
-
"@react-doctor/api": "0.5.8",
|
|
77
77
|
"@react-doctor/core": "0.5.8",
|
|
78
|
-
"@react-doctor/language-server": "0.5.8"
|
|
78
|
+
"@react-doctor/language-server": "0.5.8",
|
|
79
|
+
"@react-doctor/api": "0.5.8"
|
|
79
80
|
},
|
|
80
81
|
"engines": {
|
|
81
82
|
"node": "^20.19.0 || >=22.13.0"
|