react-doctor 0.5.8-dev.986557d → 0.5.8-dev.99f2417
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/LICENSE +14 -1
- package/dist/cli.js +616 -241
- package/dist/index.d.ts +11 -2
- package/dist/index.js +259 -66
- package/dist/lsp.js +200 -67
- package/package.json +9 -8
package/dist/lsp.js
CHANGED
|
@@ -6021,7 +6021,7 @@ const composePassthrough = /* @__PURE__ */ dual(2, (left, right) => (input) => {
|
|
|
6021
6021
|
* @since 2.0.0
|
|
6022
6022
|
*/
|
|
6023
6023
|
const Scheduler = /* @__PURE__ */ Reference("effect/Scheduler", { defaultValue: () => new MixedScheduler() });
|
|
6024
|
-
const setImmediate = "setImmediate" in globalThis ? (f) => {
|
|
6024
|
+
const setImmediate$1 = "setImmediate" in globalThis ? (f) => {
|
|
6025
6025
|
const timer = globalThis.setImmediate(f);
|
|
6026
6026
|
return () => globalThis.clearImmediate(timer);
|
|
6027
6027
|
} : (f) => {
|
|
@@ -6065,7 +6065,7 @@ var PriorityBuckets = class {
|
|
|
6065
6065
|
var MixedScheduler = class {
|
|
6066
6066
|
executionMode;
|
|
6067
6067
|
setImmediate;
|
|
6068
|
-
constructor(executionMode = "async", setImmediateFn = setImmediate) {
|
|
6068
|
+
constructor(executionMode = "async", setImmediateFn = setImmediate$1) {
|
|
6069
6069
|
this.executionMode = executionMode;
|
|
6070
6070
|
this.setImmediate = setImmediateFn;
|
|
6071
6071
|
}
|
|
@@ -6090,7 +6090,7 @@ var MixedSchedulerDispatcher = class {
|
|
|
6090
6090
|
tasks = /* @__PURE__ */ new PriorityBuckets();
|
|
6091
6091
|
running = void 0;
|
|
6092
6092
|
setImmediate;
|
|
6093
|
-
constructor(setImmediateFn = setImmediate) {
|
|
6093
|
+
constructor(setImmediateFn = setImmediate$1) {
|
|
6094
6094
|
this.setImmediate = setImmediateFn;
|
|
6095
6095
|
}
|
|
6096
6096
|
/**
|
|
@@ -33730,6 +33730,7 @@ const MILLISECONDS_PER_SECOND = 1e3;
|
|
|
33730
33730
|
const SCORE_API_URL = "https://www.react.doctor/api/score";
|
|
33731
33731
|
const FETCH_TIMEOUT_MS = 1e4;
|
|
33732
33732
|
const GITHUB_VIEWER_PERMISSION_TIMEOUT_MS = 2e3;
|
|
33733
|
+
const SPAWN_ARGS_MAX_LENGTH_CHARS = 24e3;
|
|
33733
33734
|
const PER_WORKER_MEM_BUDGET_BYTES = 1024 * 1024 * 1024;
|
|
33734
33735
|
const DEFAULT_BRANCH_CANDIDATES = ["main", "master"];
|
|
33735
33736
|
const ADOPTABLE_LINT_CONFIG_FILENAMES = [".oxlintrc.json", ".eslintrc.json"];
|
|
@@ -33810,6 +33811,7 @@ const DEAD_CODE_PHASE_TIMEOUT_MS = 15e4;
|
|
|
33810
33811
|
const LINT_PHASE_TIMEOUT_MS = 3e5;
|
|
33811
33812
|
const SCAN_TOTAL_DEADLINE_MS = 9e5;
|
|
33812
33813
|
const DEAD_CODE_WORKER_MAX_OLD_SPACE_MB = 8192;
|
|
33814
|
+
const DEAD_CODE_WORKER_MEM_BUDGET_BYTES = 2 * 1024 * 1024 * 1024;
|
|
33813
33815
|
const DEAD_CODE_TIMEOUT_CEILING_MS = 6e5;
|
|
33814
33816
|
const DEAD_CODE_PHASE_TIMEOUT_OVER_WORKER_MS = 3e4;
|
|
33815
33817
|
const DEAD_CODE_OVERLAP_PARSE_SHARE = .4;
|
|
@@ -34546,7 +34548,10 @@ for (const [legacyRuleKey, nativeRuleKey] of Object.entries(LEGACY_RULE_KEY_TO_N
|
|
|
34546
34548
|
NATIVE_RULE_KEY_TO_LEGACY_RULE_KEYS.set(nativeRuleKey, aliases);
|
|
34547
34549
|
}
|
|
34548
34550
|
const getLegacyRuleKeysForNative = (ruleKey) => NATIVE_RULE_KEY_TO_LEGACY_RULE_KEYS.get(ruleKey) ?? [];
|
|
34549
|
-
const canonicalizeRuleKey = (ruleKey) =>
|
|
34551
|
+
const canonicalizeRuleKey = (ruleKey) => {
|
|
34552
|
+
const nativeRuleKey = LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY[ruleKey];
|
|
34553
|
+
return typeof nativeRuleKey === "string" ? nativeRuleKey : ruleKey;
|
|
34554
|
+
};
|
|
34550
34555
|
const isReactDoctorShortIdOf = (bareRuleKey, qualifiedRuleKey) => !bareRuleKey.includes("/") && qualifiedRuleKey === `react-doctor/${bareRuleKey}`;
|
|
34551
34556
|
const isSameRuleKey = (candidateRuleKey, targetRuleKey) => {
|
|
34552
34557
|
const canonicalCandidate = canonicalizeRuleKey(candidateRuleKey);
|
|
@@ -35049,6 +35054,7 @@ const collectStringSet = (values) => {
|
|
|
35049
35054
|
if (!Array.isArray(values)) return /* @__PURE__ */ new Set();
|
|
35050
35055
|
return new Set(values.filter((value) => typeof value === "string"));
|
|
35051
35056
|
};
|
|
35057
|
+
const JSX_NO_UNDEF_IDENTIFIER_PATTERN = /^`([^`]+)`/;
|
|
35052
35058
|
/**
|
|
35053
35059
|
* Pre-compiles every stateful filter and returns a single
|
|
35054
35060
|
* `apply(diagnostic)` closure that runs (in order):
|
|
@@ -35082,8 +35088,10 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
35082
35088
|
const compiledOverrides = compileIgnoreOverrides(userConfig);
|
|
35083
35089
|
const textComponentNames = collectStringSet(userConfig?.textComponents);
|
|
35084
35090
|
const rawTextWrapperComponentNames = collectStringSet(userConfig?.rawTextWrapperComponents);
|
|
35091
|
+
const runtimeGlobalNames = collectStringSet(userConfig?.runtimeGlobals);
|
|
35085
35092
|
const hasTextComponents = textComponentNames.size > 0;
|
|
35086
35093
|
const hasRawTextWrappers = rawTextWrapperComponentNames.size > 0;
|
|
35094
|
+
const hasRuntimeGlobals = runtimeGlobalNames.size > 0;
|
|
35087
35095
|
const fileLinesCache = /* @__PURE__ */ new Map();
|
|
35088
35096
|
const fileContextCache = /* @__PURE__ */ new Map();
|
|
35089
35097
|
const libraryFileCache = /* @__PURE__ */ new Map();
|
|
@@ -35135,6 +35143,12 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
35135
35143
|
if (hasRawTextWrappers && isInsideStringOnlyWrapper(lines, diagnostic.line, diagnostic.column, rawTextWrapperComponentNames)) return true;
|
|
35136
35144
|
return false;
|
|
35137
35145
|
};
|
|
35146
|
+
const isJsxNoUndefSuppressedByConfig = (diagnostic) => {
|
|
35147
|
+
if (!hasRuntimeGlobals) return false;
|
|
35148
|
+
if (diagnostic.rule !== "jsx-no-undef") return false;
|
|
35149
|
+
const identifierMatch = JSX_NO_UNDEF_IDENTIFIER_PATTERN.exec(diagnostic.message);
|
|
35150
|
+
return identifierMatch !== null && runtimeGlobalNames.has(identifierMatch[1]);
|
|
35151
|
+
};
|
|
35138
35152
|
return { apply: (diagnostic) => {
|
|
35139
35153
|
if (shouldAutoSuppress(diagnostic)) return null;
|
|
35140
35154
|
let current = diagnostic;
|
|
@@ -35159,6 +35173,7 @@ const buildDiagnosticPipeline = (input) => {
|
|
|
35159
35173
|
if (isFileIgnoredByPatterns(current.filePath, rootDirectory, ignoredFilePatterns)) return null;
|
|
35160
35174
|
if (isDiagnosticIgnoredByOverrides(current, rootDirectory, compiledOverrides)) return null;
|
|
35161
35175
|
if (isRnRawTextSuppressedByConfig(current)) return null;
|
|
35176
|
+
if (isJsxNoUndefSuppressedByConfig(current)) return null;
|
|
35162
35177
|
}
|
|
35163
35178
|
if (respectInlineDisables && current.line > 0) {
|
|
35164
35179
|
const lines = getFileLines(current.filePath);
|
|
@@ -35398,7 +35413,7 @@ const resolveScanConcurrency = (requested) => {
|
|
|
35398
35413
|
if (!Number.isFinite(requested) || requested < 1) return 1;
|
|
35399
35414
|
return Math.min(Math.floor(requested), 32);
|
|
35400
35415
|
};
|
|
35401
|
-
const readSystemFacts = () => ({
|
|
35416
|
+
const readSystemFacts$1 = () => ({
|
|
35402
35417
|
availableCores: os.availableParallelism(),
|
|
35403
35418
|
totalMemoryBytes: os.totalmem(),
|
|
35404
35419
|
cgroupMemoryLimitBytes: readCgroupMemoryLimitBytes()
|
|
@@ -35419,7 +35434,7 @@ const readSystemFacts = () => ({
|
|
|
35419
35434
|
* `facts` is injectable so tests exercise core-bound, memory-bound, cgroup-
|
|
35420
35435
|
* limited, and ceiling cases without mocking `os` or the filesystem.
|
|
35421
35436
|
*/
|
|
35422
|
-
const resolveAutoScanConcurrency = (facts = readSystemFacts()) => {
|
|
35437
|
+
const resolveAutoScanConcurrency = (facts = readSystemFacts$1()) => {
|
|
35423
35438
|
const availableMemoryBytes = Math.min(facts.totalMemoryBytes, facts.cgroupMemoryLimitBytes ?? Number.POSITIVE_INFINITY);
|
|
35424
35439
|
const memoryBoundedWorkers = Math.floor(availableMemoryBytes / PER_WORKER_MEM_BUDGET_BYTES);
|
|
35425
35440
|
return resolveScanConcurrency(Math.min(facts.availableCores, memoryBoundedWorkers));
|
|
@@ -35610,6 +35625,13 @@ const BOOLEAN_FIELD_NAMES = [
|
|
|
35610
35625
|
"adoptExistingLintConfig"
|
|
35611
35626
|
];
|
|
35612
35627
|
const STRING_FIELD_NAMES = ["rootDir"];
|
|
35628
|
+
const STRING_ARRAY_FIELD_NAMES = [
|
|
35629
|
+
"projects",
|
|
35630
|
+
"textComponents",
|
|
35631
|
+
"rawTextWrapperComponents",
|
|
35632
|
+
"runtimeGlobals",
|
|
35633
|
+
"serverAuthFunctionNames"
|
|
35634
|
+
];
|
|
35613
35635
|
const SURFACE_CONTROL_FIELD_NAMES = [
|
|
35614
35636
|
"includeTags",
|
|
35615
35637
|
"excludeTags",
|
|
@@ -35711,6 +35733,7 @@ const validateConfigTypes = (config) => {
|
|
|
35711
35733
|
const validated = { ...config };
|
|
35712
35734
|
for (const fieldName of BOOLEAN_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => coerceMaybeBooleanString(fieldName, value));
|
|
35713
35735
|
for (const fieldName of STRING_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => validateString(fieldName, value));
|
|
35736
|
+
for (const fieldName of STRING_ARRAY_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => validateStringArrayField(fieldName, value));
|
|
35714
35737
|
applyFieldValidator(config, validated, "surfaces", validateSurfacesField);
|
|
35715
35738
|
for (const fieldName of SEVERITY_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => validateSeverityMap(fieldName, value, fieldName === "categories"));
|
|
35716
35739
|
applyFieldValidator(config, validated, "plugins", (value) => validateStringArrayField("plugins", value));
|
|
@@ -36897,7 +36920,10 @@ const shouldEnableRule = (requires, tags, capabilities, ignoredTags, disabledBy)
|
|
|
36897
36920
|
}
|
|
36898
36921
|
return true;
|
|
36899
36922
|
};
|
|
36900
|
-
const
|
|
36923
|
+
const yieldToEventLoop = () => new Promise((resolve) => {
|
|
36924
|
+
setImmediate(resolve);
|
|
36925
|
+
});
|
|
36926
|
+
const createSecurityScanSession = (rootDirectory, options) => {
|
|
36901
36927
|
const capabilities = options.project ? buildCapabilities(options.project) : /* @__PURE__ */ new Set();
|
|
36902
36928
|
const ignoredTags = options.ignoredTags ?? /* @__PURE__ */ new Set();
|
|
36903
36929
|
const enabledScanRules = REACT_DOCTOR_RULES.flatMap((entry) => {
|
|
@@ -36912,7 +36938,7 @@ const checkSecurityScan = (rootDirectory, options = {}) => {
|
|
|
36912
36938
|
committedFilesOnly: rule.committedFilesOnly === true
|
|
36913
36939
|
}];
|
|
36914
36940
|
});
|
|
36915
|
-
if (enabledScanRules.length === 0) return
|
|
36941
|
+
if (enabledScanRules.length === 0) return null;
|
|
36916
36942
|
const diagnostics = [];
|
|
36917
36943
|
const seen = /* @__PURE__ */ new Set();
|
|
36918
36944
|
const gitIgnoredCache = /* @__PURE__ */ new Map();
|
|
@@ -36924,15 +36950,34 @@ const checkSecurityScan = (rootDirectory, options = {}) => {
|
|
|
36924
36950
|
}
|
|
36925
36951
|
return status === true;
|
|
36926
36952
|
};
|
|
36927
|
-
|
|
36928
|
-
|
|
36929
|
-
|
|
36930
|
-
|
|
36931
|
-
|
|
36932
|
-
|
|
36933
|
-
|
|
36953
|
+
const scanFile = (file) => {
|
|
36954
|
+
for (const { entry, scan, committedFilesOnly } of enabledScanRules) for (const finding of scan(file)) {
|
|
36955
|
+
if (committedFilesOnly && isFileGitIgnored(file)) continue;
|
|
36956
|
+
const diagnostic = buildSecurityScanDiagnostic(finding, entry, file.relativePath);
|
|
36957
|
+
const key = `${diagnostic.rule}:${diagnostic.filePath}:${diagnostic.line}:${diagnostic.column}:${diagnostic.message}`;
|
|
36958
|
+
if (seen.has(key)) continue;
|
|
36959
|
+
seen.add(key);
|
|
36960
|
+
diagnostics.push(diagnostic);
|
|
36961
|
+
}
|
|
36962
|
+
};
|
|
36963
|
+
return {
|
|
36964
|
+
scanFile,
|
|
36965
|
+
diagnostics
|
|
36966
|
+
};
|
|
36967
|
+
};
|
|
36968
|
+
const checkSecurityScanCooperative = async (rootDirectory, options = {}) => {
|
|
36969
|
+
const session = createSecurityScanSession(rootDirectory, options);
|
|
36970
|
+
if (session === null) return [];
|
|
36971
|
+
let filesSinceYield = 0;
|
|
36972
|
+
for (const file of collectSecurityScanFiles(rootDirectory)) {
|
|
36973
|
+
session.scanFile(file);
|
|
36974
|
+
filesSinceYield += 1;
|
|
36975
|
+
if (filesSinceYield >= 16) {
|
|
36976
|
+
filesSinceYield = 0;
|
|
36977
|
+
await yieldToEventLoop();
|
|
36978
|
+
}
|
|
36934
36979
|
}
|
|
36935
|
-
return diagnostics;
|
|
36980
|
+
return session.diagnostics;
|
|
36936
36981
|
};
|
|
36937
36982
|
var import_picocolors = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
36938
36983
|
let p = process || {}, argv = p.argv || [], env = p.env || {};
|
|
@@ -37149,6 +37194,74 @@ const collectDeadCodeIgnorePatterns = (rootDirectory) => {
|
|
|
37149
37194
|
return [...seen].filter((pattern) => pattern.length > 0);
|
|
37150
37195
|
};
|
|
37151
37196
|
const collectDeadCodeEntryPatterns = (rootDirectory) => [...new Set(collectKnipPatterns(rootDirectory, "entry"))].filter((pattern) => pattern.length > 0);
|
|
37197
|
+
const readSystemFacts = () => ({
|
|
37198
|
+
availableCores: os.availableParallelism(),
|
|
37199
|
+
totalMemoryBytes: os.totalmem(),
|
|
37200
|
+
cgroupMemoryLimitBytes: readCgroupMemoryLimitBytes()
|
|
37201
|
+
});
|
|
37202
|
+
/**
|
|
37203
|
+
* How many real deslop dead-code child processes may run at once, across the
|
|
37204
|
+
* concurrent per-project `runInspect` fibers of one CLI run. The cap is the
|
|
37205
|
+
* smaller of the core count and the number of `DEAD_CODE_WORKER_MEM_BUDGET_BYTES`
|
|
37206
|
+
* workers that fit in available memory, floored at 1.
|
|
37207
|
+
*
|
|
37208
|
+
* On a roomy dev box / CI runner this resolves high enough that every
|
|
37209
|
+
* concurrently-scanned project still spawns its own worker (no serialization vs
|
|
37210
|
+
* the prior uncapped behavior); on a memory-constrained runner it collapses
|
|
37211
|
+
* toward 1, so the `withDeadCodeWorkerSlot` semaphore serializes the spawns
|
|
37212
|
+
* instead of oversubscribing memory with N simultaneous children — the global
|
|
37213
|
+
* cap the per-project spawn path lacked.
|
|
37214
|
+
*
|
|
37215
|
+
* Mirrors `resolveAutoScanConcurrency` (lint), but budgets memory per the
|
|
37216
|
+
* heavier dead-code worker. `facts` is injectable for tests.
|
|
37217
|
+
*/
|
|
37218
|
+
const resolveDeadCodeConcurrency = (facts = readSystemFacts()) => {
|
|
37219
|
+
const availableMemoryBytes = Math.min(facts.totalMemoryBytes, facts.cgroupMemoryLimitBytes ?? Number.POSITIVE_INFINITY);
|
|
37220
|
+
const memoryBoundedWorkers = Math.floor(availableMemoryBytes / DEAD_CODE_WORKER_MEM_BUDGET_BYTES);
|
|
37221
|
+
return Math.max(1, Math.min(facts.availableCores, memoryBoundedWorkers));
|
|
37222
|
+
};
|
|
37223
|
+
let availableSlots = -1;
|
|
37224
|
+
const waiters = [];
|
|
37225
|
+
const releaseSlot = () => {
|
|
37226
|
+
const nextWaiter = waiters.shift();
|
|
37227
|
+
if (nextWaiter !== void 0) nextWaiter();
|
|
37228
|
+
else availableSlots += 1;
|
|
37229
|
+
};
|
|
37230
|
+
/**
|
|
37231
|
+
* Runs `task` once a dead-code worker slot is free, releasing the slot when the
|
|
37232
|
+
* task settles (success or failure). With a high cap (roomy machine) every
|
|
37233
|
+
* caller proceeds immediately; with a low cap (constrained runner) callers
|
|
37234
|
+
* queue and run as slots free.
|
|
37235
|
+
*
|
|
37236
|
+
* `abortSignal` short-circuits the WAIT: if it's already aborted, or fires while
|
|
37237
|
+
* this caller is queued, the call rejects without acquiring a slot or running
|
|
37238
|
+
* `task` — so a cancelled scan (e.g. lint failed) doesn't sit in the queue and
|
|
37239
|
+
* then spawn a child only to tear it down. A queued caller that aborts removes
|
|
37240
|
+
* its own waiter so a later release never hands a slot to a dead request.
|
|
37241
|
+
*/
|
|
37242
|
+
const withDeadCodeWorkerSlot = async (task, abortSignal) => {
|
|
37243
|
+
if (abortSignal?.aborted) throw new Error("Dead-code worker aborted.");
|
|
37244
|
+
if (availableSlots < 0) availableSlots = resolveDeadCodeConcurrency();
|
|
37245
|
+
if (availableSlots > 0) availableSlots -= 1;
|
|
37246
|
+
else await new Promise((resolve, reject) => {
|
|
37247
|
+
const waiter = () => {
|
|
37248
|
+
abortSignal?.removeEventListener("abort", onAbort);
|
|
37249
|
+
resolve();
|
|
37250
|
+
};
|
|
37251
|
+
const onAbort = () => {
|
|
37252
|
+
const queuedIndex = waiters.indexOf(waiter);
|
|
37253
|
+
if (queuedIndex !== -1) waiters.splice(queuedIndex, 1);
|
|
37254
|
+
reject(/* @__PURE__ */ new Error("Dead-code worker aborted."));
|
|
37255
|
+
};
|
|
37256
|
+
waiters.push(waiter);
|
|
37257
|
+
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
37258
|
+
});
|
|
37259
|
+
try {
|
|
37260
|
+
return await task();
|
|
37261
|
+
} finally {
|
|
37262
|
+
releaseSlot();
|
|
37263
|
+
}
|
|
37264
|
+
};
|
|
37152
37265
|
/**
|
|
37153
37266
|
* Resolves a path to its canonical, symlink-free form, falling back to
|
|
37154
37267
|
* the input when it cannot be realpath'd (broken symlink, permission
|
|
@@ -37173,6 +37286,11 @@ const toCanonicalPath = (filePath) => {
|
|
|
37173
37286
|
};
|
|
37174
37287
|
const DEAD_CODE_PLUGIN = "deslop";
|
|
37175
37288
|
const DEAD_CODE_CATEGORY = "Maintainability";
|
|
37289
|
+
const REACT_DOCTOR_TOOLCHAIN_PACKAGES = new Set([
|
|
37290
|
+
"react-doctor",
|
|
37291
|
+
"eslint-plugin-react-doctor",
|
|
37292
|
+
"oxlint-plugin-react-doctor"
|
|
37293
|
+
]);
|
|
37176
37294
|
const DEAD_CODE_WORKER_SCRIPT = `
|
|
37177
37295
|
const inputChunks = [];
|
|
37178
37296
|
process.stdin.on("data", (chunk) => inputChunks.push(chunk));
|
|
@@ -37439,14 +37557,17 @@ const checkDeadCode = async (options) => {
|
|
|
37439
37557
|
if (!NFS.existsSync(Path.join(rootDirectory, "package.json"))) return [];
|
|
37440
37558
|
const entryPatterns = collectDeadCodeEntryPatterns(rootDirectory);
|
|
37441
37559
|
const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory);
|
|
37442
|
-
const
|
|
37443
|
-
|
|
37444
|
-
|
|
37445
|
-
|
|
37446
|
-
|
|
37447
|
-
|
|
37448
|
-
|
|
37449
|
-
|
|
37560
|
+
const spawnAndRun = () => {
|
|
37561
|
+
return runDeadCodeWorkerWithTimeout((options.createWorker ?? createDeadCodeWorker)({
|
|
37562
|
+
rootDirectory,
|
|
37563
|
+
entryPatterns,
|
|
37564
|
+
tsConfigPath: resolveTsConfigPath(rootDirectory),
|
|
37565
|
+
ignorePatterns,
|
|
37566
|
+
deslopJsModuleSpecifier: options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js"),
|
|
37567
|
+
parseConcurrency: options.parseConcurrency
|
|
37568
|
+
}), options.workerTimeoutMs ?? 12e4, options.abortSignal);
|
|
37569
|
+
};
|
|
37570
|
+
const result = parseDeadCodeWorkerResult(options.createWorker === void 0 ? await withDeadCodeWorkerSlot(spawnAndRun, options.abortSignal) : await spawnAndRun());
|
|
37450
37571
|
const toRelative = (filePath) => toRelativeFilePath(rootDirectory, filePath);
|
|
37451
37572
|
const diagnostics = [];
|
|
37452
37573
|
for (const unusedFile of result.unusedFiles) diagnostics.push({
|
|
@@ -37475,6 +37596,7 @@ const checkDeadCode = async (options) => {
|
|
|
37475
37596
|
});
|
|
37476
37597
|
}
|
|
37477
37598
|
for (const unusedDependency of result.unusedDependencies) {
|
|
37599
|
+
if (REACT_DOCTOR_TOOLCHAIN_PACKAGES.has(unusedDependency.name)) continue;
|
|
37478
37600
|
const label = unusedDependency.isDevDependency ? "devDependency" : "dependency";
|
|
37479
37601
|
diagnostics.push({
|
|
37480
37602
|
filePath: "package.json",
|
|
@@ -37847,43 +37969,46 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
37847
37969
|
* reason: GitInvocationFailed })` so the rest of the codebase
|
|
37848
37970
|
* sees a single failure channel.
|
|
37849
37971
|
*/
|
|
37850
|
-
const runCommand = (input) =>
|
|
37851
|
-
const
|
|
37852
|
-
cwd: input.directory,
|
|
37853
|
-
env: input.env,
|
|
37854
|
-
extendEnv: true
|
|
37855
|
-
}));
|
|
37856
|
-
const maxStdoutBytes = input.maxStdoutBytes;
|
|
37857
|
-
const stdoutByteCount = yield* make$13(0);
|
|
37858
|
-
const [stdout, stderr, status] = yield* all([
|
|
37859
|
-
mkString(decodeText(maxStdoutBytes === void 0 ? handle.stdout : handle.stdout.pipe(tap((chunk) => updateAndGet(stdoutByteCount, (total) => total + chunk.length).pipe(flatMap$2((total) => total > maxStdoutBytes ? fail$4(new ReactDoctorError({ reason: new GitInvocationFailed({
|
|
37860
|
-
args: [...input.args],
|
|
37861
|
-
directory: input.directory,
|
|
37862
|
-
cause: /* @__PURE__ */ new Error(`git stdout exceeded ${maxStdoutBytes} bytes`)
|
|
37863
|
-
}) })) : void_)))))),
|
|
37864
|
-
mkString(decodeText(handle.stderr)),
|
|
37865
|
-
handle.exitCode
|
|
37866
|
-
], { concurrency: 3 });
|
|
37867
|
-
return {
|
|
37868
|
-
status,
|
|
37869
|
-
stdout,
|
|
37870
|
-
stderr
|
|
37871
|
-
};
|
|
37872
|
-
})).pipe(catchTag$1("PlatformError", (cause) => {
|
|
37873
|
-
if (input.command !== "git") return succeed$2({
|
|
37972
|
+
const runCommand = (input) => {
|
|
37973
|
+
const foldSpawnFailure = (cause) => input.command !== "git" ? succeed$2({
|
|
37874
37974
|
status: 127,
|
|
37875
37975
|
stdout: "",
|
|
37876
37976
|
stderr: String(cause)
|
|
37877
|
-
})
|
|
37878
|
-
return new ReactDoctorError({ reason: new GitInvocationFailed({
|
|
37977
|
+
}) : fail$4(new ReactDoctorError({ reason: new GitInvocationFailed({
|
|
37879
37978
|
args: [...input.args],
|
|
37880
37979
|
directory: input.directory,
|
|
37881
37980
|
cause
|
|
37882
|
-
}) });
|
|
37883
|
-
|
|
37884
|
-
|
|
37885
|
-
|
|
37886
|
-
|
|
37981
|
+
}) }));
|
|
37982
|
+
return scoped(gen(function* () {
|
|
37983
|
+
if (!isDirectory(input.directory)) return yield* foldSpawnFailure(`spawn ENOTDIR (cwd is not a directory: ${input.directory})`);
|
|
37984
|
+
const argvLengthChars = input.command.length + 1 + input.args.reduce((total, arg) => total + arg.length + 1, 0);
|
|
37985
|
+
if (argvLengthChars > 24e3) return yield* foldSpawnFailure(`spawn ENAMETOOLONG (${argvLengthChars} argv chars exceed ${SPAWN_ARGS_MAX_LENGTH_CHARS})`);
|
|
37986
|
+
const handle = yield* spawner.spawn(make$1(input.command, [...input.args], {
|
|
37987
|
+
cwd: input.directory,
|
|
37988
|
+
env: input.env,
|
|
37989
|
+
extendEnv: true
|
|
37990
|
+
}));
|
|
37991
|
+
const maxStdoutBytes = input.maxStdoutBytes;
|
|
37992
|
+
const stdoutByteCount = yield* make$13(0);
|
|
37993
|
+
const [stdout, stderr, status] = yield* all([
|
|
37994
|
+
mkString(decodeText(maxStdoutBytes === void 0 ? handle.stdout : handle.stdout.pipe(tap((chunk) => updateAndGet(stdoutByteCount, (total) => total + chunk.length).pipe(flatMap$2((total) => total > maxStdoutBytes ? fail$4(new ReactDoctorError({ reason: new GitInvocationFailed({
|
|
37995
|
+
args: [...input.args],
|
|
37996
|
+
directory: input.directory,
|
|
37997
|
+
cause: /* @__PURE__ */ new Error(`git stdout exceeded ${maxStdoutBytes} bytes`)
|
|
37998
|
+
}) })) : void_)))))),
|
|
37999
|
+
mkString(decodeText(handle.stderr)),
|
|
38000
|
+
handle.exitCode
|
|
38001
|
+
], { concurrency: 3 });
|
|
38002
|
+
return {
|
|
38003
|
+
status,
|
|
38004
|
+
stdout,
|
|
38005
|
+
stderr
|
|
38006
|
+
};
|
|
38007
|
+
})).pipe(catchTag$1("PlatformError", foldSpawnFailure), withSpan("git.exec", { attributes: {
|
|
38008
|
+
"git.command": input.command,
|
|
38009
|
+
"git.subcommand": input.args[0] ?? ""
|
|
38010
|
+
} }));
|
|
38011
|
+
};
|
|
37887
38012
|
const runGit = (directory, args) => runCommand({
|
|
37888
38013
|
command: "git",
|
|
37889
38014
|
args,
|
|
@@ -37916,7 +38041,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
37916
38041
|
"rev-parse",
|
|
37917
38042
|
"--verify",
|
|
37918
38043
|
branch
|
|
37919
|
-
]).pipe(map$3((result) => result.status === 0));
|
|
38044
|
+
]).pipe(map$3((result) => result.status === 0), catch_$1((error) => error.reason._tag === "GitInvocationFailed" ? succeed$2(false) : fail$4(error)));
|
|
37920
38045
|
const headSha = (directory) => runGit(directory, ["rev-parse", "HEAD"]).pipe(map$3((result) => result.status === 0 ? trimOrNull(result.stdout) : null));
|
|
37921
38046
|
const mergeBase = (input) => isSafeGitRevision(input.ref) ? runGit(input.directory, [
|
|
37922
38047
|
"merge-base",
|
|
@@ -38130,7 +38255,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
38130
38255
|
]);
|
|
38131
38256
|
if (result.status !== 0) return null;
|
|
38132
38257
|
return parseChangedLineRanges(result.stdout);
|
|
38133
|
-
}).pipe(withSpan("Git.changedLineRanges"))
|
|
38258
|
+
}).pipe(catch_$1((error) => error.reason._tag === "GitInvocationFailed" ? succeed$2(null) : fail$4(error)), withSpan("Git.changedLineRanges"))
|
|
38134
38259
|
});
|
|
38135
38260
|
})).pipe(provide$2(layer$2.pipe(provide$2(mergeAll$1(layer$1, layer)))));
|
|
38136
38261
|
/**
|
|
@@ -40607,7 +40732,10 @@ const formatLintFailText = (reasonTag, nodeVersion) => {
|
|
|
40607
40732
|
* diagnostics).
|
|
40608
40733
|
* 2. beforeLint hook (e.g. CLI renders the project-detection block)
|
|
40609
40734
|
* 3. environment checks (reduced-motion + pnpm hardening +
|
|
40610
|
-
* expo/react-native
|
|
40735
|
+
* expo/react-native), collected synchronously. The heavier
|
|
40736
|
+
* content-regex security scan is forked instead (like supply-chain
|
|
40737
|
+
* below) and joined before the concat, so its CPU overlaps lint
|
|
40738
|
+
* rather than blocking the event loop before it.
|
|
40611
40739
|
* 4. The supply-chain check (Socket.dev) is forked onto a background
|
|
40612
40740
|
* fiber so its ~100% network-bound time overlaps the ~100%
|
|
40613
40741
|
* CPU/subprocess-bound lint pass below, collapsing two serial
|
|
@@ -40627,7 +40755,7 @@ const formatLintFailText = (reasonTag, nodeVersion) => {
|
|
|
40627
40755
|
* order, so terminal output is identical either way; supply-chain
|
|
40628
40756
|
* rides alongside without a spinner.
|
|
40629
40757
|
* 6. Join the supply-chain fiber, then assemble the diagnostics in a
|
|
40630
|
-
* FIXED order (env, supply-chain, lint, dead-code) so the output is
|
|
40758
|
+
* FIXED order (env, security-scan, supply-chain, lint, dead-code) so the output is
|
|
40631
40759
|
* byte-identical regardless of which fiber settled first. The
|
|
40632
40760
|
* viewer-permission fiber is joined later, during score-metadata
|
|
40633
40761
|
* assembly (it feeds score metadata, not diagnostics). The per-element
|
|
@@ -40688,12 +40816,12 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40688
40816
|
...checkPnpmHardening(scanDirectory),
|
|
40689
40817
|
...checkReactServerComponentsAdvisory(scanDirectory, project),
|
|
40690
40818
|
...checkExpoProject(scanDirectory, project),
|
|
40691
|
-
...checkReactNativeProject(scanDirectory, project)
|
|
40692
|
-
...checkSecurityScan(scanDirectory, {
|
|
40693
|
-
project,
|
|
40694
|
-
ignoredTags: input.ignoredTags
|
|
40695
|
-
})
|
|
40819
|
+
...checkReactNativeProject(scanDirectory, project)
|
|
40696
40820
|
])));
|
|
40821
|
+
const securityScanFiber = yield* forkChild(runCollect(applyPerElementPipeline(isDiffMode ? empty$4 : unwrap(promise(() => checkSecurityScanCooperative(scanDirectory, {
|
|
40822
|
+
project,
|
|
40823
|
+
ignoredTags: input.ignoredTags
|
|
40824
|
+
})).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)))))).pipe(withSpan("SecurityScan.run")));
|
|
40697
40825
|
const shouldRunSupplyChain = !isDiffMode || (input.supplyChainManifestChanged ?? false);
|
|
40698
40826
|
const supplyChainOverlapTimeout = yield* SupplyChainOverlapTimeoutMs;
|
|
40699
40827
|
const supplyChainFiber = yield* forkChild(shouldRunSupplyChain ? runCollect(applyPerElementPipeline(supplyChainService.run({
|
|
@@ -40829,9 +40957,11 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40829
40957
|
else yield* scanProgress.succeed(`Scanned ${scannedFilesLabel} in ${scanElapsedSeconds}s${workerCountSuffix}`);
|
|
40830
40958
|
const supplyChainResult = yield* join(supplyChainFiber);
|
|
40831
40959
|
const supplyChainCollected = supplyChainResult.diagnostics;
|
|
40960
|
+
const securityScanCollected = yield* join(securityScanFiber);
|
|
40832
40961
|
yield* reporterService.finalize;
|
|
40833
40962
|
const finalDiagnostics = sortDiagnosticsStable(assignFixGroups([
|
|
40834
40963
|
...envCollected,
|
|
40964
|
+
...securityScanCollected,
|
|
40835
40965
|
...supplyChainCollected,
|
|
40836
40966
|
...lintCollected,
|
|
40837
40967
|
...deadCodeCollected
|
|
@@ -42903,6 +43033,7 @@ const SENTRY_FLUSH_TIMEOUT_MS = 2e3;
|
|
|
42903
43033
|
const METRIC = {
|
|
42904
43034
|
cliInvoked: "cli.invoked",
|
|
42905
43035
|
cliError: "cli.error",
|
|
43036
|
+
cliEnvironmentError: "cli.env_error",
|
|
42906
43037
|
projectDetected: "project.detected",
|
|
42907
43038
|
projectPathSelected: "project.path_selected",
|
|
42908
43039
|
projectConfigSelected: "project.config_selected",
|
|
@@ -42932,7 +43063,9 @@ const METRIC = {
|
|
|
42932
43063
|
lspSessionStarted: "lsp.session.started",
|
|
42933
43064
|
lspScanCompleted: "lsp.scan.completed",
|
|
42934
43065
|
lspScanDuration: "lsp.scan.duration",
|
|
42935
|
-
lspScanDiagnostics: "lsp.scan.diagnostics"
|
|
43066
|
+
lspScanDiagnostics: "lsp.scan.diagnostics",
|
|
43067
|
+
aiTrainingWarningShown: "ai.training.warning_shown",
|
|
43068
|
+
jsonOutUsed: "json.out_used"
|
|
42936
43069
|
};
|
|
42937
43070
|
//#endregion
|
|
42938
43071
|
//#region src/cli/utils/to-category-key.ts
|
|
@@ -43249,5 +43382,5 @@ const startLanguageServer = () => {
|
|
|
43249
43382
|
};
|
|
43250
43383
|
//#endregion
|
|
43251
43384
|
export { startLanguageServer };
|
|
43252
|
-
!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]="
|
|
43253
|
-
//# debugId=
|
|
43385
|
+
!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]="0423aeac-9239-56af-8e8c-ada39e5172dd")}catch(e){}}();
|
|
43386
|
+
//# debugId=0423aeac-9239-56af-8e8c-ada39e5172dd
|
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.99f2417",
|
|
4
4
|
"description": "Your agent writes bad React. This catches it",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"accessibility",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"bugs": {
|
|
21
21
|
"url": "https://github.com/millionco/react-doctor/issues"
|
|
22
22
|
},
|
|
23
|
-
"license": "
|
|
23
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
24
24
|
"author": "Million Software, Inc",
|
|
25
25
|
"repository": {
|
|
26
26
|
"type": "git",
|
|
@@ -34,7 +34,8 @@
|
|
|
34
34
|
"bin/**",
|
|
35
35
|
"dist/**/*.js",
|
|
36
36
|
"dist/**/*.d.ts",
|
|
37
|
-
"dist/skills/**"
|
|
37
|
+
"dist/skills/**",
|
|
38
|
+
"LICENSE"
|
|
38
39
|
],
|
|
39
40
|
"type": "module",
|
|
40
41
|
"sideEffects": false,
|
|
@@ -59,12 +60,12 @@
|
|
|
59
60
|
"magicast": "^0.5.3",
|
|
60
61
|
"oxlint": ">=1.66.0 <1.67.0",
|
|
61
62
|
"prompts": "^2.4.2",
|
|
62
|
-
"typescript": ">=5.0.4 <
|
|
63
|
+
"typescript": ">=5.0.4 <6",
|
|
63
64
|
"vscode-languageserver": "^9.0.1",
|
|
64
65
|
"vscode-languageserver-textdocument": "^1.0.12",
|
|
65
66
|
"vscode-uri": "^3.1.0",
|
|
66
|
-
"
|
|
67
|
-
"
|
|
67
|
+
"deslop-js": "0.5.8",
|
|
68
|
+
"oxlint-plugin-react-doctor": "0.5.8-dev.99f2417"
|
|
68
69
|
},
|
|
69
70
|
"devDependencies": {
|
|
70
71
|
"@types/babel__code-frame": "^7.27.0",
|
|
@@ -72,9 +73,9 @@
|
|
|
72
73
|
"@xterm/headless": "^6.0.0",
|
|
73
74
|
"commander": "^14.0.3",
|
|
74
75
|
"ora": "^9.4.0",
|
|
76
|
+
"@react-doctor/api": "0.5.8",
|
|
75
77
|
"@react-doctor/core": "0.5.8",
|
|
76
|
-
"@react-doctor/language-server": "0.5.8"
|
|
77
|
-
"@react-doctor/api": "0.5.8"
|
|
78
|
+
"@react-doctor/language-server": "0.5.8"
|
|
78
79
|
},
|
|
79
80
|
"engines": {
|
|
80
81
|
"node": "^20.19.0 || >=22.13.0"
|