react-doctor 0.6.1 → 0.6.2-dev.173cc0a
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +132 -44
- package/dist/index.js +129 -41
- package/dist/lsp.js +130 -42
- package/package.json +6 -6
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
2
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="2921b8ff-14cf-5604-8ae5-b65b4f1347fc")}catch(e){}}();
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import * as NodeChildProcess from "node:child_process";
|
|
5
5
|
import { execFile, execFileSync, spawn, spawnSync } from "node:child_process";
|
|
@@ -38678,7 +38678,7 @@ const resolveAutoScanConcurrency = (facts = readSystemFacts$1()) => {
|
|
|
38678
38678
|
return resolveScanConcurrency(Math.min(facts.availableCores, memoryBoundedWorkers));
|
|
38679
38679
|
};
|
|
38680
38680
|
const resolveLintBatchOrdering = () => {
|
|
38681
|
-
return process.env["REACT_DOCTOR_LINT_BATCH_ORDERING"]?.trim().toLowerCase() === "
|
|
38681
|
+
return process.env["REACT_DOCTOR_LINT_BATCH_ORDERING"]?.trim().toLowerCase() === "arrival" ? "arrival" : "cost";
|
|
38682
38682
|
};
|
|
38683
38683
|
/**
|
|
38684
38684
|
* Per-batch oxlint wall-clock budget. Reads from the env var on
|
|
@@ -38775,17 +38775,19 @@ var DeadCodeOverlap = class extends Reference("react-doctor/DeadCodeOverlap", {
|
|
|
38775
38775
|
return "auto";
|
|
38776
38776
|
} }) {};
|
|
38777
38777
|
/**
|
|
38778
|
-
* How the full-scan lint pass
|
|
38779
|
-
*
|
|
38780
|
-
*
|
|
38781
|
-
*
|
|
38782
|
-
* the
|
|
38783
|
-
*
|
|
38784
|
-
*
|
|
38785
|
-
*
|
|
38786
|
-
* `
|
|
38787
|
-
*
|
|
38788
|
-
*
|
|
38778
|
+
* How the full-scan lint pass plans its file batches. `"cost"` (the default)
|
|
38779
|
+
* builds size-balanced LPT batches (`planLintBatches`): the same mandatory
|
|
38780
|
+
* batch count as greedy chunking (`ceil(files / 100)`), but every batch gets
|
|
38781
|
+
* an even share of files AND bytes, so no 100-file chunk is a straggler while
|
|
38782
|
+
* the remainder-batch worker idles — and the heavy files are SPREAD across
|
|
38783
|
+
* batches, the precondition the old sort-desc-then-chunk-100 `cost` mode
|
|
38784
|
+
* lacked (it packed the heaviest files into one wave-1 straggler batch,
|
|
38785
|
+
* measurably regressing size-skewed repos, which is why it never earned the
|
|
38786
|
+
* default). `"arrival"` (`REACT_DOCTOR_LINT_BATCH_ORDERING=arrival`) is the
|
|
38787
|
+
* rollback hatch to plain greedy 100-file chunking in discovery order. Tests
|
|
38788
|
+
* override via `Layer.succeed(LintBatchOrdering, ...)`. Diff / staged scans
|
|
38789
|
+
* never reach this — they pass user-scoped `includePaths` that skip discovery
|
|
38790
|
+
* and stay in arrival order; only the full-scan branch reads it.
|
|
38789
38791
|
*/
|
|
38790
38792
|
var LintBatchOrdering = class extends Reference("react-doctor/LintBatchOrdering", { defaultValue: resolveLintBatchOrdering }) {};
|
|
38791
38793
|
const CACHE_DISABLED_VALUES$1 = new Set(["1", "true"]);
|
|
@@ -40170,6 +40172,7 @@ function* collectSecurityScanFiles(rootDirectory) {
|
|
|
40170
40172
|
isGeneratedBundleByName: classification.isGeneratedBundleByName
|
|
40171
40173
|
});
|
|
40172
40174
|
}
|
|
40175
|
+
yield null;
|
|
40173
40176
|
}
|
|
40174
40177
|
for (const candidates of [
|
|
40175
40178
|
priorityCandidates,
|
|
@@ -40273,31 +40276,39 @@ const createSecurityScanSession = (rootDirectory, options) => {
|
|
|
40273
40276
|
}
|
|
40274
40277
|
return status === true;
|
|
40275
40278
|
};
|
|
40276
|
-
const
|
|
40277
|
-
for (const { entry, scan, committedFilesOnly } of enabledScanRules)
|
|
40278
|
-
|
|
40279
|
-
|
|
40280
|
-
|
|
40281
|
-
|
|
40282
|
-
|
|
40283
|
-
|
|
40279
|
+
const scanFileByRule = function* (file) {
|
|
40280
|
+
for (const { entry, scan, committedFilesOnly } of enabledScanRules) {
|
|
40281
|
+
for (const finding of scan(file)) {
|
|
40282
|
+
if (committedFilesOnly && isFileGitIgnored(file)) continue;
|
|
40283
|
+
const diagnostic = buildSecurityScanDiagnostic(finding, entry, file.relativePath);
|
|
40284
|
+
const key = `${diagnostic.rule}:${diagnostic.filePath}:${diagnostic.line}:${diagnostic.column}:${diagnostic.message}`;
|
|
40285
|
+
if (seen.has(key)) continue;
|
|
40286
|
+
seen.add(key);
|
|
40287
|
+
diagnostics.push(diagnostic);
|
|
40288
|
+
}
|
|
40289
|
+
yield;
|
|
40284
40290
|
}
|
|
40285
40291
|
};
|
|
40286
40292
|
return {
|
|
40287
|
-
|
|
40293
|
+
scanFileByRule,
|
|
40288
40294
|
diagnostics
|
|
40289
40295
|
};
|
|
40290
40296
|
};
|
|
40291
40297
|
const checkSecurityScanCooperative = async (rootDirectory, options = {}) => {
|
|
40292
40298
|
const session = createSecurityScanSession(rootDirectory, options);
|
|
40293
40299
|
if (session === null) return [];
|
|
40294
|
-
let
|
|
40300
|
+
let sliceStartedAt = performance.now();
|
|
40295
40301
|
for (const file of collectSecurityScanFiles(rootDirectory)) {
|
|
40296
|
-
|
|
40297
|
-
|
|
40298
|
-
|
|
40299
|
-
|
|
40302
|
+
if (file === null) {
|
|
40303
|
+
if (performance.now() - sliceStartedAt >= 12) {
|
|
40304
|
+
await yieldToEventLoop();
|
|
40305
|
+
sliceStartedAt = performance.now();
|
|
40306
|
+
}
|
|
40307
|
+
continue;
|
|
40308
|
+
}
|
|
40309
|
+
for (const _ruleStep of session.scanFileByRule(file)) if (performance.now() - sliceStartedAt >= 12) {
|
|
40300
40310
|
await yieldToEventLoop();
|
|
40311
|
+
sliceStartedAt = performance.now();
|
|
40301
40312
|
}
|
|
40302
40313
|
}
|
|
40303
40314
|
return session.diagnostics;
|
|
@@ -43076,6 +43087,70 @@ const hashFileContents = (filePath) => {
|
|
|
43076
43087
|
return null;
|
|
43077
43088
|
}
|
|
43078
43089
|
};
|
|
43090
|
+
/**
|
|
43091
|
+
* Balanced LPT (longest-processing-time-first) lint batch planner. Where
|
|
43092
|
+
* `batchIncludePaths` greedily fills 100-file batches and leaves a small
|
|
43093
|
+
* remainder batch (617 files → 6×100 + 17, so the parallel pool's wall clock
|
|
43094
|
+
* is set by whichever 100-file chunk happened to collect the most work while
|
|
43095
|
+
* the remainder's worker idles), this planner keeps the SAME mandatory batch
|
|
43096
|
+
* count — `ceil(files / OXLINT_MAX_FILES_PER_BATCH)`, the JS-plugin
|
|
43097
|
+
* perf-cliff / native-binding SIGABRT guard — and assigns files largest-first,
|
|
43098
|
+
* each to the least-loaded batch by cumulative byte size. Every batch ends up
|
|
43099
|
+
* with an even share of files AND bytes, so no batch is a straggler; heavy
|
|
43100
|
+
* files are SPREAD across batches (the documented precondition for `cost`
|
|
43101
|
+
* ordering to beat `arrival`) instead of concentrated in one.
|
|
43102
|
+
*
|
|
43103
|
+
* Deliberately NOT worker-count-aware: splitting into extra batches to feed
|
|
43104
|
+
* idle workers was measured (619-file corpus, 10 cores) to REGRESS the lint
|
|
43105
|
+
* wall — each additional concurrent subprocess pays a contended cold start
|
|
43106
|
+
* (node boot + plugin import + native dlopen, ~180 ms uncontended and 2-3×
|
|
43107
|
+
* that in a full wave) that outweighs the smaller per-batch file share.
|
|
43108
|
+
*
|
|
43109
|
+
* Every batch also respects the spawn-args char budget; a file that fits no
|
|
43110
|
+
* existing batch opens a new one, mirroring `batchIncludePaths`. Within a
|
|
43111
|
+
* batch, files return in arrival order, and the assignment is fully
|
|
43112
|
+
* deterministic (ties broken by lowest batch index), so a plan is
|
|
43113
|
+
* reproducible for a fixed (files, sizes) input. Batch composition cannot
|
|
43114
|
+
* change diagnostics — rules that look beyond one file (`CROSS_FILE_RULE_IDS`)
|
|
43115
|
+
* read the OTHER files via the filesystem, never via batch-mates — so the
|
|
43116
|
+
* plan only moves wall-clock time.
|
|
43117
|
+
*
|
|
43118
|
+
* O(files × batches); the linear least-loaded scan is fine at real-world
|
|
43119
|
+
* scale (a 3,500-file monorepo is 36 batches ≈ 126k comparisons).
|
|
43120
|
+
*/
|
|
43121
|
+
const planLintBatches = ({ baseArgs, files, sizeByFile }) => {
|
|
43122
|
+
if (files.length === 0) return [];
|
|
43123
|
+
const batchCount = Math.ceil(files.length / 100);
|
|
43124
|
+
const maxFilesPerBatch = Math.ceil(files.length / batchCount);
|
|
43125
|
+
const baseArgsLengthChars = estimateArgsLength(baseArgs);
|
|
43126
|
+
const descendingSizeIndexes = [...files.keys()].sort((leftIndex, rightIndex) => (sizeByFile.get(files[rightIndex]) ?? 0) - (sizeByFile.get(files[leftIndex]) ?? 0));
|
|
43127
|
+
const batches = Array.from({ length: batchCount }, () => ({
|
|
43128
|
+
fileIndexes: [],
|
|
43129
|
+
totalSizeBytes: 0,
|
|
43130
|
+
argsLengthChars: baseArgsLengthChars
|
|
43131
|
+
}));
|
|
43132
|
+
for (const fileIndex of descendingSizeIndexes) {
|
|
43133
|
+
const entryLengthChars = files[fileIndex].length + 1;
|
|
43134
|
+
let targetBatch = null;
|
|
43135
|
+
for (const batch of batches) {
|
|
43136
|
+
if (batch.fileIndexes.length >= maxFilesPerBatch) continue;
|
|
43137
|
+
if (batch.fileIndexes.length > 0 && batch.argsLengthChars + entryLengthChars > 24e3) continue;
|
|
43138
|
+
if (targetBatch === null || batch.totalSizeBytes < targetBatch.totalSizeBytes || batch.totalSizeBytes === targetBatch.totalSizeBytes && batch.fileIndexes.length < targetBatch.fileIndexes.length) targetBatch = batch;
|
|
43139
|
+
}
|
|
43140
|
+
if (targetBatch === null) {
|
|
43141
|
+
targetBatch = {
|
|
43142
|
+
fileIndexes: [],
|
|
43143
|
+
totalSizeBytes: 0,
|
|
43144
|
+
argsLengthChars: baseArgsLengthChars
|
|
43145
|
+
};
|
|
43146
|
+
batches.push(targetBatch);
|
|
43147
|
+
}
|
|
43148
|
+
targetBatch.fileIndexes.push(fileIndex);
|
|
43149
|
+
targetBatch.totalSizeBytes += sizeByFile.get(files[fileIndex]) ?? 0;
|
|
43150
|
+
targetBatch.argsLengthChars += entryLengthChars;
|
|
43151
|
+
}
|
|
43152
|
+
return batches.filter((batch) => batch.fileIndexes.length > 0).map((batch) => batch.fileIndexes.sort((leftIndex, rightIndex) => leftIndex - rightIndex).map((fileIndex) => files[fileIndex]));
|
|
43153
|
+
};
|
|
43079
43154
|
const projectCacheSubdir = (projectDirectory) => crypto.createHash("sha256").update(projectDirectory).digest("hex").slice(0, 16);
|
|
43080
43155
|
const resolveReactDoctorCacheDir = (projectDirectory) => {
|
|
43081
43156
|
const cacheDirOverride = process.env["REACT_DOCTOR_CACHE_DIR"]?.trim();
|
|
@@ -43091,7 +43166,6 @@ const resolveUserCacheScope = () => {
|
|
|
43091
43166
|
return "shared";
|
|
43092
43167
|
}
|
|
43093
43168
|
};
|
|
43094
|
-
const sortSourceFilesByCost = (entries) => [...entries].sort((left, right) => right.sizeBytes - left.sizeBytes).map((entry) => entry.path);
|
|
43095
43169
|
/**
|
|
43096
43170
|
* Atomically (re)writes the generated oxlintrc.json. Used twice in
|
|
43097
43171
|
* the runner: once for the primary scan, once for the
|
|
@@ -43150,7 +43224,7 @@ const reactHooksJsPluginDropNote = (error) => {
|
|
|
43150
43224
|
* 6. always restore disable directives + clean up the temp dir
|
|
43151
43225
|
*/
|
|
43152
43226
|
const runOxlint = async (options) => {
|
|
43153
|
-
const { rootDirectory, project, includePaths, nodeBinaryPath = process.execPath, customRulesOnly = false, respectInlineDisables = true, adoptExistingLintConfig = true, ignoredTags = /* @__PURE__ */ new Set(), userConfig, configSourceDirectory = rootDirectory, onPartialFailure, perFileLintCacheEnabled = false, onCacheStats, spawnTimeoutMs, outputMaxBytes, lintBatchOrdering = "
|
|
43227
|
+
const { rootDirectory, project, includePaths, nodeBinaryPath = process.execPath, customRulesOnly = false, respectInlineDisables = true, adoptExistingLintConfig = true, ignoredTags = /* @__PURE__ */ new Set(), userConfig, configSourceDirectory = rootDirectory, onPartialFailure, perFileLintCacheEnabled = false, onCacheStats, spawnTimeoutMs, outputMaxBytes, lintBatchOrdering = "cost" } = options;
|
|
43154
43228
|
const serverAuthFunctionNames = Array.isArray(userConfig?.serverAuthFunctionNames) ? userConfig.serverAuthFunctionNames.filter((entry) => typeof entry === "string" && entry.length > 0) : void 0;
|
|
43155
43229
|
const severityControls = buildRuleSeverityControls(userConfig);
|
|
43156
43230
|
validateRuleRegistration();
|
|
@@ -43202,8 +43276,15 @@ const runOxlint = async (options) => {
|
|
|
43202
43276
|
"json",
|
|
43203
43277
|
...sharedArgs
|
|
43204
43278
|
];
|
|
43205
|
-
|
|
43206
|
-
const
|
|
43279
|
+
await yieldToEventLoop();
|
|
43280
|
+
const sizedScanFiles = includePaths === void 0 ? listSourceFilesWithSize(rootDirectory) : null;
|
|
43281
|
+
const candidateFiles = includePaths !== void 0 ? includePaths : (sizedScanFiles ?? []).map((entry) => entry.path);
|
|
43282
|
+
const sizeByFile = sizedScanFiles !== null && lintBatchOrdering === "cost" ? new Map(sizedScanFiles.map((entry) => [entry.path, entry.sizeBytes])) : null;
|
|
43283
|
+
const buildFileBatches = (passBaseArgs, passFiles) => sizeByFile !== null ? planLintBatches({
|
|
43284
|
+
baseArgs: passBaseArgs,
|
|
43285
|
+
files: passFiles,
|
|
43286
|
+
sizeByFile
|
|
43287
|
+
}) : batchIncludePaths(passBaseArgs, passFiles);
|
|
43207
43288
|
const runConfigOverFiles = async (buildConfigForPass, configFileName, files, fileProgress) => {
|
|
43208
43289
|
if (files.length === 0) return {
|
|
43209
43290
|
diagnostics: [],
|
|
@@ -43217,7 +43298,7 @@ const runOxlint = async (options) => {
|
|
|
43217
43298
|
};
|
|
43218
43299
|
const passConfigPath = Path.join(configDirectory, configFileName);
|
|
43219
43300
|
const passBaseArgs = makeBaseArgs(passConfigPath);
|
|
43220
|
-
const passFileBatches =
|
|
43301
|
+
const passFileBatches = buildFileBatches(passBaseArgs, files);
|
|
43221
43302
|
const spawnPass = () => spawnLintBatches({
|
|
43222
43303
|
baseArgs: passBaseArgs,
|
|
43223
43304
|
fileBatches: passFileBatches,
|
|
@@ -43266,6 +43347,7 @@ const runOxlint = async (options) => {
|
|
|
43266
43347
|
const cacheKeyByFile = /* @__PURE__ */ new Map();
|
|
43267
43348
|
const missFiles = [];
|
|
43268
43349
|
const replayedDiagnostics = [];
|
|
43350
|
+
let hashSliceStartedAt = performance.now();
|
|
43269
43351
|
for (const candidateFile of candidateFiles) {
|
|
43270
43352
|
const contentHash = hashFileContents(Path.resolve(rootDirectory, candidateFile));
|
|
43271
43353
|
if (contentHash === null) {
|
|
@@ -43277,23 +43359,29 @@ const runOxlint = async (options) => {
|
|
|
43277
43359
|
const cachedDiagnostics = cache.lookup(cacheKey);
|
|
43278
43360
|
if (cachedDiagnostics === null) missFiles.push(candidateFile);
|
|
43279
43361
|
else replayedDiagnostics.push(...cachedDiagnostics);
|
|
43362
|
+
if (performance.now() - hashSliceStartedAt >= 12) {
|
|
43363
|
+
await yieldToEventLoop();
|
|
43364
|
+
hashSliceStartedAt = performance.now();
|
|
43365
|
+
}
|
|
43280
43366
|
}
|
|
43281
43367
|
const cacheHitFileCount = candidateFiles.length - missFiles.length;
|
|
43282
|
-
const
|
|
43368
|
+
const fullResult = await runConfigOverFiles((overrides) => buildConfig({
|
|
43283
43369
|
extendsPaths: [],
|
|
43284
|
-
ruleSelection: "cacheable",
|
|
43285
43370
|
disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin
|
|
43286
|
-
}), "oxlintrc.
|
|
43371
|
+
}), "oxlintrc.full.json", missFiles, options.onFileProgress && ((scannedFileCount) => options.onFileProgress?.(scannedFileCount, candidateFiles.length)));
|
|
43372
|
+
const missFileSet = new Set(missFiles);
|
|
43287
43373
|
const sidecarResult = await runConfigOverFiles(() => buildConfig({
|
|
43288
43374
|
extendsPaths: [],
|
|
43289
43375
|
ruleSelection: "sidecar"
|
|
43290
|
-
}), "oxlintrc.sidecar.json", candidateFiles, options.onFileProgress);
|
|
43376
|
+
}), "oxlintrc.sidecar.json", candidateFiles.filter((candidateFile) => !missFileSet.has(candidateFile)), options.onFileProgress && ((scannedFileCount) => options.onFileProgress?.(missFiles.length + scannedFileCount, candidateFiles.length)));
|
|
43377
|
+
const freshCacheableDiagnostics = [];
|
|
43378
|
+
for (const diagnostic of fullResult.diagnostics) if (!CROSS_FILE_RULE_IDS.has(diagnostic.rule)) freshCacheableDiagnostics.push(diagnostic);
|
|
43291
43379
|
onCacheStats?.(cacheHitFileCount, candidateFiles.length);
|
|
43292
43380
|
const missFileByNormalizedPath = /* @__PURE__ */ new Map();
|
|
43293
43381
|
for (const missFile of missFiles) missFileByNormalizedPath.set(missFile.replaceAll("\\", "/"), missFile);
|
|
43294
43382
|
const freshDiagnosticsByFile = /* @__PURE__ */ new Map();
|
|
43295
43383
|
let isAttributionSound = true;
|
|
43296
|
-
for (const diagnostic of
|
|
43384
|
+
for (const diagnostic of freshCacheableDiagnostics) {
|
|
43297
43385
|
const missFile = missFileByNormalizedPath.get(diagnostic.filePath);
|
|
43298
43386
|
if (missFile === void 0) {
|
|
43299
43387
|
isAttributionSound = false;
|
|
@@ -43303,7 +43391,7 @@ const runOxlint = async (options) => {
|
|
|
43303
43391
|
fileDiagnostics.push(diagnostic);
|
|
43304
43392
|
freshDiagnosticsByFile.set(missFile, fileDiagnostics);
|
|
43305
43393
|
}
|
|
43306
|
-
if (!
|
|
43394
|
+
if (!fullResult.didDropReactHooksJsPlugin && !fullResult.hadPartialFailure && isAttributionSound) {
|
|
43307
43395
|
for (const missFile of missFiles) {
|
|
43308
43396
|
const cacheKey = cacheKeyByFile.get(missFile);
|
|
43309
43397
|
if (cacheKey !== void 0) cache.store(cacheKey, freshDiagnosticsByFile.get(missFile) ?? []);
|
|
@@ -43312,12 +43400,12 @@ const runOxlint = async (options) => {
|
|
|
43312
43400
|
}
|
|
43313
43401
|
return dedupeDiagnostics([
|
|
43314
43402
|
...replayedDiagnostics,
|
|
43315
|
-
...
|
|
43403
|
+
...fullResult.diagnostics,
|
|
43316
43404
|
...sidecarResult.diagnostics
|
|
43317
43405
|
]);
|
|
43318
43406
|
}
|
|
43319
43407
|
const baseArgs = makeBaseArgs(configPath);
|
|
43320
|
-
const fileBatches =
|
|
43408
|
+
const fileBatches = buildFileBatches(baseArgs, candidateFiles);
|
|
43321
43409
|
const runBatches = () => spawnLintBatches({
|
|
43322
43410
|
baseArgs,
|
|
43323
43411
|
fileBatches,
|
|
@@ -45232,7 +45320,7 @@ const makeNoopConsole = () => ({
|
|
|
45232
45320
|
});
|
|
45233
45321
|
//#endregion
|
|
45234
45322
|
//#region src/cli/utils/version.ts
|
|
45235
|
-
const VERSION = "0.6.
|
|
45323
|
+
const VERSION = "0.6.2-dev.173cc0a";
|
|
45236
45324
|
//#endregion
|
|
45237
45325
|
//#region src/cli/utils/json-mode.ts
|
|
45238
45326
|
let context = null;
|
|
@@ -45601,13 +45689,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
|
|
|
45601
45689
|
* uploads source-map artifacts under, so stack frames symbolicate. Honors the
|
|
45602
45690
|
* standard `SENTRY_RELEASE` override.
|
|
45603
45691
|
*/
|
|
45604
|
-
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.6.
|
|
45692
|
+
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.6.2-dev.173cc0a`;
|
|
45605
45693
|
/**
|
|
45606
45694
|
* Deployment environment shown in Sentry's environment filter. Defaults to
|
|
45607
45695
|
* `production` for tagged releases and `development` for dev/unbuilt versions,
|
|
45608
45696
|
* overridable via the standard `SENTRY_ENVIRONMENT` env var.
|
|
45609
45697
|
*/
|
|
45610
|
-
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.6.
|
|
45698
|
+
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.6.2-dev.173cc0a") ? "development" : "production");
|
|
45611
45699
|
/**
|
|
45612
45700
|
* Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
|
|
45613
45701
|
* (set to `0` to disable tracing) and falls back to
|
|
@@ -63590,4 +63678,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
|
|
|
63590
63678
|
export {};
|
|
63591
63679
|
|
|
63592
63680
|
//# sourceMappingURL=cli.js.map
|
|
63593
|
-
//# debugId=
|
|
63681
|
+
//# debugId=2921b8ff-14cf-5604-8ae5-b65b4f1347fc
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
2
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="8565c6ea-39f3-51e6-895a-ae9f7c026d0e")}catch(e){}}();
|
|
3
3
|
import { r as __toESM$1, t as __commonJSMin$1 } from "./chunk-N93fKeF6.js";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import * as NFS from "node:fs";
|
|
@@ -35479,7 +35479,7 @@ const resolveAutoScanConcurrency = (facts = readSystemFacts$1()) => {
|
|
|
35479
35479
|
return resolveScanConcurrency(Math.min(facts.availableCores, memoryBoundedWorkers));
|
|
35480
35480
|
};
|
|
35481
35481
|
const resolveLintBatchOrdering = () => {
|
|
35482
|
-
return process.env["REACT_DOCTOR_LINT_BATCH_ORDERING"]?.trim().toLowerCase() === "
|
|
35482
|
+
return process.env["REACT_DOCTOR_LINT_BATCH_ORDERING"]?.trim().toLowerCase() === "arrival" ? "arrival" : "cost";
|
|
35483
35483
|
};
|
|
35484
35484
|
/**
|
|
35485
35485
|
* Per-batch oxlint wall-clock budget. Reads from the env var on
|
|
@@ -35576,17 +35576,19 @@ var DeadCodeOverlap = class extends Reference("react-doctor/DeadCodeOverlap", {
|
|
|
35576
35576
|
return "auto";
|
|
35577
35577
|
} }) {};
|
|
35578
35578
|
/**
|
|
35579
|
-
* How the full-scan lint pass
|
|
35580
|
-
*
|
|
35581
|
-
*
|
|
35582
|
-
*
|
|
35583
|
-
* the
|
|
35584
|
-
*
|
|
35585
|
-
*
|
|
35586
|
-
*
|
|
35587
|
-
* `
|
|
35588
|
-
*
|
|
35589
|
-
*
|
|
35579
|
+
* How the full-scan lint pass plans its file batches. `"cost"` (the default)
|
|
35580
|
+
* builds size-balanced LPT batches (`planLintBatches`): the same mandatory
|
|
35581
|
+
* batch count as greedy chunking (`ceil(files / 100)`), but every batch gets
|
|
35582
|
+
* an even share of files AND bytes, so no 100-file chunk is a straggler while
|
|
35583
|
+
* the remainder-batch worker idles — and the heavy files are SPREAD across
|
|
35584
|
+
* batches, the precondition the old sort-desc-then-chunk-100 `cost` mode
|
|
35585
|
+
* lacked (it packed the heaviest files into one wave-1 straggler batch,
|
|
35586
|
+
* measurably regressing size-skewed repos, which is why it never earned the
|
|
35587
|
+
* default). `"arrival"` (`REACT_DOCTOR_LINT_BATCH_ORDERING=arrival`) is the
|
|
35588
|
+
* rollback hatch to plain greedy 100-file chunking in discovery order. Tests
|
|
35589
|
+
* override via `Layer.succeed(LintBatchOrdering, ...)`. Diff / staged scans
|
|
35590
|
+
* never reach this — they pass user-scoped `includePaths` that skip discovery
|
|
35591
|
+
* and stay in arrival order; only the full-scan branch reads it.
|
|
35590
35592
|
*/
|
|
35591
35593
|
var LintBatchOrdering = class extends Reference("react-doctor/LintBatchOrdering", { defaultValue: resolveLintBatchOrdering }) {};
|
|
35592
35594
|
const CACHE_DISABLED_VALUES = new Set(["1", "true"]);
|
|
@@ -36943,6 +36945,7 @@ function* collectSecurityScanFiles(rootDirectory) {
|
|
|
36943
36945
|
isGeneratedBundleByName: classification.isGeneratedBundleByName
|
|
36944
36946
|
});
|
|
36945
36947
|
}
|
|
36948
|
+
yield null;
|
|
36946
36949
|
}
|
|
36947
36950
|
for (const candidates of [
|
|
36948
36951
|
priorityCandidates,
|
|
@@ -37046,31 +37049,39 @@ const createSecurityScanSession = (rootDirectory, options) => {
|
|
|
37046
37049
|
}
|
|
37047
37050
|
return status === true;
|
|
37048
37051
|
};
|
|
37049
|
-
const
|
|
37050
|
-
for (const { entry, scan, committedFilesOnly } of enabledScanRules)
|
|
37051
|
-
|
|
37052
|
-
|
|
37053
|
-
|
|
37054
|
-
|
|
37055
|
-
|
|
37056
|
-
|
|
37052
|
+
const scanFileByRule = function* (file) {
|
|
37053
|
+
for (const { entry, scan, committedFilesOnly } of enabledScanRules) {
|
|
37054
|
+
for (const finding of scan(file)) {
|
|
37055
|
+
if (committedFilesOnly && isFileGitIgnored(file)) continue;
|
|
37056
|
+
const diagnostic = buildSecurityScanDiagnostic(finding, entry, file.relativePath);
|
|
37057
|
+
const key = `${diagnostic.rule}:${diagnostic.filePath}:${diagnostic.line}:${diagnostic.column}:${diagnostic.message}`;
|
|
37058
|
+
if (seen.has(key)) continue;
|
|
37059
|
+
seen.add(key);
|
|
37060
|
+
diagnostics.push(diagnostic);
|
|
37061
|
+
}
|
|
37062
|
+
yield;
|
|
37057
37063
|
}
|
|
37058
37064
|
};
|
|
37059
37065
|
return {
|
|
37060
|
-
|
|
37066
|
+
scanFileByRule,
|
|
37061
37067
|
diagnostics
|
|
37062
37068
|
};
|
|
37063
37069
|
};
|
|
37064
37070
|
const checkSecurityScanCooperative = async (rootDirectory, options = {}) => {
|
|
37065
37071
|
const session = createSecurityScanSession(rootDirectory, options);
|
|
37066
37072
|
if (session === null) return [];
|
|
37067
|
-
let
|
|
37073
|
+
let sliceStartedAt = performance.now();
|
|
37068
37074
|
for (const file of collectSecurityScanFiles(rootDirectory)) {
|
|
37069
|
-
|
|
37070
|
-
|
|
37071
|
-
|
|
37072
|
-
|
|
37075
|
+
if (file === null) {
|
|
37076
|
+
if (performance.now() - sliceStartedAt >= 12) {
|
|
37077
|
+
await yieldToEventLoop();
|
|
37078
|
+
sliceStartedAt = performance.now();
|
|
37079
|
+
}
|
|
37080
|
+
continue;
|
|
37081
|
+
}
|
|
37082
|
+
for (const _ruleStep of session.scanFileByRule(file)) if (performance.now() - sliceStartedAt >= 12) {
|
|
37073
37083
|
await yieldToEventLoop();
|
|
37084
|
+
sliceStartedAt = performance.now();
|
|
37074
37085
|
}
|
|
37075
37086
|
}
|
|
37076
37087
|
return session.diagnostics;
|
|
@@ -39833,6 +39844,70 @@ const hashFileContents = (filePath) => {
|
|
|
39833
39844
|
return null;
|
|
39834
39845
|
}
|
|
39835
39846
|
};
|
|
39847
|
+
/**
|
|
39848
|
+
* Balanced LPT (longest-processing-time-first) lint batch planner. Where
|
|
39849
|
+
* `batchIncludePaths` greedily fills 100-file batches and leaves a small
|
|
39850
|
+
* remainder batch (617 files → 6×100 + 17, so the parallel pool's wall clock
|
|
39851
|
+
* is set by whichever 100-file chunk happened to collect the most work while
|
|
39852
|
+
* the remainder's worker idles), this planner keeps the SAME mandatory batch
|
|
39853
|
+
* count — `ceil(files / OXLINT_MAX_FILES_PER_BATCH)`, the JS-plugin
|
|
39854
|
+
* perf-cliff / native-binding SIGABRT guard — and assigns files largest-first,
|
|
39855
|
+
* each to the least-loaded batch by cumulative byte size. Every batch ends up
|
|
39856
|
+
* with an even share of files AND bytes, so no batch is a straggler; heavy
|
|
39857
|
+
* files are SPREAD across batches (the documented precondition for `cost`
|
|
39858
|
+
* ordering to beat `arrival`) instead of concentrated in one.
|
|
39859
|
+
*
|
|
39860
|
+
* Deliberately NOT worker-count-aware: splitting into extra batches to feed
|
|
39861
|
+
* idle workers was measured (619-file corpus, 10 cores) to REGRESS the lint
|
|
39862
|
+
* wall — each additional concurrent subprocess pays a contended cold start
|
|
39863
|
+
* (node boot + plugin import + native dlopen, ~180 ms uncontended and 2-3×
|
|
39864
|
+
* that in a full wave) that outweighs the smaller per-batch file share.
|
|
39865
|
+
*
|
|
39866
|
+
* Every batch also respects the spawn-args char budget; a file that fits no
|
|
39867
|
+
* existing batch opens a new one, mirroring `batchIncludePaths`. Within a
|
|
39868
|
+
* batch, files return in arrival order, and the assignment is fully
|
|
39869
|
+
* deterministic (ties broken by lowest batch index), so a plan is
|
|
39870
|
+
* reproducible for a fixed (files, sizes) input. Batch composition cannot
|
|
39871
|
+
* change diagnostics — rules that look beyond one file (`CROSS_FILE_RULE_IDS`)
|
|
39872
|
+
* read the OTHER files via the filesystem, never via batch-mates — so the
|
|
39873
|
+
* plan only moves wall-clock time.
|
|
39874
|
+
*
|
|
39875
|
+
* O(files × batches); the linear least-loaded scan is fine at real-world
|
|
39876
|
+
* scale (a 3,500-file monorepo is 36 batches ≈ 126k comparisons).
|
|
39877
|
+
*/
|
|
39878
|
+
const planLintBatches = ({ baseArgs, files, sizeByFile }) => {
|
|
39879
|
+
if (files.length === 0) return [];
|
|
39880
|
+
const batchCount = Math.ceil(files.length / 100);
|
|
39881
|
+
const maxFilesPerBatch = Math.ceil(files.length / batchCount);
|
|
39882
|
+
const baseArgsLengthChars = estimateArgsLength(baseArgs);
|
|
39883
|
+
const descendingSizeIndexes = [...files.keys()].sort((leftIndex, rightIndex) => (sizeByFile.get(files[rightIndex]) ?? 0) - (sizeByFile.get(files[leftIndex]) ?? 0));
|
|
39884
|
+
const batches = Array.from({ length: batchCount }, () => ({
|
|
39885
|
+
fileIndexes: [],
|
|
39886
|
+
totalSizeBytes: 0,
|
|
39887
|
+
argsLengthChars: baseArgsLengthChars
|
|
39888
|
+
}));
|
|
39889
|
+
for (const fileIndex of descendingSizeIndexes) {
|
|
39890
|
+
const entryLengthChars = files[fileIndex].length + 1;
|
|
39891
|
+
let targetBatch = null;
|
|
39892
|
+
for (const batch of batches) {
|
|
39893
|
+
if (batch.fileIndexes.length >= maxFilesPerBatch) continue;
|
|
39894
|
+
if (batch.fileIndexes.length > 0 && batch.argsLengthChars + entryLengthChars > 24e3) continue;
|
|
39895
|
+
if (targetBatch === null || batch.totalSizeBytes < targetBatch.totalSizeBytes || batch.totalSizeBytes === targetBatch.totalSizeBytes && batch.fileIndexes.length < targetBatch.fileIndexes.length) targetBatch = batch;
|
|
39896
|
+
}
|
|
39897
|
+
if (targetBatch === null) {
|
|
39898
|
+
targetBatch = {
|
|
39899
|
+
fileIndexes: [],
|
|
39900
|
+
totalSizeBytes: 0,
|
|
39901
|
+
argsLengthChars: baseArgsLengthChars
|
|
39902
|
+
};
|
|
39903
|
+
batches.push(targetBatch);
|
|
39904
|
+
}
|
|
39905
|
+
targetBatch.fileIndexes.push(fileIndex);
|
|
39906
|
+
targetBatch.totalSizeBytes += sizeByFile.get(files[fileIndex]) ?? 0;
|
|
39907
|
+
targetBatch.argsLengthChars += entryLengthChars;
|
|
39908
|
+
}
|
|
39909
|
+
return batches.filter((batch) => batch.fileIndexes.length > 0).map((batch) => batch.fileIndexes.sort((leftIndex, rightIndex) => leftIndex - rightIndex).map((fileIndex) => files[fileIndex]));
|
|
39910
|
+
};
|
|
39836
39911
|
const projectCacheSubdir = (projectDirectory) => crypto.createHash("sha256").update(projectDirectory).digest("hex").slice(0, 16);
|
|
39837
39912
|
const resolveReactDoctorCacheDir = (projectDirectory) => {
|
|
39838
39913
|
const cacheDirOverride = process.env["REACT_DOCTOR_CACHE_DIR"]?.trim();
|
|
@@ -39848,7 +39923,6 @@ const resolveUserCacheScope = () => {
|
|
|
39848
39923
|
return "shared";
|
|
39849
39924
|
}
|
|
39850
39925
|
};
|
|
39851
|
-
const sortSourceFilesByCost = (entries) => [...entries].sort((left, right) => right.sizeBytes - left.sizeBytes).map((entry) => entry.path);
|
|
39852
39926
|
/**
|
|
39853
39927
|
* Atomically (re)writes the generated oxlintrc.json. Used twice in
|
|
39854
39928
|
* the runner: once for the primary scan, once for the
|
|
@@ -39907,7 +39981,7 @@ const reactHooksJsPluginDropNote = (error) => {
|
|
|
39907
39981
|
* 6. always restore disable directives + clean up the temp dir
|
|
39908
39982
|
*/
|
|
39909
39983
|
const runOxlint = async (options) => {
|
|
39910
|
-
const { rootDirectory, project, includePaths, nodeBinaryPath = process.execPath, customRulesOnly = false, respectInlineDisables = true, adoptExistingLintConfig = true, ignoredTags = /* @__PURE__ */ new Set(), userConfig, configSourceDirectory = rootDirectory, onPartialFailure, perFileLintCacheEnabled = false, onCacheStats, spawnTimeoutMs, outputMaxBytes, lintBatchOrdering = "
|
|
39984
|
+
const { rootDirectory, project, includePaths, nodeBinaryPath = process.execPath, customRulesOnly = false, respectInlineDisables = true, adoptExistingLintConfig = true, ignoredTags = /* @__PURE__ */ new Set(), userConfig, configSourceDirectory = rootDirectory, onPartialFailure, perFileLintCacheEnabled = false, onCacheStats, spawnTimeoutMs, outputMaxBytes, lintBatchOrdering = "cost" } = options;
|
|
39911
39985
|
const serverAuthFunctionNames = Array.isArray(userConfig?.serverAuthFunctionNames) ? userConfig.serverAuthFunctionNames.filter((entry) => typeof entry === "string" && entry.length > 0) : void 0;
|
|
39912
39986
|
const severityControls = buildRuleSeverityControls(userConfig);
|
|
39913
39987
|
validateRuleRegistration();
|
|
@@ -39959,8 +40033,15 @@ const runOxlint = async (options) => {
|
|
|
39959
40033
|
"json",
|
|
39960
40034
|
...sharedArgs
|
|
39961
40035
|
];
|
|
39962
|
-
|
|
39963
|
-
const
|
|
40036
|
+
await yieldToEventLoop();
|
|
40037
|
+
const sizedScanFiles = includePaths === void 0 ? listSourceFilesWithSize(rootDirectory) : null;
|
|
40038
|
+
const candidateFiles = includePaths !== void 0 ? includePaths : (sizedScanFiles ?? []).map((entry) => entry.path);
|
|
40039
|
+
const sizeByFile = sizedScanFiles !== null && lintBatchOrdering === "cost" ? new Map(sizedScanFiles.map((entry) => [entry.path, entry.sizeBytes])) : null;
|
|
40040
|
+
const buildFileBatches = (passBaseArgs, passFiles) => sizeByFile !== null ? planLintBatches({
|
|
40041
|
+
baseArgs: passBaseArgs,
|
|
40042
|
+
files: passFiles,
|
|
40043
|
+
sizeByFile
|
|
40044
|
+
}) : batchIncludePaths(passBaseArgs, passFiles);
|
|
39964
40045
|
const runConfigOverFiles = async (buildConfigForPass, configFileName, files, fileProgress) => {
|
|
39965
40046
|
if (files.length === 0) return {
|
|
39966
40047
|
diagnostics: [],
|
|
@@ -39974,7 +40055,7 @@ const runOxlint = async (options) => {
|
|
|
39974
40055
|
};
|
|
39975
40056
|
const passConfigPath = Path.join(configDirectory, configFileName);
|
|
39976
40057
|
const passBaseArgs = makeBaseArgs(passConfigPath);
|
|
39977
|
-
const passFileBatches =
|
|
40058
|
+
const passFileBatches = buildFileBatches(passBaseArgs, files);
|
|
39978
40059
|
const spawnPass = () => spawnLintBatches({
|
|
39979
40060
|
baseArgs: passBaseArgs,
|
|
39980
40061
|
fileBatches: passFileBatches,
|
|
@@ -40023,6 +40104,7 @@ const runOxlint = async (options) => {
|
|
|
40023
40104
|
const cacheKeyByFile = /* @__PURE__ */ new Map();
|
|
40024
40105
|
const missFiles = [];
|
|
40025
40106
|
const replayedDiagnostics = [];
|
|
40107
|
+
let hashSliceStartedAt = performance.now();
|
|
40026
40108
|
for (const candidateFile of candidateFiles) {
|
|
40027
40109
|
const contentHash = hashFileContents(Path.resolve(rootDirectory, candidateFile));
|
|
40028
40110
|
if (contentHash === null) {
|
|
@@ -40034,23 +40116,29 @@ const runOxlint = async (options) => {
|
|
|
40034
40116
|
const cachedDiagnostics = cache.lookup(cacheKey);
|
|
40035
40117
|
if (cachedDiagnostics === null) missFiles.push(candidateFile);
|
|
40036
40118
|
else replayedDiagnostics.push(...cachedDiagnostics);
|
|
40119
|
+
if (performance.now() - hashSliceStartedAt >= 12) {
|
|
40120
|
+
await yieldToEventLoop();
|
|
40121
|
+
hashSliceStartedAt = performance.now();
|
|
40122
|
+
}
|
|
40037
40123
|
}
|
|
40038
40124
|
const cacheHitFileCount = candidateFiles.length - missFiles.length;
|
|
40039
|
-
const
|
|
40125
|
+
const fullResult = await runConfigOverFiles((overrides) => buildConfig({
|
|
40040
40126
|
extendsPaths: [],
|
|
40041
|
-
ruleSelection: "cacheable",
|
|
40042
40127
|
disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin
|
|
40043
|
-
}), "oxlintrc.
|
|
40128
|
+
}), "oxlintrc.full.json", missFiles, options.onFileProgress && ((scannedFileCount) => options.onFileProgress?.(scannedFileCount, candidateFiles.length)));
|
|
40129
|
+
const missFileSet = new Set(missFiles);
|
|
40044
40130
|
const sidecarResult = await runConfigOverFiles(() => buildConfig({
|
|
40045
40131
|
extendsPaths: [],
|
|
40046
40132
|
ruleSelection: "sidecar"
|
|
40047
|
-
}), "oxlintrc.sidecar.json", candidateFiles, options.onFileProgress);
|
|
40133
|
+
}), "oxlintrc.sidecar.json", candidateFiles.filter((candidateFile) => !missFileSet.has(candidateFile)), options.onFileProgress && ((scannedFileCount) => options.onFileProgress?.(missFiles.length + scannedFileCount, candidateFiles.length)));
|
|
40134
|
+
const freshCacheableDiagnostics = [];
|
|
40135
|
+
for (const diagnostic of fullResult.diagnostics) if (!CROSS_FILE_RULE_IDS.has(diagnostic.rule)) freshCacheableDiagnostics.push(diagnostic);
|
|
40048
40136
|
onCacheStats?.(cacheHitFileCount, candidateFiles.length);
|
|
40049
40137
|
const missFileByNormalizedPath = /* @__PURE__ */ new Map();
|
|
40050
40138
|
for (const missFile of missFiles) missFileByNormalizedPath.set(missFile.replaceAll("\\", "/"), missFile);
|
|
40051
40139
|
const freshDiagnosticsByFile = /* @__PURE__ */ new Map();
|
|
40052
40140
|
let isAttributionSound = true;
|
|
40053
|
-
for (const diagnostic of
|
|
40141
|
+
for (const diagnostic of freshCacheableDiagnostics) {
|
|
40054
40142
|
const missFile = missFileByNormalizedPath.get(diagnostic.filePath);
|
|
40055
40143
|
if (missFile === void 0) {
|
|
40056
40144
|
isAttributionSound = false;
|
|
@@ -40060,7 +40148,7 @@ const runOxlint = async (options) => {
|
|
|
40060
40148
|
fileDiagnostics.push(diagnostic);
|
|
40061
40149
|
freshDiagnosticsByFile.set(missFile, fileDiagnostics);
|
|
40062
40150
|
}
|
|
40063
|
-
if (!
|
|
40151
|
+
if (!fullResult.didDropReactHooksJsPlugin && !fullResult.hadPartialFailure && isAttributionSound) {
|
|
40064
40152
|
for (const missFile of missFiles) {
|
|
40065
40153
|
const cacheKey = cacheKeyByFile.get(missFile);
|
|
40066
40154
|
if (cacheKey !== void 0) cache.store(cacheKey, freshDiagnosticsByFile.get(missFile) ?? []);
|
|
@@ -40069,12 +40157,12 @@ const runOxlint = async (options) => {
|
|
|
40069
40157
|
}
|
|
40070
40158
|
return dedupeDiagnostics([
|
|
40071
40159
|
...replayedDiagnostics,
|
|
40072
|
-
...
|
|
40160
|
+
...fullResult.diagnostics,
|
|
40073
40161
|
...sidecarResult.diagnostics
|
|
40074
40162
|
]);
|
|
40075
40163
|
}
|
|
40076
40164
|
const baseArgs = makeBaseArgs(configPath);
|
|
40077
|
-
const fileBatches =
|
|
40165
|
+
const fileBatches = buildFileBatches(baseArgs, candidateFiles);
|
|
40078
40166
|
const runBatches = () => spawnLintBatches({
|
|
40079
40167
|
baseArgs,
|
|
40080
40168
|
fileBatches,
|
|
@@ -41758,4 +41846,4 @@ const toJsonReport = (result, options) => buildJsonReport({
|
|
|
41758
41846
|
export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
|
|
41759
41847
|
|
|
41760
41848
|
//# sourceMappingURL=index.js.map
|
|
41761
|
-
//# debugId=
|
|
41849
|
+
//# debugId=8565c6ea-39f3-51e6-895a-ae9f7c026d0e
|
package/dist/lsp.js
CHANGED
|
@@ -35512,7 +35512,7 @@ const resolveAutoScanConcurrency = (facts = readSystemFacts$1()) => {
|
|
|
35512
35512
|
return resolveScanConcurrency(Math.min(facts.availableCores, memoryBoundedWorkers));
|
|
35513
35513
|
};
|
|
35514
35514
|
const resolveLintBatchOrdering = () => {
|
|
35515
|
-
return process.env["REACT_DOCTOR_LINT_BATCH_ORDERING"]?.trim().toLowerCase() === "
|
|
35515
|
+
return process.env["REACT_DOCTOR_LINT_BATCH_ORDERING"]?.trim().toLowerCase() === "arrival" ? "arrival" : "cost";
|
|
35516
35516
|
};
|
|
35517
35517
|
/**
|
|
35518
35518
|
* Per-batch oxlint wall-clock budget. Reads from the env var on
|
|
@@ -35609,17 +35609,19 @@ var DeadCodeOverlap = class extends Reference("react-doctor/DeadCodeOverlap", {
|
|
|
35609
35609
|
return "auto";
|
|
35610
35610
|
} }) {};
|
|
35611
35611
|
/**
|
|
35612
|
-
* How the full-scan lint pass
|
|
35613
|
-
*
|
|
35614
|
-
*
|
|
35615
|
-
*
|
|
35616
|
-
* the
|
|
35617
|
-
*
|
|
35618
|
-
*
|
|
35619
|
-
*
|
|
35620
|
-
* `
|
|
35621
|
-
*
|
|
35622
|
-
*
|
|
35612
|
+
* How the full-scan lint pass plans its file batches. `"cost"` (the default)
|
|
35613
|
+
* builds size-balanced LPT batches (`planLintBatches`): the same mandatory
|
|
35614
|
+
* batch count as greedy chunking (`ceil(files / 100)`), but every batch gets
|
|
35615
|
+
* an even share of files AND bytes, so no 100-file chunk is a straggler while
|
|
35616
|
+
* the remainder-batch worker idles — and the heavy files are SPREAD across
|
|
35617
|
+
* batches, the precondition the old sort-desc-then-chunk-100 `cost` mode
|
|
35618
|
+
* lacked (it packed the heaviest files into one wave-1 straggler batch,
|
|
35619
|
+
* measurably regressing size-skewed repos, which is why it never earned the
|
|
35620
|
+
* default). `"arrival"` (`REACT_DOCTOR_LINT_BATCH_ORDERING=arrival`) is the
|
|
35621
|
+
* rollback hatch to plain greedy 100-file chunking in discovery order. Tests
|
|
35622
|
+
* override via `Layer.succeed(LintBatchOrdering, ...)`. Diff / staged scans
|
|
35623
|
+
* never reach this — they pass user-scoped `includePaths` that skip discovery
|
|
35624
|
+
* and stay in arrival order; only the full-scan branch reads it.
|
|
35623
35625
|
*/
|
|
35624
35626
|
var LintBatchOrdering = class extends Reference("react-doctor/LintBatchOrdering", { defaultValue: resolveLintBatchOrdering }) {};
|
|
35625
35627
|
const CACHE_DISABLED_VALUES = new Set(["1", "true"]);
|
|
@@ -36928,6 +36930,7 @@ function* collectSecurityScanFiles(rootDirectory) {
|
|
|
36928
36930
|
isGeneratedBundleByName: classification.isGeneratedBundleByName
|
|
36929
36931
|
});
|
|
36930
36932
|
}
|
|
36933
|
+
yield null;
|
|
36931
36934
|
}
|
|
36932
36935
|
for (const candidates of [
|
|
36933
36936
|
priorityCandidates,
|
|
@@ -37031,31 +37034,39 @@ const createSecurityScanSession = (rootDirectory, options) => {
|
|
|
37031
37034
|
}
|
|
37032
37035
|
return status === true;
|
|
37033
37036
|
};
|
|
37034
|
-
const
|
|
37035
|
-
for (const { entry, scan, committedFilesOnly } of enabledScanRules)
|
|
37036
|
-
|
|
37037
|
-
|
|
37038
|
-
|
|
37039
|
-
|
|
37040
|
-
|
|
37041
|
-
|
|
37037
|
+
const scanFileByRule = function* (file) {
|
|
37038
|
+
for (const { entry, scan, committedFilesOnly } of enabledScanRules) {
|
|
37039
|
+
for (const finding of scan(file)) {
|
|
37040
|
+
if (committedFilesOnly && isFileGitIgnored(file)) continue;
|
|
37041
|
+
const diagnostic = buildSecurityScanDiagnostic(finding, entry, file.relativePath);
|
|
37042
|
+
const key = `${diagnostic.rule}:${diagnostic.filePath}:${diagnostic.line}:${diagnostic.column}:${diagnostic.message}`;
|
|
37043
|
+
if (seen.has(key)) continue;
|
|
37044
|
+
seen.add(key);
|
|
37045
|
+
diagnostics.push(diagnostic);
|
|
37046
|
+
}
|
|
37047
|
+
yield;
|
|
37042
37048
|
}
|
|
37043
37049
|
};
|
|
37044
37050
|
return {
|
|
37045
|
-
|
|
37051
|
+
scanFileByRule,
|
|
37046
37052
|
diagnostics
|
|
37047
37053
|
};
|
|
37048
37054
|
};
|
|
37049
37055
|
const checkSecurityScanCooperative = async (rootDirectory, options = {}) => {
|
|
37050
37056
|
const session = createSecurityScanSession(rootDirectory, options);
|
|
37051
37057
|
if (session === null) return [];
|
|
37052
|
-
let
|
|
37058
|
+
let sliceStartedAt = performance.now();
|
|
37053
37059
|
for (const file of collectSecurityScanFiles(rootDirectory)) {
|
|
37054
|
-
|
|
37055
|
-
|
|
37056
|
-
|
|
37057
|
-
|
|
37060
|
+
if (file === null) {
|
|
37061
|
+
if (performance.now() - sliceStartedAt >= 12) {
|
|
37062
|
+
await yieldToEventLoop();
|
|
37063
|
+
sliceStartedAt = performance.now();
|
|
37064
|
+
}
|
|
37065
|
+
continue;
|
|
37066
|
+
}
|
|
37067
|
+
for (const _ruleStep of session.scanFileByRule(file)) if (performance.now() - sliceStartedAt >= 12) {
|
|
37058
37068
|
await yieldToEventLoop();
|
|
37069
|
+
sliceStartedAt = performance.now();
|
|
37059
37070
|
}
|
|
37060
37071
|
}
|
|
37061
37072
|
return session.diagnostics;
|
|
@@ -39818,6 +39829,70 @@ const hashFileContents = (filePath) => {
|
|
|
39818
39829
|
return null;
|
|
39819
39830
|
}
|
|
39820
39831
|
};
|
|
39832
|
+
/**
|
|
39833
|
+
* Balanced LPT (longest-processing-time-first) lint batch planner. Where
|
|
39834
|
+
* `batchIncludePaths` greedily fills 100-file batches and leaves a small
|
|
39835
|
+
* remainder batch (617 files → 6×100 + 17, so the parallel pool's wall clock
|
|
39836
|
+
* is set by whichever 100-file chunk happened to collect the most work while
|
|
39837
|
+
* the remainder's worker idles), this planner keeps the SAME mandatory batch
|
|
39838
|
+
* count — `ceil(files / OXLINT_MAX_FILES_PER_BATCH)`, the JS-plugin
|
|
39839
|
+
* perf-cliff / native-binding SIGABRT guard — and assigns files largest-first,
|
|
39840
|
+
* each to the least-loaded batch by cumulative byte size. Every batch ends up
|
|
39841
|
+
* with an even share of files AND bytes, so no batch is a straggler; heavy
|
|
39842
|
+
* files are SPREAD across batches (the documented precondition for `cost`
|
|
39843
|
+
* ordering to beat `arrival`) instead of concentrated in one.
|
|
39844
|
+
*
|
|
39845
|
+
* Deliberately NOT worker-count-aware: splitting into extra batches to feed
|
|
39846
|
+
* idle workers was measured (619-file corpus, 10 cores) to REGRESS the lint
|
|
39847
|
+
* wall — each additional concurrent subprocess pays a contended cold start
|
|
39848
|
+
* (node boot + plugin import + native dlopen, ~180 ms uncontended and 2-3×
|
|
39849
|
+
* that in a full wave) that outweighs the smaller per-batch file share.
|
|
39850
|
+
*
|
|
39851
|
+
* Every batch also respects the spawn-args char budget; a file that fits no
|
|
39852
|
+
* existing batch opens a new one, mirroring `batchIncludePaths`. Within a
|
|
39853
|
+
* batch, files return in arrival order, and the assignment is fully
|
|
39854
|
+
* deterministic (ties broken by lowest batch index), so a plan is
|
|
39855
|
+
* reproducible for a fixed (files, sizes) input. Batch composition cannot
|
|
39856
|
+
* change diagnostics — rules that look beyond one file (`CROSS_FILE_RULE_IDS`)
|
|
39857
|
+
* read the OTHER files via the filesystem, never via batch-mates — so the
|
|
39858
|
+
* plan only moves wall-clock time.
|
|
39859
|
+
*
|
|
39860
|
+
* O(files × batches); the linear least-loaded scan is fine at real-world
|
|
39861
|
+
* scale (a 3,500-file monorepo is 36 batches ≈ 126k comparisons).
|
|
39862
|
+
*/
|
|
39863
|
+
const planLintBatches = ({ baseArgs, files, sizeByFile }) => {
|
|
39864
|
+
if (files.length === 0) return [];
|
|
39865
|
+
const batchCount = Math.ceil(files.length / 100);
|
|
39866
|
+
const maxFilesPerBatch = Math.ceil(files.length / batchCount);
|
|
39867
|
+
const baseArgsLengthChars = estimateArgsLength(baseArgs);
|
|
39868
|
+
const descendingSizeIndexes = [...files.keys()].sort((leftIndex, rightIndex) => (sizeByFile.get(files[rightIndex]) ?? 0) - (sizeByFile.get(files[leftIndex]) ?? 0));
|
|
39869
|
+
const batches = Array.from({ length: batchCount }, () => ({
|
|
39870
|
+
fileIndexes: [],
|
|
39871
|
+
totalSizeBytes: 0,
|
|
39872
|
+
argsLengthChars: baseArgsLengthChars
|
|
39873
|
+
}));
|
|
39874
|
+
for (const fileIndex of descendingSizeIndexes) {
|
|
39875
|
+
const entryLengthChars = files[fileIndex].length + 1;
|
|
39876
|
+
let targetBatch = null;
|
|
39877
|
+
for (const batch of batches) {
|
|
39878
|
+
if (batch.fileIndexes.length >= maxFilesPerBatch) continue;
|
|
39879
|
+
if (batch.fileIndexes.length > 0 && batch.argsLengthChars + entryLengthChars > 24e3) continue;
|
|
39880
|
+
if (targetBatch === null || batch.totalSizeBytes < targetBatch.totalSizeBytes || batch.totalSizeBytes === targetBatch.totalSizeBytes && batch.fileIndexes.length < targetBatch.fileIndexes.length) targetBatch = batch;
|
|
39881
|
+
}
|
|
39882
|
+
if (targetBatch === null) {
|
|
39883
|
+
targetBatch = {
|
|
39884
|
+
fileIndexes: [],
|
|
39885
|
+
totalSizeBytes: 0,
|
|
39886
|
+
argsLengthChars: baseArgsLengthChars
|
|
39887
|
+
};
|
|
39888
|
+
batches.push(targetBatch);
|
|
39889
|
+
}
|
|
39890
|
+
targetBatch.fileIndexes.push(fileIndex);
|
|
39891
|
+
targetBatch.totalSizeBytes += sizeByFile.get(files[fileIndex]) ?? 0;
|
|
39892
|
+
targetBatch.argsLengthChars += entryLengthChars;
|
|
39893
|
+
}
|
|
39894
|
+
return batches.filter((batch) => batch.fileIndexes.length > 0).map((batch) => batch.fileIndexes.sort((leftIndex, rightIndex) => leftIndex - rightIndex).map((fileIndex) => files[fileIndex]));
|
|
39895
|
+
};
|
|
39821
39896
|
const projectCacheSubdir = (projectDirectory) => crypto.createHash("sha256").update(projectDirectory).digest("hex").slice(0, 16);
|
|
39822
39897
|
const resolveReactDoctorCacheDir = (projectDirectory) => {
|
|
39823
39898
|
const cacheDirOverride = process.env["REACT_DOCTOR_CACHE_DIR"]?.trim();
|
|
@@ -39833,7 +39908,6 @@ const resolveUserCacheScope = () => {
|
|
|
39833
39908
|
return "shared";
|
|
39834
39909
|
}
|
|
39835
39910
|
};
|
|
39836
|
-
const sortSourceFilesByCost = (entries) => [...entries].sort((left, right) => right.sizeBytes - left.sizeBytes).map((entry) => entry.path);
|
|
39837
39911
|
/**
|
|
39838
39912
|
* Atomically (re)writes the generated oxlintrc.json. Used twice in
|
|
39839
39913
|
* the runner: once for the primary scan, once for the
|
|
@@ -39892,7 +39966,7 @@ const reactHooksJsPluginDropNote = (error) => {
|
|
|
39892
39966
|
* 6. always restore disable directives + clean up the temp dir
|
|
39893
39967
|
*/
|
|
39894
39968
|
const runOxlint = async (options) => {
|
|
39895
|
-
const { rootDirectory, project, includePaths, nodeBinaryPath = process.execPath, customRulesOnly = false, respectInlineDisables = true, adoptExistingLintConfig = true, ignoredTags = /* @__PURE__ */ new Set(), userConfig, configSourceDirectory = rootDirectory, onPartialFailure, perFileLintCacheEnabled = false, onCacheStats, spawnTimeoutMs, outputMaxBytes, lintBatchOrdering = "
|
|
39969
|
+
const { rootDirectory, project, includePaths, nodeBinaryPath = process.execPath, customRulesOnly = false, respectInlineDisables = true, adoptExistingLintConfig = true, ignoredTags = /* @__PURE__ */ new Set(), userConfig, configSourceDirectory = rootDirectory, onPartialFailure, perFileLintCacheEnabled = false, onCacheStats, spawnTimeoutMs, outputMaxBytes, lintBatchOrdering = "cost" } = options;
|
|
39896
39970
|
const serverAuthFunctionNames = Array.isArray(userConfig?.serverAuthFunctionNames) ? userConfig.serverAuthFunctionNames.filter((entry) => typeof entry === "string" && entry.length > 0) : void 0;
|
|
39897
39971
|
const severityControls = buildRuleSeverityControls(userConfig);
|
|
39898
39972
|
validateRuleRegistration();
|
|
@@ -39944,8 +40018,15 @@ const runOxlint = async (options) => {
|
|
|
39944
40018
|
"json",
|
|
39945
40019
|
...sharedArgs
|
|
39946
40020
|
];
|
|
39947
|
-
|
|
39948
|
-
const
|
|
40021
|
+
await yieldToEventLoop();
|
|
40022
|
+
const sizedScanFiles = includePaths === void 0 ? listSourceFilesWithSize(rootDirectory) : null;
|
|
40023
|
+
const candidateFiles = includePaths !== void 0 ? includePaths : (sizedScanFiles ?? []).map((entry) => entry.path);
|
|
40024
|
+
const sizeByFile = sizedScanFiles !== null && lintBatchOrdering === "cost" ? new Map(sizedScanFiles.map((entry) => [entry.path, entry.sizeBytes])) : null;
|
|
40025
|
+
const buildFileBatches = (passBaseArgs, passFiles) => sizeByFile !== null ? planLintBatches({
|
|
40026
|
+
baseArgs: passBaseArgs,
|
|
40027
|
+
files: passFiles,
|
|
40028
|
+
sizeByFile
|
|
40029
|
+
}) : batchIncludePaths(passBaseArgs, passFiles);
|
|
39949
40030
|
const runConfigOverFiles = async (buildConfigForPass, configFileName, files, fileProgress) => {
|
|
39950
40031
|
if (files.length === 0) return {
|
|
39951
40032
|
diagnostics: [],
|
|
@@ -39959,7 +40040,7 @@ const runOxlint = async (options) => {
|
|
|
39959
40040
|
};
|
|
39960
40041
|
const passConfigPath = Path.join(configDirectory, configFileName);
|
|
39961
40042
|
const passBaseArgs = makeBaseArgs(passConfigPath);
|
|
39962
|
-
const passFileBatches =
|
|
40043
|
+
const passFileBatches = buildFileBatches(passBaseArgs, files);
|
|
39963
40044
|
const spawnPass = () => spawnLintBatches({
|
|
39964
40045
|
baseArgs: passBaseArgs,
|
|
39965
40046
|
fileBatches: passFileBatches,
|
|
@@ -40008,6 +40089,7 @@ const runOxlint = async (options) => {
|
|
|
40008
40089
|
const cacheKeyByFile = /* @__PURE__ */ new Map();
|
|
40009
40090
|
const missFiles = [];
|
|
40010
40091
|
const replayedDiagnostics = [];
|
|
40092
|
+
let hashSliceStartedAt = performance.now();
|
|
40011
40093
|
for (const candidateFile of candidateFiles) {
|
|
40012
40094
|
const contentHash = hashFileContents(Path.resolve(rootDirectory, candidateFile));
|
|
40013
40095
|
if (contentHash === null) {
|
|
@@ -40019,23 +40101,29 @@ const runOxlint = async (options) => {
|
|
|
40019
40101
|
const cachedDiagnostics = cache.lookup(cacheKey);
|
|
40020
40102
|
if (cachedDiagnostics === null) missFiles.push(candidateFile);
|
|
40021
40103
|
else replayedDiagnostics.push(...cachedDiagnostics);
|
|
40104
|
+
if (performance.now() - hashSliceStartedAt >= 12) {
|
|
40105
|
+
await yieldToEventLoop();
|
|
40106
|
+
hashSliceStartedAt = performance.now();
|
|
40107
|
+
}
|
|
40022
40108
|
}
|
|
40023
40109
|
const cacheHitFileCount = candidateFiles.length - missFiles.length;
|
|
40024
|
-
const
|
|
40110
|
+
const fullResult = await runConfigOverFiles((overrides) => buildConfig({
|
|
40025
40111
|
extendsPaths: [],
|
|
40026
|
-
ruleSelection: "cacheable",
|
|
40027
40112
|
disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin
|
|
40028
|
-
}), "oxlintrc.
|
|
40113
|
+
}), "oxlintrc.full.json", missFiles, options.onFileProgress && ((scannedFileCount) => options.onFileProgress?.(scannedFileCount, candidateFiles.length)));
|
|
40114
|
+
const missFileSet = new Set(missFiles);
|
|
40029
40115
|
const sidecarResult = await runConfigOverFiles(() => buildConfig({
|
|
40030
40116
|
extendsPaths: [],
|
|
40031
40117
|
ruleSelection: "sidecar"
|
|
40032
|
-
}), "oxlintrc.sidecar.json", candidateFiles, options.onFileProgress);
|
|
40118
|
+
}), "oxlintrc.sidecar.json", candidateFiles.filter((candidateFile) => !missFileSet.has(candidateFile)), options.onFileProgress && ((scannedFileCount) => options.onFileProgress?.(missFiles.length + scannedFileCount, candidateFiles.length)));
|
|
40119
|
+
const freshCacheableDiagnostics = [];
|
|
40120
|
+
for (const diagnostic of fullResult.diagnostics) if (!CROSS_FILE_RULE_IDS.has(diagnostic.rule)) freshCacheableDiagnostics.push(diagnostic);
|
|
40033
40121
|
onCacheStats?.(cacheHitFileCount, candidateFiles.length);
|
|
40034
40122
|
const missFileByNormalizedPath = /* @__PURE__ */ new Map();
|
|
40035
40123
|
for (const missFile of missFiles) missFileByNormalizedPath.set(missFile.replaceAll("\\", "/"), missFile);
|
|
40036
40124
|
const freshDiagnosticsByFile = /* @__PURE__ */ new Map();
|
|
40037
40125
|
let isAttributionSound = true;
|
|
40038
|
-
for (const diagnostic of
|
|
40126
|
+
for (const diagnostic of freshCacheableDiagnostics) {
|
|
40039
40127
|
const missFile = missFileByNormalizedPath.get(diagnostic.filePath);
|
|
40040
40128
|
if (missFile === void 0) {
|
|
40041
40129
|
isAttributionSound = false;
|
|
@@ -40045,7 +40133,7 @@ const runOxlint = async (options) => {
|
|
|
40045
40133
|
fileDiagnostics.push(diagnostic);
|
|
40046
40134
|
freshDiagnosticsByFile.set(missFile, fileDiagnostics);
|
|
40047
40135
|
}
|
|
40048
|
-
if (!
|
|
40136
|
+
if (!fullResult.didDropReactHooksJsPlugin && !fullResult.hadPartialFailure && isAttributionSound) {
|
|
40049
40137
|
for (const missFile of missFiles) {
|
|
40050
40138
|
const cacheKey = cacheKeyByFile.get(missFile);
|
|
40051
40139
|
if (cacheKey !== void 0) cache.store(cacheKey, freshDiagnosticsByFile.get(missFile) ?? []);
|
|
@@ -40054,12 +40142,12 @@ const runOxlint = async (options) => {
|
|
|
40054
40142
|
}
|
|
40055
40143
|
return dedupeDiagnostics([
|
|
40056
40144
|
...replayedDiagnostics,
|
|
40057
|
-
...
|
|
40145
|
+
...fullResult.diagnostics,
|
|
40058
40146
|
...sidecarResult.diagnostics
|
|
40059
40147
|
]);
|
|
40060
40148
|
}
|
|
40061
40149
|
const baseArgs = makeBaseArgs(configPath);
|
|
40062
|
-
const fileBatches =
|
|
40150
|
+
const fileBatches = buildFileBatches(baseArgs, candidateFiles);
|
|
40063
40151
|
const runBatches = () => spawnLintBatches({
|
|
40064
40152
|
baseArgs,
|
|
40065
40153
|
fileBatches,
|
|
@@ -41432,7 +41520,7 @@ const computeConfigFingerprint = (projectDirectory, version) => {
|
|
|
41432
41520
|
/** Display name used in client-facing messages and progress titles. */
|
|
41433
41521
|
const SERVER_DISPLAY_NAME = "React Doctor";
|
|
41434
41522
|
/** Server version reported in `serverInfo`; injected at build, `dev` from source. */
|
|
41435
|
-
const SERVER_VERSION = "0.6.
|
|
41523
|
+
const SERVER_VERSION = "0.6.2";
|
|
41436
41524
|
/** `Diagnostic.source` shown next to every published diagnostic. */
|
|
41437
41525
|
const DIAGNOSTIC_SOURCE = "react-doctor";
|
|
41438
41526
|
/**
|
|
@@ -43482,5 +43570,5 @@ const startLanguageServer = () => {
|
|
|
43482
43570
|
};
|
|
43483
43571
|
//#endregion
|
|
43484
43572
|
export { startLanguageServer };
|
|
43485
|
-
!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]="
|
|
43486
|
-
//# debugId=
|
|
43573
|
+
!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]="ec0a0a63-f78d-58b0-abbd-1faf2b2f859a")}catch(e){}}();
|
|
43574
|
+
//# debugId=ec0a0a63-f78d-58b0-abbd-1faf2b2f859a
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-doctor",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2-dev.173cc0a",
|
|
4
4
|
"description": "Your agent writes bad React. This catches it",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"accessibility",
|
|
@@ -65,8 +65,8 @@
|
|
|
65
65
|
"vscode-languageserver-textdocument": "^1.0.12",
|
|
66
66
|
"vscode-uri": "^3.1.0",
|
|
67
67
|
"yaml": "^2.9.0",
|
|
68
|
-
"deslop-js": "0.6.
|
|
69
|
-
"oxlint-plugin-react-doctor": "0.6.
|
|
68
|
+
"deslop-js": "0.6.2",
|
|
69
|
+
"oxlint-plugin-react-doctor": "0.6.2-dev.173cc0a"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@types/babel__code-frame": "^7.27.0",
|
|
@@ -74,9 +74,9 @@
|
|
|
74
74
|
"@xterm/headless": "^6.0.0",
|
|
75
75
|
"commander": "^14.0.3",
|
|
76
76
|
"ora": "^9.4.0",
|
|
77
|
-
"@react-doctor/
|
|
78
|
-
"@react-doctor/
|
|
79
|
-
"@react-doctor/language-server": "0.6.
|
|
77
|
+
"@react-doctor/core": "0.6.2",
|
|
78
|
+
"@react-doctor/api": "0.6.2",
|
|
79
|
+
"@react-doctor/language-server": "0.6.2"
|
|
80
80
|
},
|
|
81
81
|
"engines": {
|
|
82
82
|
"node": "^20.19.0 || >=22.13.0"
|