react-doctor 0.2.13-dev.adbec28 → 0.2.14-dev.3ceb748
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 +12 -1
- package/dist/{cli-logger-CSZagq1E.js → cli-logger-BgVL1vBI.js} +176 -18
- package/dist/cli.js +42 -14
- package/dist/index.d.ts +9 -3
- package/dist/index.js +173 -16
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<picture>
|
|
2
2
|
<source media="(prefers-color-scheme: dark)" srcset="./assets/react-doctor-readme-logo-dark.svg">
|
|
3
3
|
<source media="(prefers-color-scheme: light)" srcset="./assets/react-doctor-readme-logo-light.svg">
|
|
4
|
-
<img alt="React Doctor" src="./assets/react-doctor-readme-logo-light.svg" width="
|
|
4
|
+
<img alt="React Doctor" src="./assets/react-doctor-readme-logo-light.svg" width="134" height="36">
|
|
5
5
|
</picture>
|
|
6
6
|
|
|
7
7
|
[](https://npmjs.com/package/react-doctor)
|
|
@@ -71,6 +71,17 @@ React Doctor scans the files changed in the pull request, emits inline annotatio
|
|
|
71
71
|
|
|
72
72
|
[Add GitHub Action →](https://github.com/marketplace/actions/react-doctor)
|
|
73
73
|
|
|
74
|
+
### 4. Configure rules in `react-doctor.config.json`
|
|
75
|
+
|
|
76
|
+
Point the `$schema` key at `https://react.doctor/schema/config.json` to get autocomplete, hover docs, and typo warnings for every option in any editor that understands JSON Schema.
|
|
77
|
+
|
|
78
|
+
```jsonc
|
|
79
|
+
{
|
|
80
|
+
"$schema": "https://react.doctor/schema/config.json",
|
|
81
|
+
"lint": true,
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
74
85
|
## Contributing
|
|
75
86
|
|
|
76
87
|
[Issues welcome!](https://github.com/millionco/react-doctor/issues)
|
|
@@ -3189,6 +3189,7 @@ const isTailwindAtLeast = (detected, required) => {
|
|
|
3189
3189
|
const JSX_FILE_PATTERN = /\.(tsx|jsx)$/;
|
|
3190
3190
|
const MILLISECONDS_PER_SECOND = 1e3;
|
|
3191
3191
|
const SCORE_API_URL = "https://www.react.doctor/api/score";
|
|
3192
|
+
const ENTERPRISE_CONTACT_URL = "https://react.doctor/enterprise";
|
|
3192
3193
|
const SHARE_BASE_URL = "https://www.react.doctor/share";
|
|
3193
3194
|
const PROMPTS_RULES_BASE_URL = "https://www.react.doctor/prompts/rules";
|
|
3194
3195
|
const FETCH_TIMEOUT_MS = 1e4;
|
|
@@ -5909,6 +5910,149 @@ const appendReanimatedSharedValueHint = (help, rule, project) => {
|
|
|
5909
5910
|
if (!help) return REANIMATED_SHARED_VALUE_HINT;
|
|
5910
5911
|
return `${help}\n\n${REANIMATED_SHARED_VALUE_HINT}`;
|
|
5911
5912
|
};
|
|
5913
|
+
const REDACTED_PLACEHOLDER = "<redacted>";
|
|
5914
|
+
const KEEP_PREFIX = `$1${REDACTED_PLACEHOLDER}`;
|
|
5915
|
+
const KNOWN_SECRET_RULES = [
|
|
5916
|
+
{
|
|
5917
|
+
pattern: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g,
|
|
5918
|
+
replacement: REDACTED_PLACEHOLDER
|
|
5919
|
+
},
|
|
5920
|
+
{
|
|
5921
|
+
pattern: /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/g,
|
|
5922
|
+
replacement: REDACTED_PLACEHOLDER
|
|
5923
|
+
},
|
|
5924
|
+
{
|
|
5925
|
+
pattern: /(?<=:\/\/)[^\s/:@]+:[^\s/@]+(?=@)/g,
|
|
5926
|
+
replacement: REDACTED_PLACEHOLDER
|
|
5927
|
+
},
|
|
5928
|
+
{
|
|
5929
|
+
pattern: /\b(AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA|A3T[A-Z0-9])[0-9A-Z]{16,}/g,
|
|
5930
|
+
replacement: KEEP_PREFIX
|
|
5931
|
+
},
|
|
5932
|
+
{
|
|
5933
|
+
pattern: /\b(gh[pousr]_)[A-Za-z0-9]{36,}/g,
|
|
5934
|
+
replacement: KEEP_PREFIX
|
|
5935
|
+
},
|
|
5936
|
+
{
|
|
5937
|
+
pattern: /\b(github_pat_)[A-Za-z0-9_]{22,}/g,
|
|
5938
|
+
replacement: KEEP_PREFIX
|
|
5939
|
+
},
|
|
5940
|
+
{
|
|
5941
|
+
pattern: /\b(glpat-)[A-Za-z0-9_-]{20,}/g,
|
|
5942
|
+
replacement: KEEP_PREFIX
|
|
5943
|
+
},
|
|
5944
|
+
{
|
|
5945
|
+
pattern: /\b(xox[baprs]-)[A-Za-z0-9-]{10,}/g,
|
|
5946
|
+
replacement: KEEP_PREFIX
|
|
5947
|
+
},
|
|
5948
|
+
{
|
|
5949
|
+
pattern: /(?<=hooks\.slack\.com\/services\/)[A-Za-z0-9/+_-]{20,}/g,
|
|
5950
|
+
replacement: REDACTED_PLACEHOLDER
|
|
5951
|
+
},
|
|
5952
|
+
{
|
|
5953
|
+
pattern: /\b((?:sk|rk)_(?:live|test)_)[0-9A-Za-z]{10,}/g,
|
|
5954
|
+
replacement: KEEP_PREFIX
|
|
5955
|
+
},
|
|
5956
|
+
{
|
|
5957
|
+
pattern: /\b(sk-(?:proj-|ant-)?)[A-Za-z0-9_-]{20,}/g,
|
|
5958
|
+
replacement: KEEP_PREFIX
|
|
5959
|
+
},
|
|
5960
|
+
{
|
|
5961
|
+
pattern: /\b(AIza)[0-9A-Za-z_-]{35,}/g,
|
|
5962
|
+
replacement: KEEP_PREFIX
|
|
5963
|
+
},
|
|
5964
|
+
{
|
|
5965
|
+
pattern: /\b(ya29\.)[0-9A-Za-z_-]{20,}/g,
|
|
5966
|
+
replacement: KEEP_PREFIX
|
|
5967
|
+
},
|
|
5968
|
+
{
|
|
5969
|
+
pattern: /\b(npm_)[A-Za-z0-9]{36,}/g,
|
|
5970
|
+
replacement: KEEP_PREFIX
|
|
5971
|
+
},
|
|
5972
|
+
{
|
|
5973
|
+
pattern: /\b(SG\.)[A-Za-z0-9_-]{22,}\.[A-Za-z0-9_-]{43,}/g,
|
|
5974
|
+
replacement: KEEP_PREFIX
|
|
5975
|
+
},
|
|
5976
|
+
{
|
|
5977
|
+
pattern: /\b(SK)[0-9a-fA-F]{32,}/g,
|
|
5978
|
+
replacement: KEEP_PREFIX
|
|
5979
|
+
},
|
|
5980
|
+
{
|
|
5981
|
+
pattern: /\b(dop_v1_)[a-f0-9]{64,}/g,
|
|
5982
|
+
replacement: KEEP_PREFIX
|
|
5983
|
+
},
|
|
5984
|
+
{
|
|
5985
|
+
pattern: /\b(shp(?:at|ca|pa|ss)_)[a-fA-F0-9]{32,}/g,
|
|
5986
|
+
replacement: KEEP_PREFIX
|
|
5987
|
+
},
|
|
5988
|
+
{
|
|
5989
|
+
pattern: /\b(sq0[a-z]{3}-)[0-9A-Za-z_-]{22,}/g,
|
|
5990
|
+
replacement: KEEP_PREFIX
|
|
5991
|
+
},
|
|
5992
|
+
{
|
|
5993
|
+
pattern: /\b([0-9]{8,10}:AA)[0-9A-Za-z_-]{32,}/g,
|
|
5994
|
+
replacement: KEEP_PREFIX
|
|
5995
|
+
},
|
|
5996
|
+
{
|
|
5997
|
+
pattern: /(?<=\bBearer\s)[A-Za-z0-9._~+/=-]{16,}/g,
|
|
5998
|
+
replacement: REDACTED_PLACEHOLDER
|
|
5999
|
+
},
|
|
6000
|
+
{
|
|
6001
|
+
pattern: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g,
|
|
6002
|
+
replacement: REDACTED_PLACEHOLDER
|
|
6003
|
+
}
|
|
6004
|
+
];
|
|
6005
|
+
const CANDIDATE_TOKEN_PATTERN = /[A-Za-z0-9_][A-Za-z0-9_-]*/g;
|
|
6006
|
+
const GIT_OBJECT_ID_PATTERN = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
|
|
6007
|
+
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
6008
|
+
const HAS_LETTER_PATTERN = /[A-Za-z]/;
|
|
6009
|
+
const HAS_DIGIT_PATTERN = /[0-9]/;
|
|
6010
|
+
const shannonEntropyBits = (value) => {
|
|
6011
|
+
const counts = /* @__PURE__ */ new Map();
|
|
6012
|
+
for (const char of value) counts.set(char, (counts.get(char) ?? 0) + 1);
|
|
6013
|
+
let bits = 0;
|
|
6014
|
+
for (const count of counts.values()) {
|
|
6015
|
+
const probability = count / value.length;
|
|
6016
|
+
bits -= probability * Math.log2(probability);
|
|
6017
|
+
}
|
|
6018
|
+
return bits;
|
|
6019
|
+
};
|
|
6020
|
+
const looksLikeHighEntropySecret = (token) => {
|
|
6021
|
+
if (token.length < 32) return false;
|
|
6022
|
+
if (!HAS_LETTER_PATTERN.test(token) || !HAS_DIGIT_PATTERN.test(token)) return false;
|
|
6023
|
+
if (GIT_OBJECT_ID_PATTERN.test(token) || UUID_PATTERN.test(token)) return false;
|
|
6024
|
+
return shannonEntropyBits(token) >= 3;
|
|
6025
|
+
};
|
|
6026
|
+
const redactHighEntropyTokens = (text) => text.replace(CANDIDATE_TOKEN_PATTERN, (token) => looksLikeHighEntropySecret(token) ? REDACTED_PLACEHOLDER : token);
|
|
6027
|
+
/**
|
|
6028
|
+
* Masks API keys, tokens, private keys, credentialed URLs, and emails
|
|
6029
|
+
* found anywhere inside a free-text string, returning the scrubbed text.
|
|
6030
|
+
* Applied to every diagnostic's `message` / `help` at construction time
|
|
6031
|
+
* so secrets never reach the terminal, the JSON report, or the score
|
|
6032
|
+
* API — react-doctor must never echo or transmit a user's secrets.
|
|
6033
|
+
*
|
|
6034
|
+
* Provider tokens keep their non-secret, type-identifying prefix (e.g.
|
|
6035
|
+
* `sk_live_<redacted>`, `ghp_<redacted>`, `AKIA<redacted>`) so the leaked
|
|
6036
|
+
* credential's type stays visible; structural or unknown-format secrets
|
|
6037
|
+
* with no meaningful prefix are masked whole.
|
|
6038
|
+
*
|
|
6039
|
+
* Runs the high-precision known-shape detectors first, then a generic
|
|
6040
|
+
* entropy-gated sweep for unknown-format secrets. Idempotent: the inert
|
|
6041
|
+
* `<redacted>` placeholder matches none of the detectors and is too
|
|
6042
|
+
* short for the generic sweep, so re-running leaves the text unchanged.
|
|
6043
|
+
*
|
|
6044
|
+
* Accepts `unknown` on purpose: callers feed it diagnostic `message` /
|
|
6045
|
+
* `help` that originate from oxlint JSON, which is only shape-checked at
|
|
6046
|
+
* the top level (the per-field `string` types are assumed, not validated).
|
|
6047
|
+
* A malformed non-string value returns `""` instead of throwing on
|
|
6048
|
+
* `.replace`, so one bad diagnostic can't abort parsing the whole batch.
|
|
6049
|
+
*/
|
|
6050
|
+
const redactSensitiveText = (text) => {
|
|
6051
|
+
if (typeof text !== "string" || text === "") return "";
|
|
6052
|
+
let redacted = text;
|
|
6053
|
+
for (const rule of KNOWN_SECRET_RULES) redacted = redacted.replace(rule.pattern, rule.replacement);
|
|
6054
|
+
return redactHighEntropyTokens(redacted);
|
|
6055
|
+
};
|
|
5912
6056
|
const REACT_MODULE_SOURCE = "react";
|
|
5913
6057
|
const REQUIRE_IDENTIFIER = "require";
|
|
5914
6058
|
const USE_IDENTIFIER = "use";
|
|
@@ -6230,6 +6374,13 @@ const getRuleRecommendation = (ruleName, project) => {
|
|
|
6230
6374
|
};
|
|
6231
6375
|
const getRuleCategory = (ruleName) => reactDoctorPlugin.rules[ruleName]?.category;
|
|
6232
6376
|
const cleanDiagnosticMessage = (message, help, plugin, rule, project) => {
|
|
6377
|
+
const cleaned = resolveCleanedDiagnostic(typeof message === "string" ? message : "", typeof help === "string" ? help : "", plugin, rule, project);
|
|
6378
|
+
return {
|
|
6379
|
+
message: redactSensitiveText(cleaned.message),
|
|
6380
|
+
help: redactSensitiveText(cleaned.help)
|
|
6381
|
+
};
|
|
6382
|
+
};
|
|
6383
|
+
const resolveCleanedDiagnostic = (message, help, plugin, rule, project) => {
|
|
6233
6384
|
if (plugin === "react-hooks-js") return {
|
|
6234
6385
|
message: REACT_COMPILER_MESSAGE,
|
|
6235
6386
|
help: appendReanimatedSharedValueHint(message.replace(FILEPATH_WITH_LOCATION_PATTERN, "").trim() || help, rule, project)
|
|
@@ -6434,11 +6585,14 @@ const spawnLintBatches = async (input) => {
|
|
|
6434
6585
|
onFileProgress(scannedFileCount + batchFileIndex, totalFileCount);
|
|
6435
6586
|
}
|
|
6436
6587
|
}, 50) : null;
|
|
6437
|
-
|
|
6438
|
-
|
|
6439
|
-
|
|
6440
|
-
|
|
6441
|
-
|
|
6588
|
+
try {
|
|
6589
|
+
const batchDiagnostics = await spawnLintBatch(batch);
|
|
6590
|
+
allDiagnostics.push(...batchDiagnostics);
|
|
6591
|
+
scannedFileCount += batch.length;
|
|
6592
|
+
onFileProgress?.(scannedFileCount, totalFileCount);
|
|
6593
|
+
} finally {
|
|
6594
|
+
if (progressInterval !== null) clearInterval(progressInterval);
|
|
6595
|
+
}
|
|
6442
6596
|
}
|
|
6443
6597
|
if (droppedFiles.length > 0 && onPartialFailure) {
|
|
6444
6598
|
const previewFiles = droppedFiles.slice(0, 3).join(", ");
|
|
@@ -6787,17 +6941,21 @@ var Reporter = class Reporter extends Context.Service()("react-doctor/Reporter")
|
|
|
6787
6941
|
});
|
|
6788
6942
|
}));
|
|
6789
6943
|
};
|
|
6790
|
-
const
|
|
6791
|
-
|
|
6792
|
-
|
|
6793
|
-
|
|
6794
|
-
|
|
6795
|
-
|
|
6796
|
-
|
|
6797
|
-
|
|
6798
|
-
|
|
6799
|
-
|
|
6800
|
-
|
|
6944
|
+
const RulePrioritySchema = Schema.Struct({
|
|
6945
|
+
priority: Schema.NullOr(Schema.Number),
|
|
6946
|
+
tier: Schema.Literals([
|
|
6947
|
+
"P0",
|
|
6948
|
+
"P1",
|
|
6949
|
+
"P2",
|
|
6950
|
+
"P3"
|
|
6951
|
+
])
|
|
6952
|
+
});
|
|
6953
|
+
const ScoreApiResponseSchema = Schema.Struct({
|
|
6954
|
+
score: Schema.Number,
|
|
6955
|
+
label: Schema.String,
|
|
6956
|
+
rules: Schema.optional(Schema.Record(Schema.String, RulePrioritySchema))
|
|
6957
|
+
});
|
|
6958
|
+
const parseScoreResult = (value) => Option.getOrNull(Schema.decodeUnknownOption(ScoreApiResponseSchema)(value));
|
|
6801
6959
|
const stripFilePaths = (diagnostics) => diagnostics.map(({ filePath: _filePath, ...rest }) => rest);
|
|
6802
6960
|
const isAbortError = (error) => error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError");
|
|
6803
6961
|
const describeFailure = (error) => {
|
|
@@ -7559,6 +7717,6 @@ const cliLogger = {
|
|
|
7559
7717
|
}
|
|
7560
7718
|
};
|
|
7561
7719
|
//#endregion
|
|
7562
|
-
export {
|
|
7720
|
+
export { highlighter as A, discoverReactSubprojects as C, formatReactDoctorError as D, formatErrorChain as E, resolveScanTarget as F, restoreLegacyThrow as I, runInspect as L, isReactDoctorError as M, layerOtlp as N, getDiffInfo as O, listWorkspacePackages as P, toRelativePath as R, buildRulePromptUrl as S, filterSourceFiles as T, SKILL_NAME as _, DeadCode as a, buildJsonReport as b, Git as c, NodeResolver as d, OXLINT_NODE_REQUIREMENT as f, SHARE_BASE_URL as g, Reporter as h, Config as i, isMonorepoRoot as j, groupBy as k, LintPartialFailures as l, Project as m, cli_logger_exports as n, ENTERPRISE_CONTACT_URL as o, Progress as p, CANONICAL_GITHUB_URL as r, Files as s, cliLogger as t, Linter as u, Score as v, filterDiagnosticsForSurface as w, buildJsonReportError as x, StagedFiles as y };
|
|
7563
7721
|
|
|
7564
|
-
//# sourceMappingURL=cli-logger-
|
|
7722
|
+
//# sourceMappingURL=cli-logger-BgVL1vBI.js.map
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { i as __toESM, n as __exportAll, r as __require, t as __commonJSMin } from "./rolldown-runtime-uZX_iqCz.js";
|
|
2
|
-
import { A as
|
|
2
|
+
import { A as highlighter, C as discoverReactSubprojects, D as formatReactDoctorError, E as formatErrorChain, F as resolveScanTarget, I as restoreLegacyThrow, L as runInspect, M as isReactDoctorError, N as layerOtlp, O as getDiffInfo, P as listWorkspacePackages, R as toRelativePath, S as buildRulePromptUrl, T as filterSourceFiles, _ as SKILL_NAME, a as DeadCode, b as buildJsonReport, c as Git, d as NodeResolver, f as OXLINT_NODE_REQUIREMENT, g as SHARE_BASE_URL, h as Reporter, i as Config, j as isMonorepoRoot, k as groupBy, l as LintPartialFailures, m as Project, o as ENTERPRISE_CONTACT_URL, p as Progress, r as CANONICAL_GITHUB_URL, s as Files, t as cliLogger, u as Linter, v as Score, w as filterDiagnosticsForSurface, x as buildJsonReportError, y as StagedFiles } from "./cli-logger-BgVL1vBI.js";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { execFileSync, execSync } from "node:child_process";
|
|
5
5
|
import path, { join } from "node:path";
|
|
@@ -6161,6 +6161,12 @@ const makeNoopConsole = () => ({
|
|
|
6161
6161
|
warn: () => {}
|
|
6162
6162
|
});
|
|
6163
6163
|
//#endregion
|
|
6164
|
+
//#region src/cli/utils/build-no-score-message.ts
|
|
6165
|
+
const ENTERPRISE_CONTACT_HINT = `Want something custom to your company? Contact us at ${ENTERPRISE_CONTACT_URL}.`;
|
|
6166
|
+
const buildNoScoreMessage = (isScoreDisabled) => {
|
|
6167
|
+
return `${isScoreDisabled ? "Score disabled by --no-score." : "Score unavailable (could not reach the score API)."} ${ENTERPRISE_CONTACT_HINT}`;
|
|
6168
|
+
};
|
|
6169
|
+
//#endregion
|
|
6164
6170
|
//#region src/cli/utils/render-agent-guidance.ts
|
|
6165
6171
|
const AGENT_GUIDANCE_LINES = [
|
|
6166
6172
|
"Treat React Doctor diagnostics as starting hypotheses. Read the relevant code before confirming or suppressing each finding.",
|
|
@@ -6192,7 +6198,22 @@ const SEVERITY_ORDER = {
|
|
|
6192
6198
|
warning: 1
|
|
6193
6199
|
};
|
|
6194
6200
|
const colorizeBySeverity = (text, severity) => severity === "error" ? highlighter.error(text) : highlighter.warn(text);
|
|
6195
|
-
const
|
|
6201
|
+
const buildRulePriorityMap = (scores) => {
|
|
6202
|
+
const rulePriority = /* @__PURE__ */ new Map();
|
|
6203
|
+
for (const score of scores) {
|
|
6204
|
+
if (!score?.rules) continue;
|
|
6205
|
+
for (const [ruleKey, info] of Object.entries(score.rules)) if (typeof info.priority === "number") rulePriority.set(ruleKey, info.priority);
|
|
6206
|
+
}
|
|
6207
|
+
return rulePriority;
|
|
6208
|
+
};
|
|
6209
|
+
const effectivePriority = (ruleKey, diagnostics, rulePriority) => {
|
|
6210
|
+
const known = rulePriority?.get(ruleKey);
|
|
6211
|
+
if (known !== void 0) return known;
|
|
6212
|
+
return diagnostics[0].severity === "error" ? 55 : 35;
|
|
6213
|
+
};
|
|
6214
|
+
const sortByImportance = (diagnosticGroups, rulePriority) => diagnosticGroups.toSorted(([ruleKeyA, diagnosticsA], [ruleKeyB, diagnosticsB]) => {
|
|
6215
|
+
const priorityDelta = effectivePriority(ruleKeyB, diagnosticsB, rulePriority) - effectivePriority(ruleKeyA, diagnosticsA, rulePriority);
|
|
6216
|
+
if (priorityDelta !== 0) return priorityDelta;
|
|
6196
6217
|
const severityDelta = SEVERITY_ORDER[diagnosticsA[0].severity] - SEVERITY_ORDER[diagnosticsB[0].severity];
|
|
6197
6218
|
if (severityDelta !== 0) return severityDelta;
|
|
6198
6219
|
return diagnosticsB.length - diagnosticsA.length;
|
|
@@ -6229,14 +6250,20 @@ const buildCompactRuleGroupLine = (ruleKey, ruleDiagnostics, ruleNameColumnWidth
|
|
|
6229
6250
|
return ` ${icon} ${siteCountBadge.length > 0 ? colorizeBySeverity(padRuleNameToColumn(ruleKey, ruleNameColumnWidth), firstDiagnostic.severity) : colorizeBySeverity(ruleKey, firstDiagnostic.severity)}${siteCountBadge.length > 0 ? ` ${highlighter.gray(siteCountBadge)}` : ""}`;
|
|
6230
6251
|
};
|
|
6231
6252
|
const getWorstSeverity = (diagnostics) => diagnostics.some((diagnostic) => diagnostic.severity === "error") ? "error" : "warning";
|
|
6232
|
-
const
|
|
6253
|
+
const categoryTopPriority = (categoryGroup, rulePriority) => {
|
|
6254
|
+
const [topRuleKey, topDiagnostics] = categoryGroup.ruleGroups[0];
|
|
6255
|
+
return effectivePriority(topRuleKey, topDiagnostics, rulePriority);
|
|
6256
|
+
};
|
|
6257
|
+
const buildCategoryDiagnosticGroups = (diagnostics, rulePriority) => {
|
|
6233
6258
|
return [...groupBy(diagnostics, (diagnostic) => diagnostic.category).entries()].map(([category, categoryDiagnostics]) => {
|
|
6234
6259
|
return {
|
|
6235
6260
|
category,
|
|
6236
6261
|
diagnostics: categoryDiagnostics,
|
|
6237
|
-
ruleGroups: sortByImportance([...groupBy(categoryDiagnostics, (diagnostic) => `${diagnostic.plugin}/${diagnostic.rule}`).entries()])
|
|
6262
|
+
ruleGroups: sortByImportance([...groupBy(categoryDiagnostics, (diagnostic) => `${diagnostic.plugin}/${diagnostic.rule}`).entries()], rulePriority)
|
|
6238
6263
|
};
|
|
6239
6264
|
}).toSorted((categoryGroupA, categoryGroupB) => {
|
|
6265
|
+
const priorityDelta = categoryTopPriority(categoryGroupB, rulePriority) - categoryTopPriority(categoryGroupA, rulePriority);
|
|
6266
|
+
if (priorityDelta !== 0) return priorityDelta;
|
|
6240
6267
|
const severityDelta = SEVERITY_ORDER[getWorstSeverity(categoryGroupA.diagnostics)] - SEVERITY_ORDER[getWorstSeverity(categoryGroupB.diagnostics)];
|
|
6241
6268
|
if (severityDelta !== 0) return severityDelta;
|
|
6242
6269
|
if (categoryGroupA.diagnostics.length !== categoryGroupB.diagnostics.length) return categoryGroupB.diagnostics.length - categoryGroupA.diagnostics.length;
|
|
@@ -6267,8 +6294,8 @@ const buildVerboseRuleGroupLines = (ruleKey, ruleDiagnostics, ruleNameColumnWidt
|
|
|
6267
6294
|
lines.push("");
|
|
6268
6295
|
return lines;
|
|
6269
6296
|
};
|
|
6270
|
-
const buildDefaultDiagnosticsLines = (diagnostics) => {
|
|
6271
|
-
const categoryGroups = buildCategoryDiagnosticGroups(diagnostics);
|
|
6297
|
+
const buildDefaultDiagnosticsLines = (diagnostics, rulePriority) => {
|
|
6298
|
+
const categoryGroups = buildCategoryDiagnosticGroups(diagnostics, rulePriority);
|
|
6272
6299
|
const lines = [];
|
|
6273
6300
|
for (const categoryGroup of categoryGroups) lines.push(buildCompactCategoryLine(categoryGroup));
|
|
6274
6301
|
lines.push("");
|
|
@@ -6280,11 +6307,11 @@ const buildDefaultDiagnosticsLines = (diagnostics) => {
|
|
|
6280
6307
|
* single Effect.forEach over Console.log so failures or fiber
|
|
6281
6308
|
* interruption produce predictable partial output.
|
|
6282
6309
|
*/
|
|
6283
|
-
const printDiagnostics = (diagnostics, isVerbose, rootDirectory) => Effect.gen(function* () {
|
|
6310
|
+
const printDiagnostics = (diagnostics, isVerbose, rootDirectory, rulePriority) => Effect.gen(function* () {
|
|
6284
6311
|
let lines;
|
|
6285
|
-
if (!isVerbose) lines = buildDefaultDiagnosticsLines(diagnostics);
|
|
6312
|
+
if (!isVerbose) lines = buildDefaultDiagnosticsLines(diagnostics, rulePriority);
|
|
6286
6313
|
else {
|
|
6287
|
-
const sortedRuleGroups = sortByImportance([...groupBy(diagnostics, (diagnostic) => `${diagnostic.plugin}/${diagnostic.rule}`).entries()]);
|
|
6314
|
+
const sortedRuleGroups = sortByImportance([...groupBy(diagnostics, (diagnostic) => `${diagnostic.plugin}/${diagnostic.rule}`).entries()], rulePriority);
|
|
6288
6315
|
const ruleNameColumnWidth = computeRuleNameColumnWidth(sortedRuleGroups.map(([ruleKey]) => ruleKey));
|
|
6289
6316
|
lines = sortedRuleGroups.flatMap(([ruleKey, ruleDiagnostics]) => buildVerboseRuleGroupLines(ruleKey, ruleDiagnostics, ruleNameColumnWidth));
|
|
6290
6317
|
}
|
|
@@ -6580,6 +6607,7 @@ const shouldSelectAllChoices = (choiceStates) => {
|
|
|
6580
6607
|
//#endregion
|
|
6581
6608
|
//#region src/cli/utils/unref-stdin.ts
|
|
6582
6609
|
const unrefStdin = () => {
|
|
6610
|
+
if (process.stdin.isTTY) return;
|
|
6583
6611
|
process.stdin.unref?.();
|
|
6584
6612
|
};
|
|
6585
6613
|
//#endregion
|
|
@@ -6679,7 +6707,7 @@ const resolveOxlintNodeEffect = (isLintEnabled, isQuiet) => Effect.gen(function*
|
|
|
6679
6707
|
const resolveOxlintNode = (isLintEnabled, isQuiet) => Effect.runPromise(resolveOxlintNodeEffect(isLintEnabled, isQuiet).pipe(Effect.provide(NodeResolver.layerNode)));
|
|
6680
6708
|
//#endregion
|
|
6681
6709
|
//#region src/cli/utils/version.ts
|
|
6682
|
-
const VERSION = "0.2.
|
|
6710
|
+
const VERSION = "0.2.14-dev.3ceb748";
|
|
6683
6711
|
//#endregion
|
|
6684
6712
|
//#region src/inspect.ts
|
|
6685
6713
|
const silentConsole = makeNoopConsole();
|
|
@@ -6803,7 +6831,7 @@ const finalizeAndRender = (input) => Effect.gen(function* () {
|
|
|
6803
6831
|
if (didLintFail) skippedChecks.push("lint");
|
|
6804
6832
|
if (didDeadCodeFail) skippedChecks.push("dead-code");
|
|
6805
6833
|
const hasSkippedChecks = skippedChecks.length > 0;
|
|
6806
|
-
const noScoreMessage = options.noScore
|
|
6834
|
+
const noScoreMessage = buildNoScoreMessage(options.noScore);
|
|
6807
6835
|
const skippedCheckReasons = {};
|
|
6808
6836
|
if (didLintFail && lintFailureReason !== null) skippedCheckReasons.lint = lintFailureReason;
|
|
6809
6837
|
else if (lintPartialFailures.length > 0) skippedCheckReasons["lint:partial"] = lintPartialFailures.join("; ");
|
|
@@ -6840,7 +6868,7 @@ const finalizeAndRender = (input) => Effect.gen(function* () {
|
|
|
6840
6868
|
return buildResult();
|
|
6841
6869
|
}
|
|
6842
6870
|
yield* Console.log("");
|
|
6843
|
-
yield* printDiagnostics([...surfaceDiagnostics], options.verbose, directory);
|
|
6871
|
+
yield* printDiagnostics([...surfaceDiagnostics], options.verbose, directory, buildRulePriorityMap([score]));
|
|
6844
6872
|
if (options.isNonInteractiveEnvironment && options.outputSurface !== "prComment") yield* printAgentGuidance();
|
|
6845
6873
|
if (demotedDiagnosticCount > 0) {
|
|
6846
6874
|
yield* Console.log(highlighter.gray(` ${demotedDiagnosticCount} demoted from the ${options.outputSurface} surface (e.g. design cleanup) — run \`npx react-doctor@latest .\` locally for the full list.`));
|
|
@@ -7229,7 +7257,7 @@ const printMultiProjectSummary = (input) => Effect.gen(function* () {
|
|
|
7229
7257
|
const surfaceDiagnostics = filterDiagnosticsForSurface(completedScans.flatMap((scan) => scan.result.diagnostics), "cli", userConfig);
|
|
7230
7258
|
if (surfaceDiagnostics.length > 0) {
|
|
7231
7259
|
yield* Console.log("");
|
|
7232
|
-
yield* printDiagnostics(surfaceDiagnostics, verbose, "");
|
|
7260
|
+
yield* printDiagnostics(surfaceDiagnostics, verbose, "", buildRulePriorityMap(completedScans.map((scan) => scan.result.score)));
|
|
7233
7261
|
}
|
|
7234
7262
|
const aggregateScore = computeAggregateScore(completedScans);
|
|
7235
7263
|
const totalSourceFileCount = completedScans.reduce((sum, scan) => sum + scan.result.project.sourceFileCount, 0);
|
|
@@ -7500,7 +7528,7 @@ const warnSetupPromptFailure = async (options, error) => {
|
|
|
7500
7528
|
return;
|
|
7501
7529
|
}
|
|
7502
7530
|
try {
|
|
7503
|
-
const { cliLogger } = await import("./cli-logger-
|
|
7531
|
+
const { cliLogger } = await import("./cli-logger-BgVL1vBI.js").then((n) => n.n);
|
|
7504
7532
|
cliLogger.warn(message);
|
|
7505
7533
|
} catch {}
|
|
7506
7534
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -20,9 +20,9 @@ interface ReactDoctorIgnoreConfig {
|
|
|
20
20
|
* locally but excluded from PR comments, the score, or the CI gate:
|
|
21
21
|
*
|
|
22
22
|
* - `cli` — local terminal output from `react-doctor` (`printDiagnostics`).
|
|
23
|
-
* - `prComment` —
|
|
24
|
-
*
|
|
25
|
-
*
|
|
23
|
+
* - `prComment` — diagnostics destined for a sticky pull-request
|
|
24
|
+
* summary comment. Selected by running the CLI with `--pr-comment`
|
|
25
|
+
* (sets `outputSurface: "prComment"`).
|
|
26
26
|
* - `score` — diagnostics shipped to the React Doctor score API
|
|
27
27
|
* (or counted toward local score calculations).
|
|
28
28
|
* - `ciFailure` — diagnostics that count toward the `--fail-on` exit
|
|
@@ -349,9 +349,15 @@ interface ProjectInfo {
|
|
|
349
349
|
}
|
|
350
350
|
//#endregion
|
|
351
351
|
//#region src/types/score.d.ts
|
|
352
|
+
type RuleTier = "P0" | "P1" | "P2" | "P3";
|
|
353
|
+
interface RulePriority {
|
|
354
|
+
readonly priority: number | null;
|
|
355
|
+
readonly tier: RuleTier;
|
|
356
|
+
}
|
|
352
357
|
interface ScoreResult {
|
|
353
358
|
score: number;
|
|
354
359
|
label: string;
|
|
360
|
+
readonly rules?: Readonly<Record<string, RulePriority>>;
|
|
355
361
|
} //#endregion
|
|
356
362
|
//#region src/types/diagnose.d.ts
|
|
357
363
|
interface DiagnoseOptions {
|
package/dist/index.js
CHANGED
|
@@ -5939,6 +5939,149 @@ const appendReanimatedSharedValueHint = (help, rule, project) => {
|
|
|
5939
5939
|
if (!help) return REANIMATED_SHARED_VALUE_HINT;
|
|
5940
5940
|
return `${help}\n\n${REANIMATED_SHARED_VALUE_HINT}`;
|
|
5941
5941
|
};
|
|
5942
|
+
const REDACTED_PLACEHOLDER = "<redacted>";
|
|
5943
|
+
const KEEP_PREFIX = `$1${REDACTED_PLACEHOLDER}`;
|
|
5944
|
+
const KNOWN_SECRET_RULES = [
|
|
5945
|
+
{
|
|
5946
|
+
pattern: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g,
|
|
5947
|
+
replacement: REDACTED_PLACEHOLDER
|
|
5948
|
+
},
|
|
5949
|
+
{
|
|
5950
|
+
pattern: /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/g,
|
|
5951
|
+
replacement: REDACTED_PLACEHOLDER
|
|
5952
|
+
},
|
|
5953
|
+
{
|
|
5954
|
+
pattern: /(?<=:\/\/)[^\s/:@]+:[^\s/@]+(?=@)/g,
|
|
5955
|
+
replacement: REDACTED_PLACEHOLDER
|
|
5956
|
+
},
|
|
5957
|
+
{
|
|
5958
|
+
pattern: /\b(AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA|A3T[A-Z0-9])[0-9A-Z]{16,}/g,
|
|
5959
|
+
replacement: KEEP_PREFIX
|
|
5960
|
+
},
|
|
5961
|
+
{
|
|
5962
|
+
pattern: /\b(gh[pousr]_)[A-Za-z0-9]{36,}/g,
|
|
5963
|
+
replacement: KEEP_PREFIX
|
|
5964
|
+
},
|
|
5965
|
+
{
|
|
5966
|
+
pattern: /\b(github_pat_)[A-Za-z0-9_]{22,}/g,
|
|
5967
|
+
replacement: KEEP_PREFIX
|
|
5968
|
+
},
|
|
5969
|
+
{
|
|
5970
|
+
pattern: /\b(glpat-)[A-Za-z0-9_-]{20,}/g,
|
|
5971
|
+
replacement: KEEP_PREFIX
|
|
5972
|
+
},
|
|
5973
|
+
{
|
|
5974
|
+
pattern: /\b(xox[baprs]-)[A-Za-z0-9-]{10,}/g,
|
|
5975
|
+
replacement: KEEP_PREFIX
|
|
5976
|
+
},
|
|
5977
|
+
{
|
|
5978
|
+
pattern: /(?<=hooks\.slack\.com\/services\/)[A-Za-z0-9/+_-]{20,}/g,
|
|
5979
|
+
replacement: REDACTED_PLACEHOLDER
|
|
5980
|
+
},
|
|
5981
|
+
{
|
|
5982
|
+
pattern: /\b((?:sk|rk)_(?:live|test)_)[0-9A-Za-z]{10,}/g,
|
|
5983
|
+
replacement: KEEP_PREFIX
|
|
5984
|
+
},
|
|
5985
|
+
{
|
|
5986
|
+
pattern: /\b(sk-(?:proj-|ant-)?)[A-Za-z0-9_-]{20,}/g,
|
|
5987
|
+
replacement: KEEP_PREFIX
|
|
5988
|
+
},
|
|
5989
|
+
{
|
|
5990
|
+
pattern: /\b(AIza)[0-9A-Za-z_-]{35,}/g,
|
|
5991
|
+
replacement: KEEP_PREFIX
|
|
5992
|
+
},
|
|
5993
|
+
{
|
|
5994
|
+
pattern: /\b(ya29\.)[0-9A-Za-z_-]{20,}/g,
|
|
5995
|
+
replacement: KEEP_PREFIX
|
|
5996
|
+
},
|
|
5997
|
+
{
|
|
5998
|
+
pattern: /\b(npm_)[A-Za-z0-9]{36,}/g,
|
|
5999
|
+
replacement: KEEP_PREFIX
|
|
6000
|
+
},
|
|
6001
|
+
{
|
|
6002
|
+
pattern: /\b(SG\.)[A-Za-z0-9_-]{22,}\.[A-Za-z0-9_-]{43,}/g,
|
|
6003
|
+
replacement: KEEP_PREFIX
|
|
6004
|
+
},
|
|
6005
|
+
{
|
|
6006
|
+
pattern: /\b(SK)[0-9a-fA-F]{32,}/g,
|
|
6007
|
+
replacement: KEEP_PREFIX
|
|
6008
|
+
},
|
|
6009
|
+
{
|
|
6010
|
+
pattern: /\b(dop_v1_)[a-f0-9]{64,}/g,
|
|
6011
|
+
replacement: KEEP_PREFIX
|
|
6012
|
+
},
|
|
6013
|
+
{
|
|
6014
|
+
pattern: /\b(shp(?:at|ca|pa|ss)_)[a-fA-F0-9]{32,}/g,
|
|
6015
|
+
replacement: KEEP_PREFIX
|
|
6016
|
+
},
|
|
6017
|
+
{
|
|
6018
|
+
pattern: /\b(sq0[a-z]{3}-)[0-9A-Za-z_-]{22,}/g,
|
|
6019
|
+
replacement: KEEP_PREFIX
|
|
6020
|
+
},
|
|
6021
|
+
{
|
|
6022
|
+
pattern: /\b([0-9]{8,10}:AA)[0-9A-Za-z_-]{32,}/g,
|
|
6023
|
+
replacement: KEEP_PREFIX
|
|
6024
|
+
},
|
|
6025
|
+
{
|
|
6026
|
+
pattern: /(?<=\bBearer\s)[A-Za-z0-9._~+/=-]{16,}/g,
|
|
6027
|
+
replacement: REDACTED_PLACEHOLDER
|
|
6028
|
+
},
|
|
6029
|
+
{
|
|
6030
|
+
pattern: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g,
|
|
6031
|
+
replacement: REDACTED_PLACEHOLDER
|
|
6032
|
+
}
|
|
6033
|
+
];
|
|
6034
|
+
const CANDIDATE_TOKEN_PATTERN = /[A-Za-z0-9_][A-Za-z0-9_-]*/g;
|
|
6035
|
+
const GIT_OBJECT_ID_PATTERN = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
|
|
6036
|
+
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
6037
|
+
const HAS_LETTER_PATTERN = /[A-Za-z]/;
|
|
6038
|
+
const HAS_DIGIT_PATTERN = /[0-9]/;
|
|
6039
|
+
const shannonEntropyBits = (value) => {
|
|
6040
|
+
const counts = /* @__PURE__ */ new Map();
|
|
6041
|
+
for (const char of value) counts.set(char, (counts.get(char) ?? 0) + 1);
|
|
6042
|
+
let bits = 0;
|
|
6043
|
+
for (const count of counts.values()) {
|
|
6044
|
+
const probability = count / value.length;
|
|
6045
|
+
bits -= probability * Math.log2(probability);
|
|
6046
|
+
}
|
|
6047
|
+
return bits;
|
|
6048
|
+
};
|
|
6049
|
+
const looksLikeHighEntropySecret = (token) => {
|
|
6050
|
+
if (token.length < 32) return false;
|
|
6051
|
+
if (!HAS_LETTER_PATTERN.test(token) || !HAS_DIGIT_PATTERN.test(token)) return false;
|
|
6052
|
+
if (GIT_OBJECT_ID_PATTERN.test(token) || UUID_PATTERN.test(token)) return false;
|
|
6053
|
+
return shannonEntropyBits(token) >= 3;
|
|
6054
|
+
};
|
|
6055
|
+
const redactHighEntropyTokens = (text) => text.replace(CANDIDATE_TOKEN_PATTERN, (token) => looksLikeHighEntropySecret(token) ? REDACTED_PLACEHOLDER : token);
|
|
6056
|
+
/**
|
|
6057
|
+
* Masks API keys, tokens, private keys, credentialed URLs, and emails
|
|
6058
|
+
* found anywhere inside a free-text string, returning the scrubbed text.
|
|
6059
|
+
* Applied to every diagnostic's `message` / `help` at construction time
|
|
6060
|
+
* so secrets never reach the terminal, the JSON report, or the score
|
|
6061
|
+
* API — react-doctor must never echo or transmit a user's secrets.
|
|
6062
|
+
*
|
|
6063
|
+
* Provider tokens keep their non-secret, type-identifying prefix (e.g.
|
|
6064
|
+
* `sk_live_<redacted>`, `ghp_<redacted>`, `AKIA<redacted>`) so the leaked
|
|
6065
|
+
* credential's type stays visible; structural or unknown-format secrets
|
|
6066
|
+
* with no meaningful prefix are masked whole.
|
|
6067
|
+
*
|
|
6068
|
+
* Runs the high-precision known-shape detectors first, then a generic
|
|
6069
|
+
* entropy-gated sweep for unknown-format secrets. Idempotent: the inert
|
|
6070
|
+
* `<redacted>` placeholder matches none of the detectors and is too
|
|
6071
|
+
* short for the generic sweep, so re-running leaves the text unchanged.
|
|
6072
|
+
*
|
|
6073
|
+
* Accepts `unknown` on purpose: callers feed it diagnostic `message` /
|
|
6074
|
+
* `help` that originate from oxlint JSON, which is only shape-checked at
|
|
6075
|
+
* the top level (the per-field `string` types are assumed, not validated).
|
|
6076
|
+
* A malformed non-string value returns `""` instead of throwing on
|
|
6077
|
+
* `.replace`, so one bad diagnostic can't abort parsing the whole batch.
|
|
6078
|
+
*/
|
|
6079
|
+
const redactSensitiveText = (text) => {
|
|
6080
|
+
if (typeof text !== "string" || text === "") return "";
|
|
6081
|
+
let redacted = text;
|
|
6082
|
+
for (const rule of KNOWN_SECRET_RULES) redacted = redacted.replace(rule.pattern, rule.replacement);
|
|
6083
|
+
return redactHighEntropyTokens(redacted);
|
|
6084
|
+
};
|
|
5942
6085
|
const REACT_MODULE_SOURCE = "react";
|
|
5943
6086
|
const REQUIRE_IDENTIFIER = "require";
|
|
5944
6087
|
const USE_IDENTIFIER = "use";
|
|
@@ -6260,6 +6403,13 @@ const getRuleRecommendation = (ruleName, project) => {
|
|
|
6260
6403
|
};
|
|
6261
6404
|
const getRuleCategory = (ruleName) => reactDoctorPlugin.rules[ruleName]?.category;
|
|
6262
6405
|
const cleanDiagnosticMessage = (message, help, plugin, rule, project) => {
|
|
6406
|
+
const cleaned = resolveCleanedDiagnostic(typeof message === "string" ? message : "", typeof help === "string" ? help : "", plugin, rule, project);
|
|
6407
|
+
return {
|
|
6408
|
+
message: redactSensitiveText(cleaned.message),
|
|
6409
|
+
help: redactSensitiveText(cleaned.help)
|
|
6410
|
+
};
|
|
6411
|
+
};
|
|
6412
|
+
const resolveCleanedDiagnostic = (message, help, plugin, rule, project) => {
|
|
6263
6413
|
if (plugin === "react-hooks-js") return {
|
|
6264
6414
|
message: REACT_COMPILER_MESSAGE,
|
|
6265
6415
|
help: appendReanimatedSharedValueHint(message.replace(FILEPATH_WITH_LOCATION_PATTERN, "").trim() || help, rule, project)
|
|
@@ -6464,11 +6614,14 @@ const spawnLintBatches = async (input) => {
|
|
|
6464
6614
|
onFileProgress(scannedFileCount + batchFileIndex, totalFileCount);
|
|
6465
6615
|
}
|
|
6466
6616
|
}, 50) : null;
|
|
6467
|
-
|
|
6468
|
-
|
|
6469
|
-
|
|
6470
|
-
|
|
6471
|
-
|
|
6617
|
+
try {
|
|
6618
|
+
const batchDiagnostics = await spawnLintBatch(batch);
|
|
6619
|
+
allDiagnostics.push(...batchDiagnostics);
|
|
6620
|
+
scannedFileCount += batch.length;
|
|
6621
|
+
onFileProgress?.(scannedFileCount, totalFileCount);
|
|
6622
|
+
} finally {
|
|
6623
|
+
if (progressInterval !== null) clearInterval(progressInterval);
|
|
6624
|
+
}
|
|
6472
6625
|
}
|
|
6473
6626
|
if (droppedFiles.length > 0 && onPartialFailure) {
|
|
6474
6627
|
const previewFiles = droppedFiles.slice(0, 3).join(", ");
|
|
@@ -6817,17 +6970,21 @@ var Reporter = class Reporter extends Context.Service()("react-doctor/Reporter")
|
|
|
6817
6970
|
});
|
|
6818
6971
|
}));
|
|
6819
6972
|
};
|
|
6820
|
-
const
|
|
6821
|
-
|
|
6822
|
-
|
|
6823
|
-
|
|
6824
|
-
|
|
6825
|
-
|
|
6826
|
-
|
|
6827
|
-
|
|
6828
|
-
|
|
6829
|
-
|
|
6830
|
-
|
|
6973
|
+
const RulePrioritySchema = Schema.Struct({
|
|
6974
|
+
priority: Schema.NullOr(Schema.Number),
|
|
6975
|
+
tier: Schema.Literals([
|
|
6976
|
+
"P0",
|
|
6977
|
+
"P1",
|
|
6978
|
+
"P2",
|
|
6979
|
+
"P3"
|
|
6980
|
+
])
|
|
6981
|
+
});
|
|
6982
|
+
const ScoreApiResponseSchema = Schema.Struct({
|
|
6983
|
+
score: Schema.Number,
|
|
6984
|
+
label: Schema.String,
|
|
6985
|
+
rules: Schema.optional(Schema.Record(Schema.String, RulePrioritySchema))
|
|
6986
|
+
});
|
|
6987
|
+
const parseScoreResult = (value) => Option.getOrNull(Schema.decodeUnknownOption(ScoreApiResponseSchema)(value));
|
|
6831
6988
|
const stripFilePaths = (diagnostics) => diagnostics.map(({ filePath: _filePath, ...rest }) => rest);
|
|
6832
6989
|
const isAbortError = (error) => error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError");
|
|
6833
6990
|
const describeFailure = (error) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-doctor",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.14-dev.3ceb748",
|
|
4
4
|
"description": "Diagnose and fix React codebases for security, performance, correctness, accessibility, bundle-size, and architecture issues",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"accessibility",
|
|
@@ -52,20 +52,20 @@
|
|
|
52
52
|
"@effect/platform-node-shared": "4.0.0-beta.70",
|
|
53
53
|
"agent-install": "0.0.5",
|
|
54
54
|
"conf": "^15.1.0",
|
|
55
|
-
"deslop-js": "^0.0.
|
|
55
|
+
"deslop-js": "^0.0.14",
|
|
56
56
|
"effect": "4.0.0-beta.70",
|
|
57
57
|
"eslint-plugin-react-hooks": "^7.1.1",
|
|
58
58
|
"oxlint": "^1.66.0",
|
|
59
59
|
"prompts": "^2.4.2",
|
|
60
60
|
"typescript": ">=5.0.4 <7",
|
|
61
|
-
"oxlint-plugin-react-doctor": "0.2.
|
|
61
|
+
"oxlint-plugin-react-doctor": "0.2.14-dev.3ceb748"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
64
|
"@types/prompts": "^2.4.9",
|
|
65
65
|
"commander": "^14.0.3",
|
|
66
66
|
"ora": "^9.4.0",
|
|
67
|
-
"@react-doctor/api": "0.2.
|
|
68
|
-
"@react-doctor/core": "0.2.
|
|
67
|
+
"@react-doctor/api": "0.2.14",
|
|
68
|
+
"@react-doctor/core": "0.2.14"
|
|
69
69
|
},
|
|
70
70
|
"engines": {
|
|
71
71
|
"node": "^20.19.0 || >=22.12.0"
|