react-doctor 0.5.7 → 0.5.8-dev.0c19858
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 +1759 -441
- package/dist/index.d.ts +15 -1
- package/dist/index.js +1082 -196
- package/dist/lsp.js +1083 -196
- package/package.json +6 -6
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]="b9d1a98e-f5ec-5357-a08b-f84864fdfa20")}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";
|
|
@@ -9,7 +9,7 @@ import path from "node:path";
|
|
|
9
9
|
import * as NodeChildProcess from "node:child_process";
|
|
10
10
|
import { spawn, spawnSync } from "node:child_process";
|
|
11
11
|
import * as ts from "typescript";
|
|
12
|
-
import reactDoctorPlugin, { ALL_REACT_DOCTOR_RULE_KEYS, FRAMEWORK_SPECIFIC_RULE_KEYS, MOTION_LIBRARY_PACKAGES, REACT_COMPILER_RULES, REACT_DOCTOR_RULES, classifySecurityScanFile, shouldReadSecurityScanContent } from "oxlint-plugin-react-doctor";
|
|
12
|
+
import reactDoctorPlugin, { ALL_REACT_DOCTOR_RULE_KEYS, CROSS_FILE_RULE_IDS, FRAMEWORK_SPECIFIC_RULE_KEYS, MOTION_LIBRARY_PACKAGES, REACT_COMPILER_RULES, REACT_DOCTOR_RULES, classifySecurityScanFile, shouldReadSecurityScanContent } from "oxlint-plugin-react-doctor";
|
|
13
13
|
import * as OS from "node:os";
|
|
14
14
|
import os from "node:os";
|
|
15
15
|
import { parseJSON5 } from "confbox";
|
|
@@ -17,7 +17,7 @@ import * as NodeUrl from "node:url";
|
|
|
17
17
|
import { fileURLToPath } from "node:url";
|
|
18
18
|
import { createJiti } from "jiti";
|
|
19
19
|
import * as Crypto from "node:crypto";
|
|
20
|
-
import { createHash } from "node:crypto";
|
|
20
|
+
import crypto, { createHash } from "node:crypto";
|
|
21
21
|
import { gzipSync } from "node:zlib";
|
|
22
22
|
//#region ../../node_modules/.pnpm/effect@4.0.0-beta.70/node_modules/effect/dist/Pipeable.js
|
|
23
23
|
/**
|
|
@@ -5996,7 +5996,7 @@ const composePassthrough = /* @__PURE__ */ dual(2, (left, right) => (input) => {
|
|
|
5996
5996
|
* @since 2.0.0
|
|
5997
5997
|
*/
|
|
5998
5998
|
const Scheduler = /* @__PURE__ */ Reference("effect/Scheduler", { defaultValue: () => new MixedScheduler() });
|
|
5999
|
-
const setImmediate = "setImmediate" in globalThis ? (f) => {
|
|
5999
|
+
const setImmediate$1 = "setImmediate" in globalThis ? (f) => {
|
|
6000
6000
|
const timer = globalThis.setImmediate(f);
|
|
6001
6001
|
return () => globalThis.clearImmediate(timer);
|
|
6002
6002
|
} : (f) => {
|
|
@@ -6040,7 +6040,7 @@ var PriorityBuckets = class {
|
|
|
6040
6040
|
var MixedScheduler = class {
|
|
6041
6041
|
executionMode;
|
|
6042
6042
|
setImmediate;
|
|
6043
|
-
constructor(executionMode = "async", setImmediateFn = setImmediate) {
|
|
6043
|
+
constructor(executionMode = "async", setImmediateFn = setImmediate$1) {
|
|
6044
6044
|
this.executionMode = executionMode;
|
|
6045
6045
|
this.setImmediate = setImmediateFn;
|
|
6046
6046
|
}
|
|
@@ -6065,7 +6065,7 @@ var MixedSchedulerDispatcher = class {
|
|
|
6065
6065
|
tasks = /* @__PURE__ */ new PriorityBuckets();
|
|
6066
6066
|
running = void 0;
|
|
6067
6067
|
setImmediate;
|
|
6068
|
-
constructor(setImmediateFn = setImmediate) {
|
|
6068
|
+
constructor(setImmediateFn = setImmediate$1) {
|
|
6069
6069
|
this.setImmediate = setImmediateFn;
|
|
6070
6070
|
}
|
|
6071
6071
|
/**
|
|
@@ -7199,7 +7199,7 @@ const provideContext$1 = /* @__PURE__ */ dual(2, (self, context) => {
|
|
|
7199
7199
|
return updateContext$1(self, merge$3(context));
|
|
7200
7200
|
});
|
|
7201
7201
|
/** @internal */
|
|
7202
|
-
const provideService$
|
|
7202
|
+
const provideService$3 = function() {
|
|
7203
7203
|
if (arguments.length === 1) return dual(2, (self, impl) => provideServiceImpl(self, arguments[0], impl));
|
|
7204
7204
|
return dual(3, (self, service, impl) => provideServiceImpl(self, service, impl)).apply(this, arguments);
|
|
7205
7205
|
};
|
|
@@ -7430,7 +7430,7 @@ const constScopeEmpty = { _tag: "Empty" };
|
|
|
7430
7430
|
/** @internal */
|
|
7431
7431
|
const scope = scopeTag;
|
|
7432
7432
|
/** @internal */
|
|
7433
|
-
const provideScope = /* @__PURE__ */ provideService$
|
|
7433
|
+
const provideScope = /* @__PURE__ */ provideService$3(scopeTag);
|
|
7434
7434
|
/** @internal */
|
|
7435
7435
|
const scoped$1 = (self) => withFiber$1((fiber) => {
|
|
7436
7436
|
const prev = fiber.context;
|
|
@@ -7871,7 +7871,7 @@ const makeLatchUnsafe = (open) => new Latch(open ?? false);
|
|
|
7871
7871
|
/** @internal */
|
|
7872
7872
|
const makeLatch = (open) => sync$2(() => makeLatchUnsafe(open));
|
|
7873
7873
|
/** @internal */
|
|
7874
|
-
const withTracerEnabled$1 = /* @__PURE__ */ provideService$
|
|
7874
|
+
const withTracerEnabled$1 = /* @__PURE__ */ provideService$3(TracerEnabled);
|
|
7875
7875
|
const bigint0 = /* @__PURE__ */ BigInt(0);
|
|
7876
7876
|
const NoopSpanProto = {
|
|
7877
7877
|
_tag: "Span",
|
|
@@ -7952,7 +7952,7 @@ const useSpan$1 = (name, ...args) => {
|
|
|
7952
7952
|
}));
|
|
7953
7953
|
});
|
|
7954
7954
|
};
|
|
7955
|
-
const provideParentSpan = /* @__PURE__ */ provideService$
|
|
7955
|
+
const provideParentSpan = /* @__PURE__ */ provideService$3(ParentSpan);
|
|
7956
7956
|
/** @internal */
|
|
7957
7957
|
const withParentSpan$1 = function() {
|
|
7958
7958
|
const dataFirst = isEffect$1(arguments[0]);
|
|
@@ -9519,7 +9519,7 @@ var CurrentMemoMap = class extends Service()("effect/Layer/CurrentMemoMap") {
|
|
|
9519
9519
|
* @category memo map
|
|
9520
9520
|
* @since 2.0.0
|
|
9521
9521
|
*/
|
|
9522
|
-
const buildWithMemoMap = /* @__PURE__ */ dual(3, (self, memoMap, scope) => provideService$
|
|
9522
|
+
const buildWithMemoMap = /* @__PURE__ */ dual(3, (self, memoMap, scope) => provideService$3(map$4(self.build(memoMap, scope), add(CurrentMemoMap, memoMap)), CurrentMemoMap, memoMap));
|
|
9523
9523
|
/**
|
|
9524
9524
|
* Builds a layer into an `Effect` value. Any resources associated with this
|
|
9525
9525
|
* layer will be released when the specified scope is closed unless their scope
|
|
@@ -10872,7 +10872,7 @@ const provide$1 = /* @__PURE__ */ dual((args) => isEffect$1(args[0]), (self, sou
|
|
|
10872
10872
|
/** @internal */
|
|
10873
10873
|
const repeatOrElse = /* @__PURE__ */ dual(3, (self, schedule, orElse) => flatMap$4(toStepWithMetadata(schedule), (step) => {
|
|
10874
10874
|
let meta = CurrentMetadata.defaultValue();
|
|
10875
|
-
return catch_$2(forever$2(tap$2(flatMap$4(suspend$3(() => provideService$
|
|
10875
|
+
return catch_$2(forever$2(tap$2(flatMap$4(suspend$3(() => provideService$3(self, CurrentMetadata, meta)), step), (meta_) => sync$2(() => {
|
|
10876
10876
|
meta = meta_;
|
|
10877
10877
|
})), { disableYield: true }), (error) => isDone$2(error) ? succeed$5(error.value) : orElse(error, meta.attempt === 0 ? none() : some(meta)));
|
|
10878
10878
|
}));
|
|
@@ -10880,7 +10880,7 @@ const repeatOrElse = /* @__PURE__ */ dual(3, (self, schedule, orElse) => flatMap
|
|
|
10880
10880
|
const retryOrElse = /* @__PURE__ */ dual(3, (self, policy, orElse) => flatMap$4(toStepWithMetadata(policy), (step) => {
|
|
10881
10881
|
let meta = CurrentMetadata.defaultValue();
|
|
10882
10882
|
let lastError;
|
|
10883
|
-
const loop = catch_$2(suspend$3(() => provideService$
|
|
10883
|
+
const loop = catch_$2(suspend$3(() => provideService$3(self, CurrentMetadata, meta)), (error) => {
|
|
10884
10884
|
lastError = error;
|
|
10885
10885
|
return flatMap$4(step(error), (meta_) => {
|
|
10886
10886
|
meta = meta_;
|
|
@@ -12996,7 +12996,7 @@ const updateContext = updateContext$1;
|
|
|
12996
12996
|
* @category Context
|
|
12997
12997
|
* @since 2.0.0
|
|
12998
12998
|
*/
|
|
12999
|
-
const provideService = provideService$
|
|
12999
|
+
const provideService$2 = provideService$3;
|
|
13000
13000
|
/**
|
|
13001
13001
|
* Scopes all resources used in this workflow to the lifetime of the workflow,
|
|
13002
13002
|
* ensuring that their finalizers are run as soon as this workflow completes
|
|
@@ -17990,6 +17990,20 @@ function decodeUnknownOption$1(schema, options) {
|
|
|
17990
17990
|
return asOption(decodeUnknownEffect(schema, options));
|
|
17991
17991
|
}
|
|
17992
17992
|
/**
|
|
17993
|
+
* Creates a synchronous decoder for `unknown` input.
|
|
17994
|
+
*
|
|
17995
|
+
* **Details**
|
|
17996
|
+
*
|
|
17997
|
+
* The returned function returns the decoded `Type` on success and throws an
|
|
17998
|
+
* `Error` with the `SchemaIssue.Issue` in its `cause` on decoding failure.
|
|
17999
|
+
*
|
|
18000
|
+
* @category decoding
|
|
18001
|
+
* @since 3.10.0
|
|
18002
|
+
*/
|
|
18003
|
+
function decodeUnknownSync$1(schema, options) {
|
|
18004
|
+
return asSync(decodeUnknownEffect(schema, options));
|
|
18005
|
+
}
|
|
18006
|
+
/**
|
|
17993
18007
|
* Creates an effectful encoder for `unknown` input.
|
|
17994
18008
|
*
|
|
17995
18009
|
* **Details**
|
|
@@ -18291,6 +18305,40 @@ function isSchemaError(u) {
|
|
|
18291
18305
|
*/
|
|
18292
18306
|
const decodeUnknownOption = decodeUnknownOption$1;
|
|
18293
18307
|
/**
|
|
18308
|
+
* Decodes an `unknown` input against a schema synchronously, returning the
|
|
18309
|
+
* decoded value or throwing an `Error` whose cause contains the schema issue.
|
|
18310
|
+
* Use this when you want to validate data at a boundary and treat a schema
|
|
18311
|
+
* mismatch as an exception. For typed input use `decodeSync`.
|
|
18312
|
+
*
|
|
18313
|
+
* **Details**
|
|
18314
|
+
*
|
|
18315
|
+
* Only service-free schemas can be decoded synchronously. For non-throwing
|
|
18316
|
+
* alternatives see `decodeUnknownOption`, `decodeUnknownExit`, or
|
|
18317
|
+
* `decodeUnknownEffect`. Options may be provided either when creating the
|
|
18318
|
+
* decoder or when applying it; application options override creation options.
|
|
18319
|
+
*
|
|
18320
|
+
* **Example** (Decoding with a transformation schema)
|
|
18321
|
+
*
|
|
18322
|
+
* ```ts
|
|
18323
|
+
* import { Schema } from "effect"
|
|
18324
|
+
*
|
|
18325
|
+
* const NumberFromString = Schema.NumberFromString
|
|
18326
|
+
*
|
|
18327
|
+
* console.log(Schema.decodeUnknownSync(NumberFromString)("42"))
|
|
18328
|
+
* // Output: 42
|
|
18329
|
+
*
|
|
18330
|
+
* Schema.decodeUnknownSync(NumberFromString)("not a number")
|
|
18331
|
+
* // throws SchemaError: NumberFromString
|
|
18332
|
+
* // └─ Encoded side transformation failure
|
|
18333
|
+
* // └─ NumberFromString
|
|
18334
|
+
* // └─ Expected a numeric string, actual "not a number"
|
|
18335
|
+
* ```
|
|
18336
|
+
*
|
|
18337
|
+
* @category decoding
|
|
18338
|
+
* @since 4.0.0
|
|
18339
|
+
*/
|
|
18340
|
+
const decodeUnknownSync = decodeUnknownSync$1;
|
|
18341
|
+
/**
|
|
18294
18342
|
* Encodes an `unknown` input against a schema synchronously, throwing a
|
|
18295
18343
|
* {@link SchemaError} on failure. Use this when you want to serialize data at a
|
|
18296
18344
|
* boundary and treat a schema mismatch as an unrecoverable error. For
|
|
@@ -25170,6 +25218,14 @@ const runWith = (self, f, onHalt) => suspend$2(() => {
|
|
|
25170
25218
|
return catchDone(flatMap$2(toTransform(self)(done$1(), scope), f), onHalt ? onHalt : succeed$2).pipe(onExit$1((exit) => close(scope, exit)));
|
|
25171
25219
|
});
|
|
25172
25220
|
/**
|
|
25221
|
+
* Provides a concrete service for a context key, removing that service
|
|
25222
|
+
* requirement from the returned channel.
|
|
25223
|
+
*
|
|
25224
|
+
* @category services
|
|
25225
|
+
* @since 2.0.0
|
|
25226
|
+
*/
|
|
25227
|
+
const provideService$1 = /* @__PURE__ */ dual(3, (self, key, service) => fromTransform$1((upstream, scope) => map$3(provideService$2(toTransform(self)(upstream, scope), key, service), provideService$2(key, service))));
|
|
25228
|
+
/**
|
|
25173
25229
|
* Runs a channel and applies an effect to each output element.
|
|
25174
25230
|
*
|
|
25175
25231
|
* **Example** (Running effects for each output)
|
|
@@ -26558,6 +26614,44 @@ const splitLines = (self) => self.channel.pipe(pipeTo(splitLines$1()), fromChann
|
|
|
26558
26614
|
*/
|
|
26559
26615
|
const ensuring = /* @__PURE__ */ dual(2, (self, finalizer) => fromChannel(ensuring$1(self.channel, finalizer)));
|
|
26560
26616
|
/**
|
|
26617
|
+
* Provides the stream with a single required service, eliminating that
|
|
26618
|
+
* requirement from its environment.
|
|
26619
|
+
*
|
|
26620
|
+
* **Example** (Providing a stream service)
|
|
26621
|
+
*
|
|
26622
|
+
* ```ts
|
|
26623
|
+
* import { Console, Context, Effect, Stream } from "effect"
|
|
26624
|
+
*
|
|
26625
|
+
* class Greeter extends Context.Service<Greeter, {
|
|
26626
|
+
* greet: (name: string) => string
|
|
26627
|
+
* }>()("Greeter") {}
|
|
26628
|
+
*
|
|
26629
|
+
* const stream = Stream.fromEffect(
|
|
26630
|
+
* Effect.service(Greeter).pipe(
|
|
26631
|
+
* Effect.map((greeter) => greeter.greet("Ada"))
|
|
26632
|
+
* )
|
|
26633
|
+
* )
|
|
26634
|
+
*
|
|
26635
|
+
* const program = Effect.gen(function*() {
|
|
26636
|
+
* const collected = yield* Stream.runCollect(
|
|
26637
|
+
* stream.pipe(
|
|
26638
|
+
* Stream.provideService(Greeter, {
|
|
26639
|
+
* greet: (name) => `Hello, ${name}`
|
|
26640
|
+
* })
|
|
26641
|
+
* )
|
|
26642
|
+
* )
|
|
26643
|
+
* yield* Console.log(collected)
|
|
26644
|
+
* })
|
|
26645
|
+
*
|
|
26646
|
+
* Effect.runPromise(program)
|
|
26647
|
+
* //=> ["Hello, Ada"]
|
|
26648
|
+
* ```
|
|
26649
|
+
*
|
|
26650
|
+
* @category services
|
|
26651
|
+
* @since 2.0.0
|
|
26652
|
+
*/
|
|
26653
|
+
const provideService = /* @__PURE__ */ dual(3, (self, key, service) => fromChannel(provideService$1(self.channel, key, service)));
|
|
26654
|
+
/**
|
|
26561
26655
|
* Runs a stream with a sink and returns the sink result.
|
|
26562
26656
|
*
|
|
26563
26657
|
* **Example** (Running a stream with a sink)
|
|
@@ -29987,7 +30081,7 @@ const make$8 = /* @__PURE__ */ fnUntraced(function* (options) {
|
|
|
29987
30081
|
const runFork = runForkWith(services);
|
|
29988
30082
|
const exportInterval = max(fromInputUnsafe(options.exportInterval), zero);
|
|
29989
30083
|
let disabledUntil = void 0;
|
|
29990
|
-
const client = filterStatusOk(get$4(services, HttpClient)).pipe(transformResponse(provideService(TracerPropagationEnabled, false)), retryTransient({
|
|
30084
|
+
const client = filterStatusOk(get$4(services, HttpClient)).pipe(transformResponse(provideService$2(TracerPropagationEnabled, false)), retryTransient({
|
|
29991
30085
|
schedule: policy,
|
|
29992
30086
|
times: 3
|
|
29993
30087
|
}));
|
|
@@ -32717,15 +32811,24 @@ const isMinifiedSource = (absolutePath) => {
|
|
|
32717
32811
|
if (fileDescriptor !== void 0) NFS.closeSync(fileDescriptor);
|
|
32718
32812
|
}
|
|
32719
32813
|
};
|
|
32720
|
-
const
|
|
32721
|
-
|
|
32814
|
+
const cachedIsLargeMinifiedByPath = /* @__PURE__ */ new Map();
|
|
32815
|
+
const clearMinifiedFileCache = () => {
|
|
32816
|
+
cachedIsLargeMinifiedByPath.clear();
|
|
32817
|
+
};
|
|
32818
|
+
const statSourceFileSize = (absolutePath) => {
|
|
32722
32819
|
try {
|
|
32723
|
-
|
|
32820
|
+
return NFS.statSync(absolutePath).size;
|
|
32724
32821
|
} catch {
|
|
32725
|
-
return
|
|
32822
|
+
return null;
|
|
32726
32823
|
}
|
|
32727
|
-
|
|
32728
|
-
|
|
32824
|
+
};
|
|
32825
|
+
const isLargeMinifiedFile = (absolutePath, knownSizeBytes) => {
|
|
32826
|
+
const cached = cachedIsLargeMinifiedByPath.get(absolutePath);
|
|
32827
|
+
if (cached !== void 0) return cached;
|
|
32828
|
+
const sizeBytes = knownSizeBytes === void 0 ? statSourceFileSize(absolutePath) : knownSizeBytes;
|
|
32829
|
+
const result = sizeBytes !== null && sizeBytes >= 2e4 && isMinifiedSource(absolutePath);
|
|
32830
|
+
cachedIsLargeMinifiedByPath.set(absolutePath, result);
|
|
32831
|
+
return result;
|
|
32729
32832
|
};
|
|
32730
32833
|
const isErrnoException = (error) => error instanceof Error && "code" in error;
|
|
32731
32834
|
const IGNORABLE_READDIR_ERROR_CODES = new Set([
|
|
@@ -33591,6 +33694,8 @@ const MILLISECONDS_PER_SECOND = 1e3;
|
|
|
33591
33694
|
const SCORE_API_URL = "https://www.react.doctor/api/score";
|
|
33592
33695
|
const FETCH_TIMEOUT_MS = 1e4;
|
|
33593
33696
|
const GITHUB_VIEWER_PERMISSION_TIMEOUT_MS = 2e3;
|
|
33697
|
+
const SPAWN_ARGS_MAX_LENGTH_CHARS = 24e3;
|
|
33698
|
+
const PER_WORKER_MEM_BUDGET_BYTES = 1024 * 1024 * 1024;
|
|
33594
33699
|
const DEFAULT_BRANCH_CANDIDATES = ["main", "master"];
|
|
33595
33700
|
const ADOPTABLE_LINT_CONFIG_FILENAMES = [".oxlintrc.json", ".eslintrc.json"];
|
|
33596
33701
|
const GIT_SHOW_MAX_BUFFER_BYTES = 10 * 1024 * 1024;
|
|
@@ -33640,7 +33745,17 @@ const STAGED_FILES_PROJECT_CONFIG_FILENAMES = [
|
|
|
33640
33745
|
];
|
|
33641
33746
|
const OXLINT_OUTPUT_MAX_BYTES = 50 * 1024 * 1024;
|
|
33642
33747
|
const OXLINT_SPAWN_TIMEOUT_MS = 6e4;
|
|
33748
|
+
const NODE_COMPILE_CACHE_DIR_NAME = "node-compile-cache";
|
|
33749
|
+
const DEAD_CODE_WORKER_TIMEOUT_MS = 12e4;
|
|
33750
|
+
const OXLINT_SPLIT_TOTAL_BUDGET_MS = 18e4;
|
|
33751
|
+
const DEAD_CODE_PHASE_TIMEOUT_MS = 15e4;
|
|
33752
|
+
const LINT_PHASE_TIMEOUT_MS = 3e5;
|
|
33753
|
+
const SCAN_TOTAL_DEADLINE_MS = 9e5;
|
|
33643
33754
|
const DEAD_CODE_WORKER_MAX_OLD_SPACE_MB = 8192;
|
|
33755
|
+
const DEAD_CODE_WORKER_MEM_BUDGET_BYTES = 2 * 1024 * 1024 * 1024;
|
|
33756
|
+
const DEAD_CODE_TIMEOUT_CEILING_MS = 6e5;
|
|
33757
|
+
const DEAD_CODE_PHASE_TIMEOUT_OVER_WORKER_MS = 3e4;
|
|
33758
|
+
const DEAD_CODE_OVERLAP_PARSE_SHARE = .4;
|
|
33644
33759
|
const RECOMMENDED_PNPM_MINIMUM_RELEASE_AGE_MINUTES = 10080;
|
|
33645
33760
|
const REACT_SERVER_DOM_PACKAGES = [
|
|
33646
33761
|
"react-server-dom-webpack",
|
|
@@ -33675,9 +33790,13 @@ const CONFIG_CACHE_TTL_MS = 300 * 1e3;
|
|
|
33675
33790
|
const SOCKET_FREE_PURL_API_BASE = "https://firewall-api.socket.dev/purl";
|
|
33676
33791
|
const SOCKET_PACKAGE_PAGE_BASE = "https://socket.dev/npm/package";
|
|
33677
33792
|
const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
|
|
33793
|
+
const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
|
|
33794
|
+
const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
|
|
33678
33795
|
const SUPPLY_CHAIN_PLUGIN = "socket";
|
|
33679
33796
|
const SUPPLY_CHAIN_RULE = "low-supply-chain-score";
|
|
33680
33797
|
const SUPPLY_CHAIN_CATEGORY = "Security";
|
|
33798
|
+
const SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS = 9e4;
|
|
33799
|
+
const SUPPLY_CHAIN_CACHE_SUBDIR = "supply-chain";
|
|
33681
33800
|
const SUPPLY_CHAIN_IGNORED_PACKAGES = new Set(["next"]);
|
|
33682
33801
|
const TSCONFIG_FILENAME = "tsconfig.json";
|
|
33683
33802
|
const isRelativeExtendsValue = (extendsValue) => extendsValue.startsWith("./") || extendsValue.startsWith("../") || Path.isAbsolute(extendsValue);
|
|
@@ -34370,7 +34489,10 @@ for (const [legacyRuleKey, nativeRuleKey] of Object.entries(LEGACY_RULE_KEY_TO_N
|
|
|
34370
34489
|
NATIVE_RULE_KEY_TO_LEGACY_RULE_KEYS.set(nativeRuleKey, aliases);
|
|
34371
34490
|
}
|
|
34372
34491
|
const getLegacyRuleKeysForNative = (ruleKey) => NATIVE_RULE_KEY_TO_LEGACY_RULE_KEYS.get(ruleKey) ?? [];
|
|
34373
|
-
const canonicalizeRuleKey = (ruleKey) =>
|
|
34492
|
+
const canonicalizeRuleKey = (ruleKey) => {
|
|
34493
|
+
const nativeRuleKey = LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY[ruleKey];
|
|
34494
|
+
return typeof nativeRuleKey === "string" ? nativeRuleKey : ruleKey;
|
|
34495
|
+
};
|
|
34374
34496
|
const isReactDoctorShortIdOf = (bareRuleKey, qualifiedRuleKey) => !bareRuleKey.includes("/") && qualifiedRuleKey === `react-doctor/${bareRuleKey}`;
|
|
34375
34497
|
const isSameRuleKey = (candidateRuleKey, targetRuleKey) => {
|
|
34376
34498
|
const canonicalCandidate = canonicalizeRuleKey(candidateRuleKey);
|
|
@@ -35036,6 +35158,11 @@ var OxlintBatchExceeded = class extends TaggedErrorClass()("OxlintBatchExceeded"
|
|
|
35036
35158
|
}
|
|
35037
35159
|
}
|
|
35038
35160
|
};
|
|
35161
|
+
var ScanDeadlineExceeded = class extends TaggedErrorClass()("ScanDeadlineExceeded", { detail: String$1 }) {
|
|
35162
|
+
get message() {
|
|
35163
|
+
return `Scan exceeded its overall time budget: ${this.detail}`;
|
|
35164
|
+
}
|
|
35165
|
+
};
|
|
35039
35166
|
var OxlintSpawnFailed = class extends TaggedErrorClass()("OxlintSpawnFailed", { cause: Unknown }) {
|
|
35040
35167
|
get message() {
|
|
35041
35168
|
return `Failed to run oxlint: ${pretty(fail$6(this.cause))}`;
|
|
@@ -35099,6 +35226,7 @@ var GitBaseBranchInvalid = class extends TaggedErrorClass()("GitBaseBranchInvali
|
|
|
35099
35226
|
const ReactDoctorErrorReason = Union([
|
|
35100
35227
|
OxlintUnavailable,
|
|
35101
35228
|
OxlintBatchExceeded,
|
|
35229
|
+
ScanDeadlineExceeded,
|
|
35102
35230
|
OxlintSpawnFailed,
|
|
35103
35231
|
OxlintOutputUnparseable,
|
|
35104
35232
|
ConfigParseFailed,
|
|
@@ -35171,15 +35299,105 @@ const layerOtlp = unwrap$3(gen(function* () {
|
|
|
35171
35299
|
}).pipe(provide$2(layer$8));
|
|
35172
35300
|
}).pipe(orDie));
|
|
35173
35301
|
/**
|
|
35174
|
-
*
|
|
35175
|
-
* `
|
|
35176
|
-
|
|
35302
|
+
* Read a positive-millisecond timeout from an env var, falling back to
|
|
35303
|
+
* `defaultMs` when the var is unset, non-finite, or not strictly positive.
|
|
35304
|
+
*/
|
|
35305
|
+
const readPositiveEnvMs = (envVarName, defaultMs) => {
|
|
35306
|
+
const rawValue = process.env[envVarName];
|
|
35307
|
+
if (rawValue === void 0) return defaultMs;
|
|
35308
|
+
const parsedValue = Number(rawValue);
|
|
35309
|
+
if (!Number.isFinite(parsedValue) || parsedValue <= 0) return defaultMs;
|
|
35310
|
+
return parsedValue;
|
|
35311
|
+
};
|
|
35312
|
+
const CGROUP_V2_MEMORY_MAX_PATH = "/sys/fs/cgroup/memory.max";
|
|
35313
|
+
const CGROUP_V1_MEMORY_LIMIT_PATH = "/sys/fs/cgroup/memory/memory.limit_in_bytes";
|
|
35314
|
+
const CGROUP_UNLIMITED_SENTINEL_BYTES = Number.MAX_SAFE_INTEGER;
|
|
35315
|
+
/**
|
|
35316
|
+
* Parses one raw cgroup memory-limit file value into a positive byte count, or
|
|
35317
|
+
* `undefined` when it represents "no limit" (the v2 `"max"` literal, an empty
|
|
35318
|
+
* read, a non-positive / non-finite value, or v1's near-2^63 unlimited
|
|
35319
|
+
* sentinel). Pure and exported so the classification is unit-testable without
|
|
35320
|
+
* touching the filesystem.
|
|
35321
|
+
*/
|
|
35322
|
+
const parseCgroupMemoryLimitBytes = (raw) => {
|
|
35323
|
+
if (raw === void 0) return void 0;
|
|
35324
|
+
const trimmed = raw.trim();
|
|
35325
|
+
if (trimmed === "" || trimmed === "max") return void 0;
|
|
35326
|
+
const parsed = Number(trimmed);
|
|
35327
|
+
if (!Number.isFinite(parsed) || parsed <= 0 || parsed >= CGROUP_UNLIMITED_SENTINEL_BYTES) return;
|
|
35328
|
+
return parsed;
|
|
35329
|
+
};
|
|
35330
|
+
const CGROUP_MEMORY_LIMIT_PATHS = [CGROUP_V2_MEMORY_MAX_PATH, CGROUP_V1_MEMORY_LIMIT_PATH];
|
|
35331
|
+
/**
|
|
35332
|
+
* Reads this process's cgroup memory limit in bytes from the first candidate
|
|
35333
|
+
* path that yields a real limit, or `undefined` when none does — no cgroup, no
|
|
35334
|
+
* limit, or the files are unreadable (e.g. macOS / Windows dev machines).
|
|
35335
|
+
* `os.totalmem()` reports the HOST total and ignores cgroup memory limits, so a
|
|
35336
|
+
* memory-constrained container over-reports total memory; `resolveAutoScan-
|
|
35337
|
+
* Concurrency` takes `min(totalmem, this)` to honor the limit.
|
|
35338
|
+
*
|
|
35339
|
+
* The cgroup v2 read is the mount-root `memory.max`, which IS the container's
|
|
35340
|
+
* limit under the standard cgroup-namespace setup CI runners use (the
|
|
35341
|
+
* container's own cgroup is the root of its namespaced view). A process in a
|
|
35342
|
+
* non-namespaced nested/delegated cgroup whose root reads `"max"` is not
|
|
35343
|
+
* detected here and falls back to the host total; the EAGAIN/ENOMEM serial
|
|
35344
|
+
* replay in `spawnLintBatches` remains the runtime backstop for that case.
|
|
35345
|
+
*
|
|
35346
|
+
* `candidatePaths` is injectable so tests exercise the v2-wins-over-v1
|
|
35347
|
+
* precedence, the skip-unreadable fallback, and the all-missing case without a
|
|
35348
|
+
* real `/sys/fs/cgroup`.
|
|
35349
|
+
*/
|
|
35350
|
+
const readCgroupMemoryLimitBytes = (candidatePaths = CGROUP_MEMORY_LIMIT_PATHS) => {
|
|
35351
|
+
for (const limitPath of candidatePaths) {
|
|
35352
|
+
let raw;
|
|
35353
|
+
try {
|
|
35354
|
+
raw = fs.readFileSync(limitPath, "utf8");
|
|
35355
|
+
} catch {
|
|
35356
|
+
continue;
|
|
35357
|
+
}
|
|
35358
|
+
const limitBytes = parseCgroupMemoryLimitBytes(raw);
|
|
35359
|
+
if (limitBytes !== void 0) return limitBytes;
|
|
35360
|
+
}
|
|
35361
|
+
};
|
|
35362
|
+
/**
|
|
35363
|
+
* Clamps a requested lint worker count to `[MIN_SCAN_CONCURRENCY,
|
|
35364
|
+
* HARD_MAX_SCAN_CONCURRENCY]` as a finite integer. This is the explicit-pin and
|
|
35365
|
+
* spawn-boundary clamp — the memory-and-core-budgeted auto count comes from
|
|
35366
|
+
* `resolveAutoScanConcurrency`. Out-of-range or non-finite requests degrade to
|
|
35177
35367
|
* `MIN_SCAN_CONCURRENCY` rather than oversubscribing or running zero workers.
|
|
35178
35368
|
*/
|
|
35179
35369
|
const resolveScanConcurrency = (requested) => {
|
|
35180
|
-
|
|
35181
|
-
|
|
35182
|
-
|
|
35370
|
+
if (!Number.isFinite(requested) || requested < 1) return 1;
|
|
35371
|
+
return Math.min(Math.floor(requested), 32);
|
|
35372
|
+
};
|
|
35373
|
+
const readSystemFacts$1 = () => ({
|
|
35374
|
+
availableCores: os.availableParallelism(),
|
|
35375
|
+
totalMemoryBytes: os.totalmem(),
|
|
35376
|
+
cgroupMemoryLimitBytes: readCgroupMemoryLimitBytes()
|
|
35377
|
+
});
|
|
35378
|
+
/**
|
|
35379
|
+
* Auto lint-worker count: the smaller of the (cgroup-CPU-aware) core count and
|
|
35380
|
+
* the number of `PER_WORKER_MEM_BUDGET_BYTES` workers that fit in available
|
|
35381
|
+
* memory, then clamped to `[MIN, HARD_MAX]` by `resolveScanConcurrency`.
|
|
35382
|
+
*
|
|
35383
|
+
* `os.availableParallelism()` already respects cgroup CPU quotas, so the core
|
|
35384
|
+
* term needs no help. Available memory is `os.totalmem()` floored by the cgroup
|
|
35385
|
+
* memory limit — `os.freemem()` is deliberately NOT used: it excludes
|
|
35386
|
+
* reclaimable page cache and reads near-zero on macOS / cache-heavy Linux, which
|
|
35387
|
+
* would collapse the auto path to a single worker. `os.totalmem()` reports the
|
|
35388
|
+
* host total even inside a container, so the cgroup limit (read directly,
|
|
35389
|
+
* because Node doesn't fold it into `totalmem()`) is the real ceiling there.
|
|
35390
|
+
*
|
|
35391
|
+
* `facts` is injectable so tests exercise core-bound, memory-bound, cgroup-
|
|
35392
|
+
* limited, and ceiling cases without mocking `os` or the filesystem.
|
|
35393
|
+
*/
|
|
35394
|
+
const resolveAutoScanConcurrency = (facts = readSystemFacts$1()) => {
|
|
35395
|
+
const availableMemoryBytes = Math.min(facts.totalMemoryBytes, facts.cgroupMemoryLimitBytes ?? Number.POSITIVE_INFINITY);
|
|
35396
|
+
const memoryBoundedWorkers = Math.floor(availableMemoryBytes / PER_WORKER_MEM_BUDGET_BYTES);
|
|
35397
|
+
return resolveScanConcurrency(Math.min(facts.availableCores, memoryBoundedWorkers));
|
|
35398
|
+
};
|
|
35399
|
+
const resolveLintBatchOrdering = () => {
|
|
35400
|
+
return process.env["REACT_DOCTOR_LINT_BATCH_ORDERING"]?.trim().toLowerCase() === "cost" ? "cost" : "arrival";
|
|
35183
35401
|
};
|
|
35184
35402
|
/**
|
|
35185
35403
|
* Per-batch oxlint wall-clock budget. Reads from the env var on
|
|
@@ -35187,11 +35405,38 @@ const resolveScanConcurrency = (requested) => {
|
|
|
35187
35405
|
* microVMs without recompiling react-doctor. Tests override via
|
|
35188
35406
|
* `Layer.succeed(OxlintSpawnTimeoutMs, ...)`.
|
|
35189
35407
|
*/
|
|
35190
|
-
var OxlintSpawnTimeoutMs = class extends Reference("react-doctor/OxlintSpawnTimeoutMs", { defaultValue: () => {
|
|
35191
|
-
|
|
35192
|
-
|
|
35408
|
+
var OxlintSpawnTimeoutMs = class extends Reference("react-doctor/OxlintSpawnTimeoutMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_OXLINT_SPAWN_TIMEOUT_MS", OXLINT_SPAWN_TIMEOUT_MS) }) {};
|
|
35409
|
+
/**
|
|
35410
|
+
* Effect-side cap on the lint phase. The env var lets CI / eval runners
|
|
35411
|
+
* raise the phase budget for slow large repos without recompiling.
|
|
35412
|
+
* Tests override via `Layer.succeed(LintPhaseTimeoutMs, ...)`.
|
|
35413
|
+
*/
|
|
35414
|
+
var LintPhaseTimeoutMs = class extends Reference("react-doctor/LintPhaseTimeoutMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_LINT_PHASE_TIMEOUT_MS", LINT_PHASE_TIMEOUT_MS) }) {};
|
|
35415
|
+
/**
|
|
35416
|
+
* Effect-side cap on the dead-code phase, sitting above the in-worker
|
|
35417
|
+
* timeout as a runtime-independent backstop. The env var raises it for
|
|
35418
|
+
* type-heavy projects; tests override via
|
|
35419
|
+
* `Layer.succeed(DeadCodePhaseTimeoutMs, ...)`.
|
|
35420
|
+
*/
|
|
35421
|
+
var DeadCodePhaseTimeoutMs = class extends Reference("react-doctor/DeadCodePhaseTimeoutMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_DEAD_CODE_PHASE_TIMEOUT_MS", DEAD_CODE_PHASE_TIMEOUT_MS) }) {};
|
|
35422
|
+
/**
|
|
35423
|
+
* Overall scan deadline backstop, bounding everything the per-phase
|
|
35424
|
+
* timeouts don't (wedged git / IO). The env var raises it for very
|
|
35425
|
+
* large repos; tests override via `Layer.succeed(ScanDeadlineMs, ...)`.
|
|
35426
|
+
*/
|
|
35427
|
+
var ScanDeadlineMs = class extends Reference("react-doctor/ScanDeadlineMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_SCAN_DEADLINE_MS", SCAN_TOTAL_DEADLINE_MS) }) {};
|
|
35428
|
+
/**
|
|
35429
|
+
* Wall-clock budget for the supply-chain check when it runs on a background
|
|
35430
|
+
* fiber overlapping the lint pass. Reads from the env var on startup so the
|
|
35431
|
+
* eval harness can raise the budget under sandbox microVMs (slower network)
|
|
35432
|
+
* without recompiling react-doctor. Tests override via
|
|
35433
|
+
* `Layer.succeed(SupplyChainOverlapTimeoutMs, ...)`.
|
|
35434
|
+
*/
|
|
35435
|
+
var SupplyChainOverlapTimeoutMs = class extends Reference("react-doctor/SupplyChainOverlapTimeoutMs", { defaultValue: () => {
|
|
35436
|
+
const raw = process.env["REACT_DOCTOR_SUPPLY_CHAIN_TIMEOUT_MS"];
|
|
35437
|
+
if (raw === void 0) return SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS;
|
|
35193
35438
|
const parsed = Number(raw);
|
|
35194
|
-
if (!Number.isFinite(parsed) || parsed <= 0) return
|
|
35439
|
+
if (!Number.isFinite(parsed) || parsed <= 0) return SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS;
|
|
35195
35440
|
return parsed;
|
|
35196
35441
|
} }) {};
|
|
35197
35442
|
/**
|
|
@@ -35202,31 +35447,93 @@ var OxlintSpawnTimeoutMs = class extends Reference("react-doctor/OxlintSpawnTime
|
|
|
35202
35447
|
*/
|
|
35203
35448
|
var OxlintOutputMaxBytes = class extends Reference("react-doctor/OxlintOutputMaxBytes", { defaultValue: () => OXLINT_OUTPUT_MAX_BYTES }) {};
|
|
35204
35449
|
/**
|
|
35205
|
-
* Number of oxlint subprocesses the lint pass runs in parallel. Defaults
|
|
35206
|
-
*
|
|
35207
|
-
* the box
|
|
35208
|
-
*
|
|
35209
|
-
*
|
|
35210
|
-
*
|
|
35211
|
-
*
|
|
35212
|
-
*
|
|
35213
|
-
*
|
|
35214
|
-
*
|
|
35450
|
+
* Number of oxlint subprocesses the lint pass runs in parallel. Defaults to a
|
|
35451
|
+
* memory-and-core-budgeted auto count (`resolveAutoScanConcurrency`) so large
|
|
35452
|
+
* repos scan fast out of the box without OOMing the native binding on a
|
|
35453
|
+
* high-core / low-memory box; `spawnLintBatches` transparently falls back to a
|
|
35454
|
+
* single worker if a parallel run still exhausts system resources. The CLI's
|
|
35455
|
+
* `--no-parallel` flag forces serial via `Layer.succeed`; the
|
|
35456
|
+
* `REACT_DOCTOR_PARALLEL` env var seeds the default for programmatic / CI
|
|
35457
|
+
* callers that never touch the flag — parallelism is opt-OUT, so only the
|
|
35458
|
+
* explicit serial values pin one worker:
|
|
35459
|
+
*
|
|
35460
|
+
* - unset / `auto` / `true` / `on` → memory-and-core-budgeted auto count
|
|
35215
35461
|
* - `0` / `false` / `off` → `1` (serial)
|
|
35216
35462
|
* - a positive integer → that many workers (clamped)
|
|
35217
|
-
* - any other value →
|
|
35463
|
+
* - any other value → memory-and-core-budgeted auto count
|
|
35218
35464
|
*
|
|
35219
35465
|
* The resolved value is always within
|
|
35220
|
-
* `[MIN_SCAN_CONCURRENCY,
|
|
35466
|
+
* `[MIN_SCAN_CONCURRENCY, HARD_MAX_SCAN_CONCURRENCY]`.
|
|
35221
35467
|
*/
|
|
35222
35468
|
var OxlintConcurrency = class extends Reference("react-doctor/OxlintConcurrency", { defaultValue: () => {
|
|
35223
35469
|
const raw = process.env["REACT_DOCTOR_PARALLEL"];
|
|
35224
|
-
if (raw === void 0) return
|
|
35470
|
+
if (raw === void 0) return resolveAutoScanConcurrency();
|
|
35225
35471
|
const normalized = raw.trim().toLowerCase();
|
|
35226
35472
|
if (normalized === "0" || normalized === "false" || normalized === "off") return 1;
|
|
35227
35473
|
const parsed = Number.parseInt(normalized, 10);
|
|
35228
35474
|
if (Number.isInteger(parsed) && parsed > 0) return resolveScanConcurrency(parsed);
|
|
35229
|
-
return
|
|
35475
|
+
return resolveAutoScanConcurrency();
|
|
35476
|
+
} }) {};
|
|
35477
|
+
/**
|
|
35478
|
+
* Three-state control for overlapping the dead-code pass with the lint pass —
|
|
35479
|
+
* forking dead-code as a child fiber that runs DURING lint instead of strictly
|
|
35480
|
+
* after it.
|
|
35481
|
+
*
|
|
35482
|
+
* - `"auto"` (default) / `"off"` → strictly SEQUENTIAL: dead-code runs after
|
|
35483
|
+
* lint with the full core budget. Both deslop's parse pool and the oxlint
|
|
35484
|
+
* pool are CPU-bound and each size themselves to all cores, so overlapping
|
|
35485
|
+
* them only oversubscribes (~2x the cores) and starves the parse pass past
|
|
35486
|
+
* its timeout — for no wall-clock win, since there are no spare cores to
|
|
35487
|
+
* absorb the second pass. Sequential is both faster per-phase and safe.
|
|
35488
|
+
* - `"on"` → force the overlap anyway. The orchestrator then SPLITS the core
|
|
35489
|
+
* budget (`DEAD_CODE_OVERLAP_PARSE_SHARE`): deslop's parse pool is capped
|
|
35490
|
+
* and lint shrinks to the remainder, so the two sum to the cores instead of
|
|
35491
|
+
* doubling them, and the dead-code timeout scales up for the reduced share.
|
|
35492
|
+
*
|
|
35493
|
+
* Seeded from `REACT_DOCTOR_DEAD_CODE_OVERLAP` so operators get a redeploy-free
|
|
35494
|
+
* switch; tests pin it via `Layer.succeed(DeadCodeOverlap, ...)`.
|
|
35495
|
+
*/
|
|
35496
|
+
var DeadCodeOverlap = class extends Reference("react-doctor/DeadCodeOverlap", { defaultValue: () => {
|
|
35497
|
+
const raw = process.env["REACT_DOCTOR_DEAD_CODE_OVERLAP"]?.trim().toLowerCase();
|
|
35498
|
+
if (raw === "on" || raw === "true" || raw === "1") return "on";
|
|
35499
|
+
if (raw === "off" || raw === "false" || raw === "0") return "off";
|
|
35500
|
+
return "auto";
|
|
35501
|
+
} }) {};
|
|
35502
|
+
/**
|
|
35503
|
+
* How the full-scan lint pass orders its file batches. `"arrival"` (the
|
|
35504
|
+
* default) keeps `git ls-files` discovery order. `"cost"` opts into LPT (feed
|
|
35505
|
+
* the largest files first); set `REACT_DOCTOR_LINT_BATCH_ORDERING=cost`. NOTE:
|
|
35506
|
+
* `cost` is OFF by default because the current sort-desc-then-chunk-100 packs
|
|
35507
|
+
* the heaviest files into one wave-1 batch — on size-skewed repos that mega-
|
|
35508
|
+
* batch is a straggler (and can trip the per-batch timeout + split), measurably
|
|
35509
|
+
* regressing the common full-scan case. LPT needs the heavy files SPREAD across
|
|
35510
|
+
* batches before `cost` earns the default. Tests override via
|
|
35511
|
+
* `Layer.succeed(LintBatchOrdering, ...)`. Diff / staged scans never reach this
|
|
35512
|
+
* — they pass user-scoped `includePaths` that skip discovery and stay in
|
|
35513
|
+
* arrival order; only the full-scan branch reads it.
|
|
35514
|
+
*/
|
|
35515
|
+
var LintBatchOrdering = class extends Reference("react-doctor/LintBatchOrdering", { defaultValue: resolveLintBatchOrdering }) {};
|
|
35516
|
+
const CACHE_DISABLED_VALUES = new Set(["1", "true"]);
|
|
35517
|
+
/**
|
|
35518
|
+
* Whether the per-file lint cache (`runners/oxlint/file-lint-cache.ts`) is
|
|
35519
|
+
* active. Defaults ON — repeat scans re-lint only the files whose content
|
|
35520
|
+
* changed, and correctness is guaranteed byte-identical to a cold scan by the
|
|
35521
|
+
* always-fresh cross-file sidecar. Opt-OUT, two knobs (matching the whole-repo
|
|
35522
|
+
* scan cache's `REACT_DOCTOR_NO_CACHE`):
|
|
35523
|
+
*
|
|
35524
|
+
* - `REACT_DOCTOR_NO_CACHE` — the global off-switch; disables BOTH the
|
|
35525
|
+
* whole-repo scan cache and this per-file cache.
|
|
35526
|
+
* - `REACT_DOCTOR_NO_FILE_CACHE` — granular: bust only the per-file cache
|
|
35527
|
+
* while keeping the whole-repo short-circuit.
|
|
35528
|
+
*
|
|
35529
|
+
* Tests override via `Layer.succeed(PerFileLintCacheEnabled, false)`.
|
|
35530
|
+
*/
|
|
35531
|
+
var PerFileLintCacheEnabled = class extends Reference("react-doctor/PerFileLintCacheEnabled", { defaultValue: () => {
|
|
35532
|
+
const noCache = process.env["REACT_DOCTOR_NO_CACHE"]?.toLowerCase() ?? "";
|
|
35533
|
+
const noFileCache = process.env["REACT_DOCTOR_NO_FILE_CACHE"]?.toLowerCase() ?? "";
|
|
35534
|
+
if (CACHE_DISABLED_VALUES.has(noCache)) return false;
|
|
35535
|
+
if (CACHE_DISABLED_VALUES.has(noFileCache)) return false;
|
|
35536
|
+
return true;
|
|
35230
35537
|
} }) {};
|
|
35231
35538
|
const DIAGNOSTIC_SURFACES = [
|
|
35232
35539
|
"cli",
|
|
@@ -35275,6 +35582,12 @@ const BOOLEAN_FIELD_NAMES = [
|
|
|
35275
35582
|
"adoptExistingLintConfig"
|
|
35276
35583
|
];
|
|
35277
35584
|
const STRING_FIELD_NAMES = ["rootDir"];
|
|
35585
|
+
const STRING_ARRAY_FIELD_NAMES = [
|
|
35586
|
+
"projects",
|
|
35587
|
+
"textComponents",
|
|
35588
|
+
"rawTextWrapperComponents",
|
|
35589
|
+
"serverAuthFunctionNames"
|
|
35590
|
+
];
|
|
35278
35591
|
const SURFACE_CONTROL_FIELD_NAMES = [
|
|
35279
35592
|
"includeTags",
|
|
35280
35593
|
"excludeTags",
|
|
@@ -35376,6 +35689,7 @@ const validateConfigTypes = (config) => {
|
|
|
35376
35689
|
const validated = { ...config };
|
|
35377
35690
|
for (const fieldName of BOOLEAN_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => coerceMaybeBooleanString(fieldName, value));
|
|
35378
35691
|
for (const fieldName of STRING_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => validateString(fieldName, value));
|
|
35692
|
+
for (const fieldName of STRING_ARRAY_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => validateStringArrayField(fieldName, value));
|
|
35379
35693
|
applyFieldValidator(config, validated, "surfaces", validateSurfacesField);
|
|
35380
35694
|
for (const fieldName of SEVERITY_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => validateSeverityMap(fieldName, value, fieldName === "categories"));
|
|
35381
35695
|
applyFieldValidator(config, validated, "plugins", (value) => validateStringArrayField("plugins", value));
|
|
@@ -35632,6 +35946,8 @@ const assignFixGroups = (diagnostics) => {
|
|
|
35632
35946
|
};
|
|
35633
35947
|
});
|
|
35634
35948
|
};
|
|
35949
|
+
const compareStrings = (left, right) => left < right ? -1 : left > right ? 1 : 0;
|
|
35950
|
+
const sortDiagnosticsStable = (diagnostics) => [...diagnostics].sort((left, right) => compareStrings(left.filePath, right.filePath) || left.line - right.line || left.column - right.column || compareStrings(left.plugin, right.plugin) || compareStrings(left.rule, right.rule) || compareStrings(left.severity, right.severity) || compareStrings(left.message, right.message));
|
|
35635
35951
|
const getDirectDependencyNames = (packageJson) => new Set([...Object.keys(packageJson.dependencies ?? {}), ...Object.keys(packageJson.devDependencies ?? {})]);
|
|
35636
35952
|
const buildExpoCheckContext = (rootDirectory, expoVersion) => {
|
|
35637
35953
|
const packageJson = readPackageJson(Path.join(rootDirectory, "package.json"));
|
|
@@ -36138,10 +36454,15 @@ const buildHardeningDiagnostic = (input) => ({
|
|
|
36138
36454
|
column: input.column ?? 0,
|
|
36139
36455
|
category: "Security"
|
|
36140
36456
|
});
|
|
36141
|
-
const checkPnpmHardening = (
|
|
36142
|
-
if (!isPnpmManagedProject(
|
|
36143
|
-
const workspacePath = Path.join(
|
|
36144
|
-
const
|
|
36457
|
+
const checkPnpmHardening = (scanDirectory) => {
|
|
36458
|
+
if (!isPnpmManagedProject(scanDirectory)) return [];
|
|
36459
|
+
const workspacePath = Path.join(scanDirectory, PNPM_WORKSPACE_FILE);
|
|
36460
|
+
const hasWorkspaceFile = isFile(workspacePath);
|
|
36461
|
+
if (!hasWorkspaceFile) {
|
|
36462
|
+
const monorepoRoot = findMonorepoRoot(scanDirectory);
|
|
36463
|
+
if (monorepoRoot !== null && isFile(Path.join(monorepoRoot, PNPM_WORKSPACE_FILE))) return [];
|
|
36464
|
+
}
|
|
36465
|
+
const settings = parseHardeningSettings(hasWorkspaceFile ? NFS.readFileSync(workspacePath, "utf-8") : "");
|
|
36145
36466
|
const diagnostics = [];
|
|
36146
36467
|
if (settings.minimumReleaseAge === null) diagnostics.push(buildHardeningDiagnostic({
|
|
36147
36468
|
message: "pnpm-workspace.yaml is missing `minimumReleaseAge` — newly published versions can ship malware that gets caught and unpublished within hours",
|
|
@@ -36603,7 +36924,10 @@ const shouldEnableRule = (requires, tags, capabilities, ignoredTags, disabledBy)
|
|
|
36603
36924
|
}
|
|
36604
36925
|
return true;
|
|
36605
36926
|
};
|
|
36606
|
-
const
|
|
36927
|
+
const yieldToEventLoop = () => new Promise((resolve) => {
|
|
36928
|
+
setImmediate(resolve);
|
|
36929
|
+
});
|
|
36930
|
+
const createSecurityScanSession = (rootDirectory, options) => {
|
|
36607
36931
|
const capabilities = options.project ? buildCapabilities(options.project) : /* @__PURE__ */ new Set();
|
|
36608
36932
|
const ignoredTags = options.ignoredTags ?? /* @__PURE__ */ new Set();
|
|
36609
36933
|
const enabledScanRules = REACT_DOCTOR_RULES.flatMap((entry) => {
|
|
@@ -36618,7 +36942,7 @@ const checkSecurityScan = (rootDirectory, options = {}) => {
|
|
|
36618
36942
|
committedFilesOnly: rule.committedFilesOnly === true
|
|
36619
36943
|
}];
|
|
36620
36944
|
});
|
|
36621
|
-
if (enabledScanRules.length === 0) return
|
|
36945
|
+
if (enabledScanRules.length === 0) return null;
|
|
36622
36946
|
const diagnostics = [];
|
|
36623
36947
|
const seen = /* @__PURE__ */ new Set();
|
|
36624
36948
|
const gitIgnoredCache = /* @__PURE__ */ new Map();
|
|
@@ -36630,15 +36954,34 @@ const checkSecurityScan = (rootDirectory, options = {}) => {
|
|
|
36630
36954
|
}
|
|
36631
36955
|
return status === true;
|
|
36632
36956
|
};
|
|
36633
|
-
|
|
36634
|
-
|
|
36635
|
-
|
|
36636
|
-
|
|
36637
|
-
|
|
36638
|
-
|
|
36639
|
-
|
|
36957
|
+
const scanFile = (file) => {
|
|
36958
|
+
for (const { entry, scan, committedFilesOnly } of enabledScanRules) for (const finding of scan(file)) {
|
|
36959
|
+
if (committedFilesOnly && isFileGitIgnored(file)) continue;
|
|
36960
|
+
const diagnostic = buildSecurityScanDiagnostic(finding, entry, file.relativePath);
|
|
36961
|
+
const key = `${diagnostic.rule}:${diagnostic.filePath}:${diagnostic.line}:${diagnostic.column}:${diagnostic.message}`;
|
|
36962
|
+
if (seen.has(key)) continue;
|
|
36963
|
+
seen.add(key);
|
|
36964
|
+
diagnostics.push(diagnostic);
|
|
36965
|
+
}
|
|
36966
|
+
};
|
|
36967
|
+
return {
|
|
36968
|
+
scanFile,
|
|
36969
|
+
diagnostics
|
|
36970
|
+
};
|
|
36971
|
+
};
|
|
36972
|
+
const checkSecurityScanCooperative = async (rootDirectory, options = {}) => {
|
|
36973
|
+
const session = createSecurityScanSession(rootDirectory, options);
|
|
36974
|
+
if (session === null) return [];
|
|
36975
|
+
let filesSinceYield = 0;
|
|
36976
|
+
for (const file of collectSecurityScanFiles(rootDirectory)) {
|
|
36977
|
+
session.scanFile(file);
|
|
36978
|
+
filesSinceYield += 1;
|
|
36979
|
+
if (filesSinceYield >= 16) {
|
|
36980
|
+
filesSinceYield = 0;
|
|
36981
|
+
await yieldToEventLoop();
|
|
36982
|
+
}
|
|
36640
36983
|
}
|
|
36641
|
-
return diagnostics;
|
|
36984
|
+
return session.diagnostics;
|
|
36642
36985
|
};
|
|
36643
36986
|
var import_picocolors = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
36644
36987
|
let p = process || {}, argv = p.argv || [], env = p.env || {};
|
|
@@ -36855,6 +37198,74 @@ const collectDeadCodeIgnorePatterns = (rootDirectory) => {
|
|
|
36855
37198
|
return [...seen].filter((pattern) => pattern.length > 0);
|
|
36856
37199
|
};
|
|
36857
37200
|
const collectDeadCodeEntryPatterns = (rootDirectory) => [...new Set(collectKnipPatterns(rootDirectory, "entry"))].filter((pattern) => pattern.length > 0);
|
|
37201
|
+
const readSystemFacts = () => ({
|
|
37202
|
+
availableCores: os.availableParallelism(),
|
|
37203
|
+
totalMemoryBytes: os.totalmem(),
|
|
37204
|
+
cgroupMemoryLimitBytes: readCgroupMemoryLimitBytes()
|
|
37205
|
+
});
|
|
37206
|
+
/**
|
|
37207
|
+
* How many real deslop dead-code child processes may run at once, across the
|
|
37208
|
+
* concurrent per-project `runInspect` fibers of one CLI run. The cap is the
|
|
37209
|
+
* smaller of the core count and the number of `DEAD_CODE_WORKER_MEM_BUDGET_BYTES`
|
|
37210
|
+
* workers that fit in available memory, floored at 1.
|
|
37211
|
+
*
|
|
37212
|
+
* On a roomy dev box / CI runner this resolves high enough that every
|
|
37213
|
+
* concurrently-scanned project still spawns its own worker (no serialization vs
|
|
37214
|
+
* the prior uncapped behavior); on a memory-constrained runner it collapses
|
|
37215
|
+
* toward 1, so the `withDeadCodeWorkerSlot` semaphore serializes the spawns
|
|
37216
|
+
* instead of oversubscribing memory with N simultaneous children — the global
|
|
37217
|
+
* cap the per-project spawn path lacked.
|
|
37218
|
+
*
|
|
37219
|
+
* Mirrors `resolveAutoScanConcurrency` (lint), but budgets memory per the
|
|
37220
|
+
* heavier dead-code worker. `facts` is injectable for tests.
|
|
37221
|
+
*/
|
|
37222
|
+
const resolveDeadCodeConcurrency = (facts = readSystemFacts()) => {
|
|
37223
|
+
const availableMemoryBytes = Math.min(facts.totalMemoryBytes, facts.cgroupMemoryLimitBytes ?? Number.POSITIVE_INFINITY);
|
|
37224
|
+
const memoryBoundedWorkers = Math.floor(availableMemoryBytes / DEAD_CODE_WORKER_MEM_BUDGET_BYTES);
|
|
37225
|
+
return Math.max(1, Math.min(facts.availableCores, memoryBoundedWorkers));
|
|
37226
|
+
};
|
|
37227
|
+
let availableSlots = -1;
|
|
37228
|
+
const waiters = [];
|
|
37229
|
+
const releaseSlot = () => {
|
|
37230
|
+
const nextWaiter = waiters.shift();
|
|
37231
|
+
if (nextWaiter !== void 0) nextWaiter();
|
|
37232
|
+
else availableSlots += 1;
|
|
37233
|
+
};
|
|
37234
|
+
/**
|
|
37235
|
+
* Runs `task` once a dead-code worker slot is free, releasing the slot when the
|
|
37236
|
+
* task settles (success or failure). With a high cap (roomy machine) every
|
|
37237
|
+
* caller proceeds immediately; with a low cap (constrained runner) callers
|
|
37238
|
+
* queue and run as slots free.
|
|
37239
|
+
*
|
|
37240
|
+
* `abortSignal` short-circuits the WAIT: if it's already aborted, or fires while
|
|
37241
|
+
* this caller is queued, the call rejects without acquiring a slot or running
|
|
37242
|
+
* `task` — so a cancelled scan (e.g. lint failed) doesn't sit in the queue and
|
|
37243
|
+
* then spawn a child only to tear it down. A queued caller that aborts removes
|
|
37244
|
+
* its own waiter so a later release never hands a slot to a dead request.
|
|
37245
|
+
*/
|
|
37246
|
+
const withDeadCodeWorkerSlot = async (task, abortSignal) => {
|
|
37247
|
+
if (abortSignal?.aborted) throw new Error("Dead-code worker aborted.");
|
|
37248
|
+
if (availableSlots < 0) availableSlots = resolveDeadCodeConcurrency();
|
|
37249
|
+
if (availableSlots > 0) availableSlots -= 1;
|
|
37250
|
+
else await new Promise((resolve, reject) => {
|
|
37251
|
+
const waiter = () => {
|
|
37252
|
+
abortSignal?.removeEventListener("abort", onAbort);
|
|
37253
|
+
resolve();
|
|
37254
|
+
};
|
|
37255
|
+
const onAbort = () => {
|
|
37256
|
+
const queuedIndex = waiters.indexOf(waiter);
|
|
37257
|
+
if (queuedIndex !== -1) waiters.splice(queuedIndex, 1);
|
|
37258
|
+
reject(/* @__PURE__ */ new Error("Dead-code worker aborted."));
|
|
37259
|
+
};
|
|
37260
|
+
waiters.push(waiter);
|
|
37261
|
+
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
37262
|
+
});
|
|
37263
|
+
try {
|
|
37264
|
+
return await task();
|
|
37265
|
+
} finally {
|
|
37266
|
+
releaseSlot();
|
|
37267
|
+
}
|
|
37268
|
+
};
|
|
36858
37269
|
/**
|
|
36859
37270
|
* Resolves a path to its canonical, symlink-free form, falling back to
|
|
36860
37271
|
* the input when it cannot be realpath'd (broken symlink, permission
|
|
@@ -36926,6 +37337,22 @@ process.stdin.on("end", () => {
|
|
|
36926
37337
|
...(workerInput.ignorePatterns.length > 0
|
|
36927
37338
|
? { ignorePatterns: workerInput.ignorePatterns }
|
|
36928
37339
|
: {}),
|
|
37340
|
+
// We consume only deslop's GRAPH-based findings (unusedFiles, unusedExports,
|
|
37341
|
+
// unusedDependencies, circularDependencies). Everything else deslop can compute
|
|
37342
|
+
// is pure wasted work for us, and it's the bulk of the runtime:
|
|
37343
|
+
// - semantic: a full TS Program for unusedTypes/enum/class-members/
|
|
37344
|
+
// misclassifiedDependencies (~37-45% of the phase).
|
|
37345
|
+
// - reportCodeQuality: the duplicate-block, complexity, feature-flag,
|
|
37346
|
+
// TypeScript-smell, private-type-leak and re-export-cycle detectors. These
|
|
37347
|
+
// are the single most expensive pass — duplicate-block detection alone was
|
|
37348
|
+
// ~83s of a ~130s Sentry scan — so skipping them is an ~8.5x dead-code
|
|
37349
|
+
// speedup on a large repo.
|
|
37350
|
+
// Both are provably safe: the consumed graph findings are computed by their own
|
|
37351
|
+
// detectors, independent of these passes (confirmed byte-identical on
|
|
37352
|
+
// excalidraw + mui-material + sentry). tsConfigPath stays — the module resolver
|
|
37353
|
+
// needs it for path-alias resolution in the import graph.
|
|
37354
|
+
semantic: { enabled: false },
|
|
37355
|
+
reportCodeQuality: false,
|
|
36929
37356
|
};
|
|
36930
37357
|
const result = await analyze(defineConfig(config));
|
|
36931
37358
|
emit({ ok: true, result: normalizeResult(result) });
|
|
@@ -37055,7 +37482,11 @@ const createDeadCodeWorker = (input) => {
|
|
|
37055
37482
|
"pipe",
|
|
37056
37483
|
"pipe"
|
|
37057
37484
|
],
|
|
37058
|
-
windowsHide: true
|
|
37485
|
+
windowsHide: true,
|
|
37486
|
+
env: input.parseConcurrency === void 0 ? process.env : {
|
|
37487
|
+
...process.env,
|
|
37488
|
+
DESLOP_PARSE_CONCURRENCY: String(input.parseConcurrency)
|
|
37489
|
+
}
|
|
37059
37490
|
});
|
|
37060
37491
|
const stdoutChunks = [];
|
|
37061
37492
|
const stderrChunks = [];
|
|
@@ -37100,41 +37531,42 @@ const createDeadCodeWorker = (input) => {
|
|
|
37100
37531
|
}
|
|
37101
37532
|
};
|
|
37102
37533
|
};
|
|
37103
|
-
const runDeadCodeWorkerWithTimeout = (handle, timeoutMs) => new Promise((resolve, reject) => {
|
|
37534
|
+
const runDeadCodeWorkerWithTimeout = (handle, timeoutMs, abortSignal) => new Promise((resolve, reject) => {
|
|
37104
37535
|
let didSettle = false;
|
|
37105
|
-
const
|
|
37106
|
-
if (didSettle) return;
|
|
37107
|
-
didSettle = true;
|
|
37108
|
-
handle.terminate?.();
|
|
37109
|
-
reject(/* @__PURE__ */ new Error(`Dead-code worker timed out after ${timeoutMs / MILLISECONDS_PER_SECOND}s.`));
|
|
37110
|
-
}, timeoutMs);
|
|
37111
|
-
timeoutHandle.unref?.();
|
|
37112
|
-
handle.result.then((value) => {
|
|
37536
|
+
const settle = (finish) => {
|
|
37113
37537
|
if (didSettle) return;
|
|
37114
37538
|
didSettle = true;
|
|
37115
37539
|
clearTimeout(timeoutHandle);
|
|
37540
|
+
abortSignal?.removeEventListener("abort", onAbort);
|
|
37116
37541
|
handle.terminate?.();
|
|
37117
|
-
|
|
37118
|
-
}
|
|
37119
|
-
|
|
37120
|
-
|
|
37121
|
-
|
|
37122
|
-
|
|
37123
|
-
|
|
37124
|
-
|
|
37542
|
+
finish();
|
|
37543
|
+
};
|
|
37544
|
+
const onAbort = () => settle(() => reject(/* @__PURE__ */ new Error("Dead-code worker aborted.")));
|
|
37545
|
+
const timeoutHandle = setTimeout(() => settle(() => reject(/* @__PURE__ */ new Error(`Dead-code worker timed out after ${timeoutMs / MILLISECONDS_PER_SECOND}s.`))), timeoutMs);
|
|
37546
|
+
timeoutHandle.unref?.();
|
|
37547
|
+
if (abortSignal?.aborted) {
|
|
37548
|
+
onAbort();
|
|
37549
|
+
return;
|
|
37550
|
+
}
|
|
37551
|
+
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
37552
|
+
handle.result.then((value) => settle(() => resolve(value)), (error) => settle(() => reject(error)));
|
|
37125
37553
|
});
|
|
37126
37554
|
const checkDeadCode = async (options) => {
|
|
37127
37555
|
const rootDirectory = toCanonicalPath(options.rootDirectory);
|
|
37128
37556
|
if (!NFS.existsSync(Path.join(rootDirectory, "package.json"))) return [];
|
|
37129
37557
|
const entryPatterns = collectDeadCodeEntryPatterns(rootDirectory);
|
|
37130
37558
|
const ignorePatterns = collectDeadCodeIgnorePatterns(rootDirectory);
|
|
37131
|
-
const
|
|
37132
|
-
|
|
37133
|
-
|
|
37134
|
-
|
|
37135
|
-
|
|
37136
|
-
|
|
37137
|
-
|
|
37559
|
+
const spawnAndRun = () => {
|
|
37560
|
+
return runDeadCodeWorkerWithTimeout((options.createWorker ?? createDeadCodeWorker)({
|
|
37561
|
+
rootDirectory,
|
|
37562
|
+
entryPatterns,
|
|
37563
|
+
tsConfigPath: resolveTsConfigPath(rootDirectory),
|
|
37564
|
+
ignorePatterns,
|
|
37565
|
+
deslopJsModuleSpecifier: options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js"),
|
|
37566
|
+
parseConcurrency: options.parseConcurrency
|
|
37567
|
+
}), options.workerTimeoutMs ?? 12e4, options.abortSignal);
|
|
37568
|
+
};
|
|
37569
|
+
const result = parseDeadCodeWorkerResult(options.createWorker === void 0 ? await withDeadCodeWorkerSlot(spawnAndRun, options.abortSignal) : await spawnAndRun());
|
|
37138
37570
|
const toRelative = (filePath) => toRelativeFilePath(rootDirectory, filePath);
|
|
37139
37571
|
const diagnostics = [];
|
|
37140
37572
|
for (const unusedFile of result.unusedFiles) diagnostics.push({
|
|
@@ -37232,7 +37664,37 @@ const isDiagnosticOnSurface = (diagnostic, surface, config) => {
|
|
|
37232
37664
|
return true;
|
|
37233
37665
|
};
|
|
37234
37666
|
const filterDiagnosticsForSurface = (diagnostics, surface, config) => diagnostics.filter((diagnostic) => isDiagnosticOnSurface(diagnostic, surface, config));
|
|
37235
|
-
|
|
37667
|
+
/**
|
|
37668
|
+
* Budget for the dead-code phase, scaled to the work. deslop's graph build is
|
|
37669
|
+
* CPU-bound and roughly linear in file count, so a fixed 120s cap is too tight
|
|
37670
|
+
* for a large repo (where the pass legitimately runs that long) and is then
|
|
37671
|
+
* tipped over by any concurrent load — silently dropping every dead-code
|
|
37672
|
+
* finding. Scaling the budget with file count (and inversely with the core
|
|
37673
|
+
* share when overlapped) lets the pass complete, while the ceiling still
|
|
37674
|
+
* reclaims a genuinely wedged worker. Returns the in-worker SIGKILL deadline
|
|
37675
|
+
* and the Effect-side phase backstop that sits a margin above it.
|
|
37676
|
+
*/
|
|
37677
|
+
const resolveDeadCodeTimeout = (input) => {
|
|
37678
|
+
const coreShareFactor = Math.max(1, input.fullConcurrency / Math.max(1, input.deadCodeConcurrency));
|
|
37679
|
+
const workerTimeoutMs = Math.min(DEAD_CODE_TIMEOUT_CEILING_MS, Math.max(DEAD_CODE_WORKER_TIMEOUT_MS, Math.ceil(input.sourceFileCount * 30 * coreShareFactor)));
|
|
37680
|
+
return {
|
|
37681
|
+
workerTimeoutMs,
|
|
37682
|
+
phaseTimeoutMs: workerTimeoutMs + DEAD_CODE_PHASE_TIMEOUT_OVER_WORKER_MS
|
|
37683
|
+
};
|
|
37684
|
+
};
|
|
37685
|
+
const collectSizedSourceFiles = (rootDirectory, relativePaths) => {
|
|
37686
|
+
const entries = [];
|
|
37687
|
+
for (const relativePath of relativePaths) {
|
|
37688
|
+
const absolutePath = Path.resolve(rootDirectory, relativePath);
|
|
37689
|
+
const sizeBytes = statSourceFileSize(absolutePath);
|
|
37690
|
+
if (isLargeMinifiedFile(absolutePath, sizeBytes)) continue;
|
|
37691
|
+
entries.push({
|
|
37692
|
+
path: relativePath,
|
|
37693
|
+
sizeBytes: sizeBytes ?? 0
|
|
37694
|
+
});
|
|
37695
|
+
}
|
|
37696
|
+
return entries;
|
|
37697
|
+
};
|
|
37236
37698
|
const listSourceFilesViaGit = (rootDirectory) => {
|
|
37237
37699
|
const result = spawnSync("git", [
|
|
37238
37700
|
"ls-files",
|
|
@@ -37265,7 +37727,8 @@ const listSourceFilesViaFilesystem = (rootDirectory) => {
|
|
|
37265
37727
|
}
|
|
37266
37728
|
return filePaths;
|
|
37267
37729
|
};
|
|
37268
|
-
const
|
|
37730
|
+
const listSourceFilesWithSize = (rootDirectory) => collectSizedSourceFiles(rootDirectory, listSourceFilesViaGit(rootDirectory) ?? listSourceFilesViaFilesystem(rootDirectory));
|
|
37731
|
+
const listSourceFiles = (rootDirectory) => listSourceFilesWithSize(rootDirectory).map((entry) => entry.path);
|
|
37269
37732
|
const resolveLintIncludePaths = (rootDirectory, userConfig, project) => {
|
|
37270
37733
|
if (!Array.isArray(userConfig?.ignore?.files) || userConfig.ignore.files.length === 0) return;
|
|
37271
37734
|
const ignoredPatterns = compileIgnoredFilePatterns(userConfig);
|
|
@@ -37308,9 +37771,12 @@ var Config = class Config extends Service()("react-doctor/Config") {
|
|
|
37308
37771
|
var DeadCode = class DeadCode extends Service()("react-doctor/DeadCode") {
|
|
37309
37772
|
static layerNode = succeed$3(DeadCode, DeadCode.of({ run: (input) => unwrap(fn("DeadCode.run")(function* () {
|
|
37310
37773
|
return yield* tryPromise({
|
|
37311
|
-
try: () => checkDeadCode({
|
|
37774
|
+
try: (signal) => checkDeadCode({
|
|
37312
37775
|
rootDirectory: input.rootDirectory,
|
|
37313
|
-
userConfig: input.userConfig
|
|
37776
|
+
userConfig: input.userConfig,
|
|
37777
|
+
parseConcurrency: input.parseConcurrency,
|
|
37778
|
+
workerTimeoutMs: input.workerTimeoutMs,
|
|
37779
|
+
abortSignal: signal
|
|
37314
37780
|
}),
|
|
37315
37781
|
catch: (cause) => new ReactDoctorError({ reason: new DeadCodeAnalysisFailed({ cause }) })
|
|
37316
37782
|
}).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)));
|
|
@@ -37501,43 +37967,46 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
37501
37967
|
* reason: GitInvocationFailed })` so the rest of the codebase
|
|
37502
37968
|
* sees a single failure channel.
|
|
37503
37969
|
*/
|
|
37504
|
-
const runCommand = (input) =>
|
|
37505
|
-
const
|
|
37506
|
-
cwd: input.directory,
|
|
37507
|
-
env: input.env,
|
|
37508
|
-
extendEnv: true
|
|
37509
|
-
}));
|
|
37510
|
-
const maxStdoutBytes = input.maxStdoutBytes;
|
|
37511
|
-
const stdoutByteCount = yield* make$13(0);
|
|
37512
|
-
const [stdout, stderr, status] = yield* all([
|
|
37513
|
-
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({
|
|
37514
|
-
args: [...input.args],
|
|
37515
|
-
directory: input.directory,
|
|
37516
|
-
cause: /* @__PURE__ */ new Error(`git stdout exceeded ${maxStdoutBytes} bytes`)
|
|
37517
|
-
}) })) : void_)))))),
|
|
37518
|
-
mkString(decodeText(handle.stderr)),
|
|
37519
|
-
handle.exitCode
|
|
37520
|
-
], { concurrency: 3 });
|
|
37521
|
-
return {
|
|
37522
|
-
status,
|
|
37523
|
-
stdout,
|
|
37524
|
-
stderr
|
|
37525
|
-
};
|
|
37526
|
-
})).pipe(catchTag$1("PlatformError", (cause) => {
|
|
37527
|
-
if (input.command !== "git") return succeed$2({
|
|
37970
|
+
const runCommand = (input) => {
|
|
37971
|
+
const foldSpawnFailure = (cause) => input.command !== "git" ? succeed$2({
|
|
37528
37972
|
status: 127,
|
|
37529
37973
|
stdout: "",
|
|
37530
37974
|
stderr: String(cause)
|
|
37531
|
-
})
|
|
37532
|
-
return new ReactDoctorError({ reason: new GitInvocationFailed({
|
|
37975
|
+
}) : fail$4(new ReactDoctorError({ reason: new GitInvocationFailed({
|
|
37533
37976
|
args: [...input.args],
|
|
37534
37977
|
directory: input.directory,
|
|
37535
37978
|
cause
|
|
37536
|
-
}) });
|
|
37537
|
-
|
|
37538
|
-
|
|
37539
|
-
|
|
37540
|
-
|
|
37979
|
+
}) }));
|
|
37980
|
+
return scoped(gen(function* () {
|
|
37981
|
+
if (!isDirectory(input.directory)) return yield* foldSpawnFailure(`spawn ENOTDIR (cwd is not a directory: ${input.directory})`);
|
|
37982
|
+
const argvLengthChars = input.command.length + 1 + input.args.reduce((total, arg) => total + arg.length + 1, 0);
|
|
37983
|
+
if (argvLengthChars > 24e3) return yield* foldSpawnFailure(`spawn ENAMETOOLONG (${argvLengthChars} argv chars exceed ${SPAWN_ARGS_MAX_LENGTH_CHARS})`);
|
|
37984
|
+
const handle = yield* spawner.spawn(make$1(input.command, [...input.args], {
|
|
37985
|
+
cwd: input.directory,
|
|
37986
|
+
env: input.env,
|
|
37987
|
+
extendEnv: true
|
|
37988
|
+
}));
|
|
37989
|
+
const maxStdoutBytes = input.maxStdoutBytes;
|
|
37990
|
+
const stdoutByteCount = yield* make$13(0);
|
|
37991
|
+
const [stdout, stderr, status] = yield* all([
|
|
37992
|
+
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({
|
|
37993
|
+
args: [...input.args],
|
|
37994
|
+
directory: input.directory,
|
|
37995
|
+
cause: /* @__PURE__ */ new Error(`git stdout exceeded ${maxStdoutBytes} bytes`)
|
|
37996
|
+
}) })) : void_)))))),
|
|
37997
|
+
mkString(decodeText(handle.stderr)),
|
|
37998
|
+
handle.exitCode
|
|
37999
|
+
], { concurrency: 3 });
|
|
38000
|
+
return {
|
|
38001
|
+
status,
|
|
38002
|
+
stdout,
|
|
38003
|
+
stderr
|
|
38004
|
+
};
|
|
38005
|
+
})).pipe(catchTag$1("PlatformError", foldSpawnFailure), withSpan("git.exec", { attributes: {
|
|
38006
|
+
"git.command": input.command,
|
|
38007
|
+
"git.subcommand": input.args[0] ?? ""
|
|
38008
|
+
} }));
|
|
38009
|
+
};
|
|
37541
38010
|
const runGit = (directory, args) => runCommand({
|
|
37542
38011
|
command: "git",
|
|
37543
38012
|
args,
|
|
@@ -37570,7 +38039,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
37570
38039
|
"rev-parse",
|
|
37571
38040
|
"--verify",
|
|
37572
38041
|
branch
|
|
37573
|
-
]).pipe(map$3((result) => result.status === 0));
|
|
38042
|
+
]).pipe(map$3((result) => result.status === 0), catch_$1((error) => error.reason._tag === "GitInvocationFailed" ? succeed$2(false) : fail$4(error)));
|
|
37574
38043
|
const headSha = (directory) => runGit(directory, ["rev-parse", "HEAD"]).pipe(map$3((result) => result.status === 0 ? trimOrNull(result.stdout) : null));
|
|
37575
38044
|
const mergeBase = (input) => isSafeGitRevision(input.ref) ? runGit(input.directory, [
|
|
37576
38045
|
"merge-base",
|
|
@@ -37784,7 +38253,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
|
|
|
37784
38253
|
]);
|
|
37785
38254
|
if (result.status !== 0) return null;
|
|
37786
38255
|
return parseChangedLineRanges(result.stdout);
|
|
37787
|
-
}).pipe(withSpan("Git.changedLineRanges"))
|
|
38256
|
+
}).pipe(catch_$1((error) => error.reason._tag === "GitInvocationFailed" ? succeed$2(null) : fail$4(error)), withSpan("Git.changedLineRanges"))
|
|
37788
38257
|
});
|
|
37789
38258
|
})).pipe(provide$2(layer$2.pipe(provide$2(mergeAll$1(layer$1, layer)))));
|
|
37790
38259
|
/**
|
|
@@ -38023,6 +38492,14 @@ const neutralizeDisableDirectives = async (rootDirectory, includePaths) => {
|
|
|
38023
38492
|
process.removeListener("exit", onExit);
|
|
38024
38493
|
};
|
|
38025
38494
|
};
|
|
38495
|
+
const ROOT_DIRECTORY_PLACEHOLDER = "<root>";
|
|
38496
|
+
const normalizeConfigForHash = (config) => {
|
|
38497
|
+
const clone = JSON.parse(JSON.stringify(config));
|
|
38498
|
+
if (clone?.settings?.["react-doctor"]) clone.settings["react-doctor"].rootDirectory = ROOT_DIRECTORY_PLACEHOLDER;
|
|
38499
|
+
if (Array.isArray(clone?.jsPlugins)) clone.jsPlugins = clone.jsPlugins.map((_, index) => `<plugin:${index}>`);
|
|
38500
|
+
return clone;
|
|
38501
|
+
};
|
|
38502
|
+
const computeRulesetHash = (input) => crypto.createHash("sha1").update(JSON.stringify(normalizeConfigForHash(input.config))).update("\0").update([...input.toolchainVersions].join("\0")).update("\0").update([...input.ignorePatterns].join("\n")).update(" ").update(input.tsconfigContent ?? "").digest("hex");
|
|
38026
38503
|
/**
|
|
38027
38504
|
* Loads a plugin module via the local require resolver and extracts
|
|
38028
38505
|
* `(name, ruleNames)` from either `module.exports.meta + rules` or
|
|
@@ -38049,16 +38526,16 @@ const readPluginShape = (pluginSpecifier, loadModule) => {
|
|
|
38049
38526
|
ruleNames: new Set(Object.keys(rules))
|
|
38050
38527
|
};
|
|
38051
38528
|
};
|
|
38052
|
-
const bundledRequire = createRequire(import.meta.url);
|
|
38529
|
+
const bundledRequire$1 = createRequire(import.meta.url);
|
|
38053
38530
|
const resolveReactHooksJsPlugin = (hasReactCompiler, customRulesOnly) => {
|
|
38054
38531
|
if (!hasReactCompiler || customRulesOnly) return null;
|
|
38055
38532
|
let pluginSpecifier;
|
|
38056
38533
|
try {
|
|
38057
|
-
pluginSpecifier = bundledRequire.resolve("eslint-plugin-react-hooks");
|
|
38534
|
+
pluginSpecifier = bundledRequire$1.resolve("eslint-plugin-react-hooks");
|
|
38058
38535
|
} catch {
|
|
38059
38536
|
return null;
|
|
38060
38537
|
}
|
|
38061
|
-
const { ruleNames } = readPluginShape(pluginSpecifier, (spec) => bundledRequire(spec));
|
|
38538
|
+
const { ruleNames } = readPluginShape(pluginSpecifier, (spec) => bundledRequire$1(spec));
|
|
38062
38539
|
return {
|
|
38063
38540
|
entry: {
|
|
38064
38541
|
name: "react-hooks-js",
|
|
@@ -38177,8 +38654,8 @@ const buildUserPluginRules = (userPlugin, severityControls) => {
|
|
|
38177
38654
|
}
|
|
38178
38655
|
return enabled;
|
|
38179
38656
|
};
|
|
38180
|
-
const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false }) => {
|
|
38181
|
-
const reactHooksJsPlugin = disableReactHooksJsPlugin ? null : resolveReactHooksJsPlugin(project.hasReactCompiler, customRulesOnly);
|
|
38657
|
+
const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false, ruleSelection }) => {
|
|
38658
|
+
const reactHooksJsPlugin = disableReactHooksJsPlugin || ruleSelection === "sidecar" ? null : resolveReactHooksJsPlugin(project.hasReactCompiler, customRulesOnly);
|
|
38182
38659
|
const reactCompilerRules = reactHooksJsPlugin ? applyRuleSeverityControls(filterRulesToAvailable(REACT_COMPILER_RULES, "react-hooks-js", reactHooksJsPlugin.availableRuleNames), severityControls) : {};
|
|
38183
38660
|
const jsPlugins = [];
|
|
38184
38661
|
if (reactHooksJsPlugin) jsPlugins.push(reactHooksJsPlugin.entry);
|
|
@@ -38187,6 +38664,8 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
38187
38664
|
for (const registryEntry of REACT_DOCTOR_RULES) {
|
|
38188
38665
|
const rule = reactDoctorPlugin.rules[registryEntry.id];
|
|
38189
38666
|
if (!rule) continue;
|
|
38667
|
+
if (ruleSelection === "cacheable" && CROSS_FILE_RULE_IDS.has(registryEntry.id)) continue;
|
|
38668
|
+
if (ruleSelection === "sidecar" && !CROSS_FILE_RULE_IDS.has(registryEntry.id)) continue;
|
|
38190
38669
|
if (rule.scan !== void 0) continue;
|
|
38191
38670
|
if (customRulesOnly && registryEntry.originallyExternal) continue;
|
|
38192
38671
|
if (rule.framework !== "global" && !rule.requires) continue;
|
|
@@ -38201,7 +38680,7 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
38201
38680
|
enabledReactDoctorRules[registryEntry.key] = severity;
|
|
38202
38681
|
}
|
|
38203
38682
|
const userPluginRules = {};
|
|
38204
|
-
for (const userPlugin of userPlugins) {
|
|
38683
|
+
if (ruleSelection !== "sidecar") for (const userPlugin of userPlugins) {
|
|
38205
38684
|
Object.assign(userPluginRules, buildUserPluginRules(userPlugin, severityControls));
|
|
38206
38685
|
jsPlugins.push(userPlugin.entry);
|
|
38207
38686
|
}
|
|
@@ -38231,6 +38710,100 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
38231
38710
|
}
|
|
38232
38711
|
};
|
|
38233
38712
|
};
|
|
38713
|
+
const atomicWriteJson = (filePath, value) => {
|
|
38714
|
+
try {
|
|
38715
|
+
NFS.mkdirSync(Path.dirname(filePath), { recursive: true });
|
|
38716
|
+
const temporaryPath = `${filePath}.${process.pid}.tmp`;
|
|
38717
|
+
NFS.writeFileSync(temporaryPath, JSON.stringify(value));
|
|
38718
|
+
NFS.renameSync(temporaryPath, filePath);
|
|
38719
|
+
} catch {
|
|
38720
|
+
return;
|
|
38721
|
+
}
|
|
38722
|
+
};
|
|
38723
|
+
const failOpenReadJson = (filePath, fallback) => {
|
|
38724
|
+
try {
|
|
38725
|
+
return JSON.parse(NFS.readFileSync(filePath, "utf8"));
|
|
38726
|
+
} catch {
|
|
38727
|
+
return fallback;
|
|
38728
|
+
}
|
|
38729
|
+
};
|
|
38730
|
+
const validateDiagnostic = decodeUnknownSync(Diagnostic);
|
|
38731
|
+
const decodeFileDiagnostics = (raw) => {
|
|
38732
|
+
if (!Array.isArray(raw)) return null;
|
|
38733
|
+
try {
|
|
38734
|
+
for (const entry of raw) validateDiagnostic(entry);
|
|
38735
|
+
return raw;
|
|
38736
|
+
} catch {
|
|
38737
|
+
return null;
|
|
38738
|
+
}
|
|
38739
|
+
};
|
|
38740
|
+
const emptyCache = () => ({
|
|
38741
|
+
version: 1,
|
|
38742
|
+
rulesets: {}
|
|
38743
|
+
});
|
|
38744
|
+
const loadRulesetEntries = (cacheFilePath, rulesetHash) => {
|
|
38745
|
+
const entries = /* @__PURE__ */ new Map();
|
|
38746
|
+
const persisted = failOpenReadJson(cacheFilePath, emptyCache());
|
|
38747
|
+
if (persisted.version !== 1 || !isRecord(persisted.rulesets)) return entries;
|
|
38748
|
+
const bucket = persisted.rulesets[rulesetHash];
|
|
38749
|
+
if (!isRecord(bucket) || !isRecord(bucket.files)) return entries;
|
|
38750
|
+
for (const [fileKey, rawDiagnostics] of Object.entries(bucket.files)) {
|
|
38751
|
+
const decoded = decodeFileDiagnostics(rawDiagnostics);
|
|
38752
|
+
if (decoded !== null) entries.set(fileKey, decoded);
|
|
38753
|
+
}
|
|
38754
|
+
return entries;
|
|
38755
|
+
};
|
|
38756
|
+
const createFileLintCache = (cacheDirectory, rulesetHash) => {
|
|
38757
|
+
const cacheFilePath = Path.join(cacheDirectory, FILE_LINT_CACHE_FILENAME);
|
|
38758
|
+
const entries = loadRulesetEntries(cacheFilePath, rulesetHash);
|
|
38759
|
+
return {
|
|
38760
|
+
lookup: (fileKey) => entries.get(fileKey) ?? null,
|
|
38761
|
+
store: (fileKey, diagnostics) => {
|
|
38762
|
+
entries.delete(fileKey);
|
|
38763
|
+
entries.set(fileKey, diagnostics);
|
|
38764
|
+
},
|
|
38765
|
+
persist: () => {
|
|
38766
|
+
const onDisk = failOpenReadJson(cacheFilePath, emptyCache());
|
|
38767
|
+
const rulesets = onDisk.version === 1 && isRecord(onDisk.rulesets) ? { ...onDisk.rulesets } : {};
|
|
38768
|
+
const existingBucket = rulesets[rulesetHash];
|
|
38769
|
+
const existingFiles = isRecord(existingBucket) && isRecord(existingBucket.files) ? existingBucket.files : {};
|
|
38770
|
+
const ourFiles = {};
|
|
38771
|
+
for (const [fileKey, diagnostics] of entries) ourFiles[fileKey] = diagnostics;
|
|
38772
|
+
const cappedEntries = Object.entries({
|
|
38773
|
+
...existingFiles,
|
|
38774
|
+
...ourFiles
|
|
38775
|
+
}).slice(-FILE_LINT_CACHE_MAX_FILE_COUNT);
|
|
38776
|
+
rulesets[rulesetHash] = {
|
|
38777
|
+
updatedAtMs: Date.now(),
|
|
38778
|
+
files: Object.fromEntries(cappedEntries)
|
|
38779
|
+
};
|
|
38780
|
+
const keptHashes = Object.entries(rulesets).sort(([, first], [, second]) => second.updatedAtMs - first.updatedAtMs).slice(0, 8).map(([hash]) => hash);
|
|
38781
|
+
const prunedRulesets = {};
|
|
38782
|
+
for (const hash of keptHashes) prunedRulesets[hash] = rulesets[hash];
|
|
38783
|
+
atomicWriteJson(cacheFilePath, {
|
|
38784
|
+
version: 1,
|
|
38785
|
+
rulesets: prunedRulesets
|
|
38786
|
+
});
|
|
38787
|
+
}
|
|
38788
|
+
};
|
|
38789
|
+
};
|
|
38790
|
+
const bundledRequire = createRequire(import.meta.url);
|
|
38791
|
+
const TOOLCHAIN_PACKAGE_SPECIFIERS = [
|
|
38792
|
+
"oxlint/package.json",
|
|
38793
|
+
"oxlint-plugin-react-doctor/package.json",
|
|
38794
|
+
"eslint-plugin-react-hooks/package.json"
|
|
38795
|
+
];
|
|
38796
|
+
const resolveOxlintToolchainVersions = () => {
|
|
38797
|
+
const versions = [`node=${process.version}`];
|
|
38798
|
+
for (const specifier of TOOLCHAIN_PACKAGE_SPECIFIERS) try {
|
|
38799
|
+
const packageJson = bundledRequire(specifier);
|
|
38800
|
+
const version = typeof packageJson.version === "string" ? packageJson.version : "unknown";
|
|
38801
|
+
versions.push(`${specifier}=${version}`);
|
|
38802
|
+
} catch {
|
|
38803
|
+
versions.push(`${specifier}=missing`);
|
|
38804
|
+
}
|
|
38805
|
+
return versions;
|
|
38806
|
+
};
|
|
38234
38807
|
const esmRequire = createRequire(import.meta.url);
|
|
38235
38808
|
const resolveOxlintBinary = () => {
|
|
38236
38809
|
const oxlintMainPath = esmRequire.resolve("oxlint");
|
|
@@ -38912,15 +39485,19 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
38912
39485
|
};
|
|
38913
39486
|
});
|
|
38914
39487
|
};
|
|
38915
|
-
const
|
|
38916
|
-
const
|
|
38917
|
-
for (const [name, value] of Object.entries(
|
|
39488
|
+
const buildOxlintChildEnv = (sourceEnv) => {
|
|
39489
|
+
const childEnv = {};
|
|
39490
|
+
for (const [name, value] of Object.entries(sourceEnv)) {
|
|
38918
39491
|
if (name === "NODE_OPTIONS" || name === "NODE_DEBUG") continue;
|
|
38919
39492
|
if (name.startsWith("npm_config_")) continue;
|
|
38920
|
-
|
|
39493
|
+
childEnv[name] = value;
|
|
38921
39494
|
}
|
|
38922
|
-
|
|
38923
|
-
|
|
39495
|
+
const isCompileCacheDisabled = Boolean(sourceEnv.NODE_DISABLE_COMPILE_CACHE);
|
|
39496
|
+
const isCompileCacheAlreadySet = childEnv.NODE_COMPILE_CACHE !== void 0;
|
|
39497
|
+
if (!isCompileCacheDisabled && !isCompileCacheAlreadySet) childEnv.NODE_COMPILE_CACHE = Path.join(os.tmpdir(), NODE_COMPILE_CACHE_DIR_NAME);
|
|
39498
|
+
return childEnv;
|
|
39499
|
+
};
|
|
39500
|
+
const SANITIZED_ENV = buildOxlintChildEnv(process.env);
|
|
38924
39501
|
/**
|
|
38925
39502
|
* Spawn one oxlint subprocess with hard ceilings on wall time and
|
|
38926
39503
|
* output size. Returns stdout on success; raises a tagged
|
|
@@ -38937,7 +39514,11 @@ const SANITIZED_ENV = (() => {
|
|
|
38937
39514
|
* The first three are splittable (the caller's binary-split retry
|
|
38938
39515
|
* shrinks the batch and re-spawns); the fourth isn't.
|
|
38939
39516
|
*/
|
|
38940
|
-
const spawnOxlint = (args, rootDirectory, nodeBinaryPath, spawnTimeoutMs = OXLINT_SPAWN_TIMEOUT_MS, outputMaxBytes = OXLINT_OUTPUT_MAX_BYTES) => new Promise((resolve, reject) => {
|
|
39517
|
+
const spawnOxlint = (args, rootDirectory, nodeBinaryPath, spawnTimeoutMs = OXLINT_SPAWN_TIMEOUT_MS, outputMaxBytes = OXLINT_OUTPUT_MAX_BYTES, abortSignal) => new Promise((resolve, reject) => {
|
|
39518
|
+
if (abortSignal?.aborted) {
|
|
39519
|
+
reject(new ReactDoctorError({ reason: new OxlintSpawnFailed({ cause: "lint phase aborted" }) }));
|
|
39520
|
+
return;
|
|
39521
|
+
}
|
|
38941
39522
|
const child = spawn(nodeBinaryPath, args, {
|
|
38942
39523
|
cwd: rootDirectory,
|
|
38943
39524
|
env: SANITIZED_ENV,
|
|
@@ -38947,7 +39528,14 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath, spawnTimeoutMs = OXLIN
|
|
|
38947
39528
|
"pipe"
|
|
38948
39529
|
]
|
|
38949
39530
|
});
|
|
39531
|
+
const onAbort = () => {
|
|
39532
|
+
child.kill("SIGKILL");
|
|
39533
|
+
reject(new ReactDoctorError({ reason: new OxlintSpawnFailed({ cause: "lint phase aborted" }) }));
|
|
39534
|
+
};
|
|
39535
|
+
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
39536
|
+
const clearAbortListener = () => abortSignal?.removeEventListener("abort", onAbort);
|
|
38950
39537
|
const timeoutHandle = setTimeout(() => {
|
|
39538
|
+
clearAbortListener();
|
|
38951
39539
|
child.kill("SIGKILL");
|
|
38952
39540
|
reject(new ReactDoctorError({ reason: new OxlintBatchExceeded({
|
|
38953
39541
|
kind: "timeout",
|
|
@@ -38982,10 +39570,12 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath, spawnTimeoutMs = OXLIN
|
|
|
38982
39570
|
});
|
|
38983
39571
|
child.on("error", (error) => {
|
|
38984
39572
|
clearTimeout(timeoutHandle);
|
|
39573
|
+
clearAbortListener();
|
|
38985
39574
|
reject(new ReactDoctorError({ reason: new OxlintSpawnFailed({ cause: error }) }));
|
|
38986
39575
|
});
|
|
38987
39576
|
child.on("close", (_code, signal) => {
|
|
38988
39577
|
clearTimeout(timeoutHandle);
|
|
39578
|
+
clearAbortListener();
|
|
38989
39579
|
if (didKillForSize) {
|
|
38990
39580
|
reject(new ReactDoctorError({ reason: new OxlintBatchExceeded({
|
|
38991
39581
|
kind: "output-too-large",
|
|
@@ -39052,26 +39642,28 @@ const isParallelismRelatedSpawnError = (error) => {
|
|
|
39052
39642
|
* loop with a slimmer config in that case.
|
|
39053
39643
|
*/
|
|
39054
39644
|
const spawnLintBatches = async (input) => {
|
|
39055
|
-
const { baseArgs, fileBatches, rootDirectory, nodeBinaryPath, project, onPartialFailure, onFileProgress, spawnTimeoutMs, outputMaxBytes } = input;
|
|
39645
|
+
const { baseArgs, fileBatches, rootDirectory, nodeBinaryPath, project, onPartialFailure, onFileProgress, spawnTimeoutMs, outputMaxBytes, splitTotalBudgetMs = OXLINT_SPLIT_TOTAL_BUDGET_MS, splitMaxDepth = 8, signal } = input;
|
|
39056
39646
|
const requestedConcurrency = resolveScanConcurrency(input.concurrency ?? 1);
|
|
39057
39647
|
const totalFileCount = fileBatches.reduce((sum, batch) => sum + batch.length, 0);
|
|
39058
39648
|
const runBatchPass = async (concurrency) => {
|
|
39059
39649
|
const allDiagnostics = [];
|
|
39060
39650
|
const droppedFiles = [];
|
|
39061
39651
|
let firstDropReason = null;
|
|
39062
|
-
const
|
|
39652
|
+
const splitDeadlineMs = Date.now() + splitTotalBudgetMs;
|
|
39653
|
+
const spawnLintBatch = async (batch, depth) => {
|
|
39063
39654
|
const batchArgs = [...baseArgs, ...batch];
|
|
39064
39655
|
try {
|
|
39065
|
-
return parseOxlintOutput(await spawnOxlint(batchArgs, rootDirectory, nodeBinaryPath, spawnTimeoutMs, outputMaxBytes), project, rootDirectory);
|
|
39656
|
+
return parseOxlintOutput(await spawnOxlint(batchArgs, rootDirectory, nodeBinaryPath, spawnTimeoutMs, outputMaxBytes, signal), project, rootDirectory);
|
|
39066
39657
|
} catch (error) {
|
|
39067
39658
|
if (!isSplittableReactDoctorError(error)) throw error;
|
|
39068
|
-
|
|
39659
|
+
const splitBudgetExhausted = Date.now() >= splitDeadlineMs || depth >= splitMaxDepth;
|
|
39660
|
+
if (batch.length <= 1 || splitBudgetExhausted) {
|
|
39069
39661
|
droppedFiles.push(...batch);
|
|
39070
|
-
if (firstDropReason === null) firstDropReason = error.message;
|
|
39662
|
+
if (firstDropReason === null) firstDropReason = splitBudgetExhausted && batch.length > 1 ? `${error.message} (split budget exhausted after ${splitMaxDepth} levels / ${splitTotalBudgetMs / MILLISECONDS_PER_SECOND}s)` : error.message;
|
|
39071
39663
|
return [];
|
|
39072
39664
|
}
|
|
39073
39665
|
const splitIndex = Math.ceil(batch.length / 2);
|
|
39074
|
-
return [...await spawnLintBatch(batch.slice(0, splitIndex)), ...await spawnLintBatch(batch.slice(splitIndex))];
|
|
39666
|
+
return [...await spawnLintBatch(batch.slice(0, splitIndex), depth + 1), ...await spawnLintBatch(batch.slice(splitIndex), depth + 1)];
|
|
39075
39667
|
}
|
|
39076
39668
|
};
|
|
39077
39669
|
let startedFileCount = 0;
|
|
@@ -39088,7 +39680,7 @@ const spawnLintBatches = async (input) => {
|
|
|
39088
39680
|
try {
|
|
39089
39681
|
const batchResults = await mapWithConcurrency(fileBatches, concurrency, async (batch) => {
|
|
39090
39682
|
startedFileCount += batch.length;
|
|
39091
|
-
const batchDiagnostics = await spawnLintBatch(batch);
|
|
39683
|
+
const batchDiagnostics = await spawnLintBatch(batch, 0);
|
|
39092
39684
|
scannedFileCount += batch.length;
|
|
39093
39685
|
if (onFileProgress) {
|
|
39094
39686
|
displayedFileCount = Math.min(Math.max(displayedFileCount, scannedFileCount), totalFileCount);
|
|
@@ -39149,6 +39741,22 @@ const validateRuleRegistration = () => {
|
|
|
39149
39741
|
].filter((entry) => entry !== null).join("; ");
|
|
39150
39742
|
console.warn(`[react-doctor] rule-registration drift: ${detail}`);
|
|
39151
39743
|
};
|
|
39744
|
+
const hashFileContents = (filePath) => {
|
|
39745
|
+
try {
|
|
39746
|
+
return crypto.createHash("sha1").update(NFS.readFileSync(filePath)).digest("hex");
|
|
39747
|
+
} catch {
|
|
39748
|
+
return null;
|
|
39749
|
+
}
|
|
39750
|
+
};
|
|
39751
|
+
const projectCacheSubdir = (projectDirectory) => crypto.createHash("sha256").update(projectDirectory).digest("hex").slice(0, 16);
|
|
39752
|
+
const resolveReactDoctorCacheDir = (projectDirectory) => {
|
|
39753
|
+
const cacheDirOverride = process.env["REACT_DOCTOR_CACHE_DIR"]?.trim();
|
|
39754
|
+
if (cacheDirOverride) return Path.join(cacheDirOverride, projectCacheSubdir(projectDirectory));
|
|
39755
|
+
const nodeModulesDirectory = Path.join(projectDirectory, "node_modules");
|
|
39756
|
+
if (NFS.existsSync(nodeModulesDirectory)) return Path.join(nodeModulesDirectory, ".cache", "react-doctor");
|
|
39757
|
+
return Path.join(os.tmpdir(), "react-doctor-cache", projectCacheSubdir(projectDirectory));
|
|
39758
|
+
};
|
|
39759
|
+
const sortSourceFilesByCost = (entries) => [...entries].sort((left, right) => right.sizeBytes - left.sizeBytes).map((entry) => entry.path);
|
|
39152
39760
|
/**
|
|
39153
39761
|
* Atomically (re)writes the generated oxlintrc.json. Used twice in
|
|
39154
39762
|
* the runner: once for the primary scan, once for the
|
|
@@ -39207,7 +39815,7 @@ const reactHooksJsPluginDropNote = (error) => {
|
|
|
39207
39815
|
* 6. always restore disable directives + clean up the temp dir
|
|
39208
39816
|
*/
|
|
39209
39817
|
const runOxlint = async (options) => {
|
|
39210
|
-
const { rootDirectory, project, includePaths, nodeBinaryPath = process.execPath, customRulesOnly = false, respectInlineDisables = true, adoptExistingLintConfig = true, ignoredTags = /* @__PURE__ */ new Set(), userConfig, configSourceDirectory = rootDirectory, onPartialFailure, spawnTimeoutMs, outputMaxBytes } = options;
|
|
39818
|
+
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 = "arrival" } = options;
|
|
39211
39819
|
const serverAuthFunctionNames = Array.isArray(userConfig?.serverAuthFunctionNames) ? userConfig.serverAuthFunctionNames.filter((entry) => typeof entry === "string" && entry.length > 0) : void 0;
|
|
39212
39820
|
const severityControls = buildRuleSeverityControls(userConfig);
|
|
39213
39821
|
validateRuleRegistration();
|
|
@@ -39224,30 +39832,156 @@ const runOxlint = async (options) => {
|
|
|
39224
39832
|
serverAuthFunctionNames,
|
|
39225
39833
|
severityControls,
|
|
39226
39834
|
userPlugins,
|
|
39227
|
-
disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin
|
|
39835
|
+
disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin,
|
|
39836
|
+
ruleSelection: overrides.ruleSelection
|
|
39228
39837
|
});
|
|
39229
39838
|
const restoreDisableDirectives = respectInlineDisables ? () => {} : await neutralizeDisableDirectives(rootDirectory, includePaths);
|
|
39230
39839
|
const configDirectory = NFS.mkdtempSync(Path.join(os.tmpdir(), "react-doctor-oxlintrc-"));
|
|
39231
39840
|
const configPath = Path.join(configDirectory, "oxlintrc.json");
|
|
39232
39841
|
try {
|
|
39233
|
-
const
|
|
39234
|
-
|
|
39235
|
-
|
|
39236
|
-
configPath,
|
|
39237
|
-
"--format",
|
|
39238
|
-
"json"
|
|
39239
|
-
];
|
|
39842
|
+
const oxlintBinary = resolveOxlintBinary();
|
|
39843
|
+
const sharedArgs = [];
|
|
39844
|
+
let tsconfigContent = null;
|
|
39240
39845
|
if (project.hasTypeScript) {
|
|
39241
39846
|
const tsconfigRelativePath = resolveTsConfigRelativePath(rootDirectory);
|
|
39242
|
-
if (tsconfigRelativePath)
|
|
39847
|
+
if (tsconfigRelativePath) {
|
|
39848
|
+
sharedArgs.push("--tsconfig", tsconfigRelativePath);
|
|
39849
|
+
try {
|
|
39850
|
+
tsconfigContent = NFS.readFileSync(Path.resolve(rootDirectory, tsconfigRelativePath), "utf8");
|
|
39851
|
+
} catch {
|
|
39852
|
+
tsconfigContent = null;
|
|
39853
|
+
}
|
|
39854
|
+
}
|
|
39243
39855
|
}
|
|
39244
39856
|
const combinedPatterns = collectIgnorePatterns(rootDirectory);
|
|
39245
39857
|
if (combinedPatterns.length > 0) {
|
|
39246
39858
|
const combinedIgnorePath = Path.join(configDirectory, "combined.ignore");
|
|
39247
39859
|
NFS.writeFileSync(combinedIgnorePath, `${combinedPatterns.join("\n")}\n`);
|
|
39248
|
-
|
|
39860
|
+
sharedArgs.push("--ignore-path", combinedIgnorePath);
|
|
39249
39861
|
}
|
|
39250
|
-
const
|
|
39862
|
+
const makeBaseArgs = (oxlintConfigPath) => [
|
|
39863
|
+
oxlintBinary,
|
|
39864
|
+
"-c",
|
|
39865
|
+
oxlintConfigPath,
|
|
39866
|
+
"--format",
|
|
39867
|
+
"json",
|
|
39868
|
+
...sharedArgs
|
|
39869
|
+
];
|
|
39870
|
+
const discoverScanFiles = () => lintBatchOrdering === "cost" ? sortSourceFilesByCost(listSourceFilesWithSize(rootDirectory)) : listSourceFiles(rootDirectory);
|
|
39871
|
+
const candidateFiles = includePaths !== void 0 ? includePaths : discoverScanFiles();
|
|
39872
|
+
const runConfigOverFiles = async (buildConfigForPass, configFileName, files, fileProgress) => {
|
|
39873
|
+
if (files.length === 0) return {
|
|
39874
|
+
diagnostics: [],
|
|
39875
|
+
didDropReactHooksJsPlugin: false,
|
|
39876
|
+
hadPartialFailure: false
|
|
39877
|
+
};
|
|
39878
|
+
let hadPartialFailure = false;
|
|
39879
|
+
const reportPartialFailure = (reason) => {
|
|
39880
|
+
hadPartialFailure = true;
|
|
39881
|
+
onPartialFailure?.(reason);
|
|
39882
|
+
};
|
|
39883
|
+
const passConfigPath = Path.join(configDirectory, configFileName);
|
|
39884
|
+
const passBaseArgs = makeBaseArgs(passConfigPath);
|
|
39885
|
+
const passFileBatches = batchIncludePaths(passBaseArgs, files);
|
|
39886
|
+
const spawnPass = () => spawnLintBatches({
|
|
39887
|
+
baseArgs: passBaseArgs,
|
|
39888
|
+
fileBatches: passFileBatches,
|
|
39889
|
+
rootDirectory,
|
|
39890
|
+
nodeBinaryPath,
|
|
39891
|
+
project,
|
|
39892
|
+
onPartialFailure: reportPartialFailure,
|
|
39893
|
+
onFileProgress: fileProgress,
|
|
39894
|
+
spawnTimeoutMs,
|
|
39895
|
+
outputMaxBytes,
|
|
39896
|
+
concurrency: options.concurrency,
|
|
39897
|
+
signal: options.signal
|
|
39898
|
+
});
|
|
39899
|
+
writeOxlintConfig(passConfigPath, buildConfigForPass({}));
|
|
39900
|
+
try {
|
|
39901
|
+
return {
|
|
39902
|
+
diagnostics: await spawnPass(),
|
|
39903
|
+
didDropReactHooksJsPlugin: false,
|
|
39904
|
+
hadPartialFailure
|
|
39905
|
+
};
|
|
39906
|
+
} catch (error) {
|
|
39907
|
+
const reactHooksJsDropNote = reactHooksJsPluginDropNote(error);
|
|
39908
|
+
if (reactHooksJsDropNote === null) throw error;
|
|
39909
|
+
writeOxlintConfig(passConfigPath, buildConfigForPass({ disableReactHooksJsPlugin: true }));
|
|
39910
|
+
const diagnostics = await spawnPass();
|
|
39911
|
+
reportPartialFailure(reactHooksJsDropNote);
|
|
39912
|
+
return {
|
|
39913
|
+
diagnostics,
|
|
39914
|
+
didDropReactHooksJsPlugin: true,
|
|
39915
|
+
hadPartialFailure
|
|
39916
|
+
};
|
|
39917
|
+
}
|
|
39918
|
+
};
|
|
39919
|
+
if (perFileLintCacheEnabled && respectInlineDisables && !project.hasReactCompiler && extendsPaths.length === 0 && userPlugins.length === 0) {
|
|
39920
|
+
const rulesetHash = computeRulesetHash({
|
|
39921
|
+
config: buildConfig({
|
|
39922
|
+
extendsPaths: [],
|
|
39923
|
+
ruleSelection: "cacheable"
|
|
39924
|
+
}),
|
|
39925
|
+
toolchainVersions: resolveOxlintToolchainVersions(),
|
|
39926
|
+
ignorePatterns: combinedPatterns,
|
|
39927
|
+
tsconfigContent
|
|
39928
|
+
});
|
|
39929
|
+
const cache = createFileLintCache(resolveReactDoctorCacheDir(rootDirectory), rulesetHash);
|
|
39930
|
+
const cacheKeyByFile = /* @__PURE__ */ new Map();
|
|
39931
|
+
const missFiles = [];
|
|
39932
|
+
const replayedDiagnostics = [];
|
|
39933
|
+
for (const candidateFile of candidateFiles) {
|
|
39934
|
+
const contentHash = hashFileContents(Path.resolve(rootDirectory, candidateFile));
|
|
39935
|
+
if (contentHash === null) {
|
|
39936
|
+
missFiles.push(candidateFile);
|
|
39937
|
+
continue;
|
|
39938
|
+
}
|
|
39939
|
+
const cacheKey = `${candidateFile.replaceAll("\\", "/")}${contentHash}`;
|
|
39940
|
+
cacheKeyByFile.set(candidateFile, cacheKey);
|
|
39941
|
+
const cachedDiagnostics = cache.lookup(cacheKey);
|
|
39942
|
+
if (cachedDiagnostics === null) missFiles.push(candidateFile);
|
|
39943
|
+
else replayedDiagnostics.push(...cachedDiagnostics);
|
|
39944
|
+
}
|
|
39945
|
+
const cacheHitFileCount = candidateFiles.length - missFiles.length;
|
|
39946
|
+
const cacheableResult = await runConfigOverFiles((overrides) => buildConfig({
|
|
39947
|
+
extendsPaths: [],
|
|
39948
|
+
ruleSelection: "cacheable",
|
|
39949
|
+
disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin
|
|
39950
|
+
}), "oxlintrc.cacheable.json", missFiles, void 0);
|
|
39951
|
+
const sidecarResult = await runConfigOverFiles(() => buildConfig({
|
|
39952
|
+
extendsPaths: [],
|
|
39953
|
+
ruleSelection: "sidecar"
|
|
39954
|
+
}), "oxlintrc.sidecar.json", candidateFiles, options.onFileProgress);
|
|
39955
|
+
onCacheStats?.(cacheHitFileCount, candidateFiles.length);
|
|
39956
|
+
const missFileByNormalizedPath = /* @__PURE__ */ new Map();
|
|
39957
|
+
for (const missFile of missFiles) missFileByNormalizedPath.set(missFile.replaceAll("\\", "/"), missFile);
|
|
39958
|
+
const freshDiagnosticsByFile = /* @__PURE__ */ new Map();
|
|
39959
|
+
let isAttributionSound = true;
|
|
39960
|
+
for (const diagnostic of cacheableResult.diagnostics) {
|
|
39961
|
+
const missFile = missFileByNormalizedPath.get(diagnostic.filePath);
|
|
39962
|
+
if (missFile === void 0) {
|
|
39963
|
+
isAttributionSound = false;
|
|
39964
|
+
break;
|
|
39965
|
+
}
|
|
39966
|
+
const fileDiagnostics = freshDiagnosticsByFile.get(missFile) ?? [];
|
|
39967
|
+
fileDiagnostics.push(diagnostic);
|
|
39968
|
+
freshDiagnosticsByFile.set(missFile, fileDiagnostics);
|
|
39969
|
+
}
|
|
39970
|
+
if (!cacheableResult.didDropReactHooksJsPlugin && !cacheableResult.hadPartialFailure && isAttributionSound) {
|
|
39971
|
+
for (const missFile of missFiles) {
|
|
39972
|
+
const cacheKey = cacheKeyByFile.get(missFile);
|
|
39973
|
+
if (cacheKey !== void 0) cache.store(cacheKey, freshDiagnosticsByFile.get(missFile) ?? []);
|
|
39974
|
+
}
|
|
39975
|
+
cache.persist();
|
|
39976
|
+
}
|
|
39977
|
+
return dedupeDiagnostics([
|
|
39978
|
+
...replayedDiagnostics,
|
|
39979
|
+
...cacheableResult.diagnostics,
|
|
39980
|
+
...sidecarResult.diagnostics
|
|
39981
|
+
]);
|
|
39982
|
+
}
|
|
39983
|
+
const baseArgs = makeBaseArgs(configPath);
|
|
39984
|
+
const fileBatches = batchIncludePaths(baseArgs, candidateFiles);
|
|
39251
39985
|
const runBatches = () => spawnLintBatches({
|
|
39252
39986
|
baseArgs,
|
|
39253
39987
|
fileBatches,
|
|
@@ -39258,7 +39992,8 @@ const runOxlint = async (options) => {
|
|
|
39258
39992
|
onFileProgress: options.onFileProgress,
|
|
39259
39993
|
spawnTimeoutMs,
|
|
39260
39994
|
outputMaxBytes,
|
|
39261
|
-
concurrency: options.concurrency
|
|
39995
|
+
concurrency: options.concurrency,
|
|
39996
|
+
signal: options.signal
|
|
39262
39997
|
});
|
|
39263
39998
|
writeOxlintConfig(configPath, buildConfig({ extendsPaths }));
|
|
39264
39999
|
try {
|
|
@@ -39337,9 +40072,11 @@ var Linter = class Linter extends Service()("react-doctor/Linter") {
|
|
|
39337
40072
|
const spawnTimeoutMs = yield* OxlintSpawnTimeoutMs;
|
|
39338
40073
|
const outputMaxBytes = yield* OxlintOutputMaxBytes;
|
|
39339
40074
|
const concurrency = yield* OxlintConcurrency;
|
|
40075
|
+
const lintBatchOrdering = yield* LintBatchOrdering;
|
|
40076
|
+
const perFileLintCacheEnabled = yield* PerFileLintCacheEnabled;
|
|
39340
40077
|
const collectedFailures = [];
|
|
39341
40078
|
const diagnostics = yield* tryPromise({
|
|
39342
|
-
try: () => runOxlint({
|
|
40079
|
+
try: (signal) => runOxlint({
|
|
39343
40080
|
rootDirectory: input.rootDirectory,
|
|
39344
40081
|
project: input.project,
|
|
39345
40082
|
includePaths: input.includePaths ? [...input.includePaths] : void 0,
|
|
@@ -39354,9 +40091,13 @@ var Linter = class Linter extends Service()("react-doctor/Linter") {
|
|
|
39354
40091
|
collectedFailures.push(reason);
|
|
39355
40092
|
},
|
|
39356
40093
|
onFileProgress: input.onFileProgress,
|
|
40094
|
+
perFileLintCacheEnabled,
|
|
40095
|
+
onCacheStats: input.onCacheStats,
|
|
39357
40096
|
spawnTimeoutMs,
|
|
39358
40097
|
outputMaxBytes,
|
|
39359
|
-
concurrency
|
|
40098
|
+
concurrency,
|
|
40099
|
+
signal,
|
|
40100
|
+
lintBatchOrdering
|
|
39360
40101
|
}),
|
|
39361
40102
|
catch: ensureReactDoctorError
|
|
39362
40103
|
});
|
|
@@ -39748,14 +40489,49 @@ const parseArtifactFromBody = (body) => {
|
|
|
39748
40489
|
}
|
|
39749
40490
|
return null;
|
|
39750
40491
|
};
|
|
39751
|
-
const
|
|
40492
|
+
const isSupplyChainCacheDisabled = () => {
|
|
40493
|
+
const noCache = process.env["REACT_DOCTOR_NO_CACHE"]?.toLowerCase() ?? "";
|
|
40494
|
+
return noCache === "1" || noCache === "true";
|
|
40495
|
+
};
|
|
40496
|
+
const supplyChainCacheFile = (cacheDirectory, dependency) => {
|
|
40497
|
+
const purlHash = crypto.createHash("sha256").update(toPurl(dependency)).digest("hex").slice(0, 16);
|
|
40498
|
+
return Path.join(cacheDirectory, SUPPLY_CHAIN_CACHE_SUBDIR, `${purlHash}.json`);
|
|
40499
|
+
};
|
|
40500
|
+
const readCachedSocketBody = (cacheFile) => {
|
|
40501
|
+
try {
|
|
40502
|
+
const entry = JSON.parse(NFS.readFileSync(cacheFile, "utf-8"));
|
|
40503
|
+
if (typeof entry === "object" && entry !== null && "fetchedAtMs" in entry && "body" in entry && typeof entry.fetchedAtMs === "number" && typeof entry.body === "string" && Date.now() - entry.fetchedAtMs <= 864e5) return entry.body;
|
|
40504
|
+
} catch {}
|
|
40505
|
+
return null;
|
|
40506
|
+
};
|
|
40507
|
+
const writeCachedSocketBody = (cacheFile, body) => {
|
|
40508
|
+
try {
|
|
40509
|
+
NFS.mkdirSync(Path.dirname(cacheFile), { recursive: true });
|
|
40510
|
+
NFS.writeFileSync(cacheFile, JSON.stringify({
|
|
40511
|
+
fetchedAtMs: Date.now(),
|
|
40512
|
+
body
|
|
40513
|
+
}));
|
|
40514
|
+
} catch {}
|
|
40515
|
+
};
|
|
40516
|
+
const fetchSocketArtifact = (dependency, cacheDirectory) => tryPromise(async (signal) => {
|
|
40517
|
+
const cacheFile = cacheDirectory === null ? null : supplyChainCacheFile(cacheDirectory, dependency);
|
|
40518
|
+
if (cacheFile !== null) {
|
|
40519
|
+
const cachedBody = readCachedSocketBody(cacheFile);
|
|
40520
|
+
if (cachedBody !== null) {
|
|
40521
|
+
const cachedArtifact = parseArtifactFromBody(cachedBody);
|
|
40522
|
+
if (cachedArtifact !== null) return cachedArtifact;
|
|
40523
|
+
}
|
|
40524
|
+
}
|
|
39752
40525
|
const requestUrl = `${SOCKET_FREE_PURL_API_BASE}/${encodeURIComponent(toPurl(dependency))}`;
|
|
39753
40526
|
const response = await fetch(requestUrl, {
|
|
39754
40527
|
headers: { "User-Agent": SOCKET_FREE_USER_AGENT },
|
|
39755
40528
|
signal
|
|
39756
40529
|
});
|
|
39757
40530
|
if (!response.ok) return null;
|
|
39758
|
-
|
|
40531
|
+
const body = await response.text();
|
|
40532
|
+
const artifact = parseArtifactFromBody(body);
|
|
40533
|
+
if (artifact !== null && cacheFile !== null) writeCachedSocketBody(cacheFile, body);
|
|
40534
|
+
return artifact;
|
|
39759
40535
|
}).pipe(timeout(FETCH_TIMEOUT_MS), orElseSucceed(() => null), tap$1((artifact) => {
|
|
39760
40536
|
const scoreAttributes = {};
|
|
39761
40537
|
if (artifact !== null) {
|
|
@@ -39860,7 +40636,8 @@ const checkSupplyChain = (input) => gen(function* () {
|
|
|
39860
40636
|
const packageJsonPath = Path.join(input.rootDirectory, "package.json");
|
|
39861
40637
|
const dependencies = collectDependenciesToScore(readPackageJson(packageJsonPath), readPackageJsonText(packageJsonPath), options.includeDevDependencies);
|
|
39862
40638
|
if (dependencies.length === 0) return [];
|
|
39863
|
-
const
|
|
40639
|
+
const cacheDirectory = isSupplyChainCacheDisabled() ? null : resolveReactDoctorCacheDir(input.rootDirectory);
|
|
40640
|
+
const artifacts = yield* forEach$1(dependencies, (dependency) => fetchSocketArtifact(dependency, cacheDirectory), { concurrency: 8 }).pipe(timeoutOption(input.totalTimeoutMs ?? 9e4), map$3((maybeArtifacts) => getOrElse$1(maybeArtifacts, () => [])));
|
|
39864
40641
|
const diagnostics = [];
|
|
39865
40642
|
for (let index = 0; index < dependencies.length; index += 1) {
|
|
39866
40643
|
const artifact = artifacts[index];
|
|
@@ -39885,6 +40662,10 @@ const checkSupplyChain = (input) => gen(function* () {
|
|
|
39885
40662
|
* The underlying `checkSupplyChain` Effect is total/fail-open — per-package
|
|
39886
40663
|
* timeouts and network failures recover to "skip" — so the stream never
|
|
39887
40664
|
* fails, mirroring `DeadCode`'s stream shape so the two compose the same way.
|
|
40665
|
+
* The orchestrator (`run-inspect.ts`) consumes this stream on a background
|
|
40666
|
+
* fiber whose network time overlaps the lint pass, joined under a generous
|
|
40667
|
+
* wall-clock budget; a budget expiry is the same fail-open outcome as a Socket
|
|
40668
|
+
* outage.
|
|
39888
40669
|
*/
|
|
39889
40670
|
var SupplyChain = class SupplyChain extends Service()("react-doctor/SupplyChain") {
|
|
39890
40671
|
static layerNode = succeed$3(SupplyChain, SupplyChain.of({ run: (input) => unwrap(checkSupplyChain(input).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)), withSpan("SupplyChain.run"))) }));
|
|
@@ -39943,18 +40724,45 @@ const formatLintFailText = (reasonTag, nodeVersion) => {
|
|
|
39943
40724
|
*
|
|
39944
40725
|
* Phases:
|
|
39945
40726
|
*
|
|
39946
|
-
* 1. Config.resolve(directory) → Project.discover → Git metadata
|
|
40727
|
+
* 1. Config.resolve(directory) → Project.discover → Git metadata.
|
|
40728
|
+
* The GitHub viewer-permission lookup is forked onto a background
|
|
40729
|
+
* fiber here and joined late (it feeds score metadata, not
|
|
40730
|
+
* diagnostics).
|
|
39947
40731
|
* 2. beforeLint hook (e.g. CLI renders the project-detection block)
|
|
39948
40732
|
* 3. environment checks (reduced-motion + pnpm hardening +
|
|
39949
|
-
* expo/react-native
|
|
39950
|
-
*
|
|
39951
|
-
*
|
|
39952
|
-
*
|
|
39953
|
-
*
|
|
39954
|
-
* fiber
|
|
39955
|
-
*
|
|
39956
|
-
*
|
|
39957
|
-
*
|
|
40733
|
+
* expo/react-native), collected synchronously. The heavier
|
|
40734
|
+
* content-regex security scan is forked instead (like supply-chain
|
|
40735
|
+
* below) and joined before the concat, so its CPU overlaps lint
|
|
40736
|
+
* rather than blocking the event loop before it.
|
|
40737
|
+
* 4. The supply-chain check (Socket.dev) is forked onto a background
|
|
40738
|
+
* fiber so its ~100% network-bound time overlaps the ~100%
|
|
40739
|
+
* CPU/subprocess-bound lint pass below, collapsing two serial
|
|
40740
|
+
* phases into roughly `max(supplyChain, lint)`. It is capped by
|
|
40741
|
+
* `SupplyChainOverlapTimeoutMs` (measured from fork) so a hung
|
|
40742
|
+
* socket can't drag out its join; on timeout it fails open to no
|
|
40743
|
+
* diagnostics — the same outcome class as a Socket outage.
|
|
40744
|
+
* 5. Linter.run runs; DeadCode.run runs concurrently (forked child
|
|
40745
|
+
* fiber) ONLY when the memory gate has headroom to run the 8 GB
|
|
40746
|
+
* dead-code child alongside the oxlint workers — or when overlap is
|
|
40747
|
+
* forced via REACT_DOCTOR_DEAD_CODE_OVERLAP. Otherwise dead-code
|
|
40748
|
+
* runs sequentially after lint, exactly as it did pre-overlap. The
|
|
40749
|
+
* fiber is joined (or interrupted, SIGKILLing its worker, on lint
|
|
40750
|
+
* failure) before diagnostics are concatenated. The afterLint hook
|
|
40751
|
+
* fires between lint and dead-code. Progress spinner labels AND the
|
|
40752
|
+
* final diagnostic / score order stay independent of execution
|
|
40753
|
+
* order, so terminal output is identical either way; supply-chain
|
|
40754
|
+
* rides alongside without a spinner.
|
|
40755
|
+
* 6. Join the supply-chain fiber, then assemble the diagnostics in a
|
|
40756
|
+
* FIXED order (env, security-scan, supply-chain, lint, dead-code) so the output is
|
|
40757
|
+
* byte-identical regardless of which fiber settled first. The
|
|
40758
|
+
* viewer-permission fiber is joined later, during score-metadata
|
|
40759
|
+
* assembly (it feeds score metadata, not diagnostics). The per-element
|
|
40760
|
+
* `Reporter.emit` side-channel now interleaves supply-chain with lint
|
|
40761
|
+
* emits, so capture-order assertions must target the deterministic
|
|
40762
|
+
* concat below, not emit order (production `Reporter.layerNoop` makes
|
|
40763
|
+
* emit a no-op).
|
|
40764
|
+
* 7. Reporter.finalize
|
|
40765
|
+
* 8. Score.compute against the surface-filtered diagnostic set
|
|
39958
40766
|
*
|
|
39959
40767
|
* The orchestrator owns spinner lifecycle via `Progress`; callers
|
|
39960
40768
|
* choose `Progress.layerOra(...)` for CLI feedback or
|
|
@@ -40006,16 +40814,27 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40006
40814
|
...checkPnpmHardening(scanDirectory),
|
|
40007
40815
|
...checkReactServerComponentsAdvisory(scanDirectory, project),
|
|
40008
40816
|
...checkExpoProject(scanDirectory, project),
|
|
40009
|
-
...checkReactNativeProject(scanDirectory, project)
|
|
40010
|
-
...checkSecurityScan(scanDirectory, {
|
|
40011
|
-
project,
|
|
40012
|
-
ignoredTags: input.ignoredTags
|
|
40013
|
-
})
|
|
40817
|
+
...checkReactNativeProject(scanDirectory, project)
|
|
40014
40818
|
])));
|
|
40015
|
-
const
|
|
40819
|
+
const securityScanFiber = yield* forkChild(runCollect(applyPerElementPipeline(isDiffMode ? empty$4 : unwrap(promise(() => checkSecurityScanCooperative(scanDirectory, {
|
|
40820
|
+
project,
|
|
40821
|
+
ignoredTags: input.ignoredTags
|
|
40822
|
+
})).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)))))).pipe(withSpan("SecurityScan.run")));
|
|
40823
|
+
const shouldRunSupplyChain = !isDiffMode || (input.supplyChainManifestChanged ?? false);
|
|
40824
|
+
const supplyChainOverlapTimeout = yield* SupplyChainOverlapTimeoutMs;
|
|
40825
|
+
const supplyChainFiber = yield* forkChild(shouldRunSupplyChain ? runCollect(applyPerElementPipeline(supplyChainService.run({
|
|
40016
40826
|
rootDirectory: scanDirectory,
|
|
40017
40827
|
userConfig: resolvedConfig.config
|
|
40018
|
-
})))
|
|
40828
|
+
}))).pipe(map$3((diagnostics) => ({
|
|
40829
|
+
diagnostics,
|
|
40830
|
+
timedOut: false
|
|
40831
|
+
})), timeout(supplyChainOverlapTimeout), orElseSucceed(() => ({
|
|
40832
|
+
diagnostics: [],
|
|
40833
|
+
timedOut: true
|
|
40834
|
+
}))) : succeed$2({
|
|
40835
|
+
diagnostics: [],
|
|
40836
|
+
timedOut: false
|
|
40837
|
+
}));
|
|
40019
40838
|
const lintFailure = yield* make$13({
|
|
40020
40839
|
didFail: false,
|
|
40021
40840
|
reason: null,
|
|
@@ -40026,12 +40845,49 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40026
40845
|
didFail: false,
|
|
40027
40846
|
reason: null
|
|
40028
40847
|
});
|
|
40029
|
-
const scanConcurrency = yield* OxlintConcurrency;
|
|
40848
|
+
const scanConcurrency = resolveScanConcurrency(yield* OxlintConcurrency);
|
|
40849
|
+
const lintPhaseTimeoutMs = yield* LintPhaseTimeoutMs;
|
|
40850
|
+
const deadCodePhaseTimeoutMs = yield* DeadCodePhaseTimeoutMs;
|
|
40851
|
+
const resolveDeadCodePhaseTimeoutMs = (scaledPhaseTimeoutMs) => deadCodePhaseTimeoutMs === 15e4 ? scaledPhaseTimeoutMs : deadCodePhaseTimeoutMs;
|
|
40030
40852
|
const workerCountSuffix = scanConcurrency > 1 ? ` ${highlighter.dim(`[~${scanConcurrency} workers]`)}` : "";
|
|
40853
|
+
const shouldRunDeadCode = input.runDeadCode && !isDiffMode && (showWarnings || deadCodeMaySurfaceWhenWarningsHidden(resolvedConfig.config));
|
|
40854
|
+
const deadCodeOverlapMode = yield* DeadCodeOverlap;
|
|
40855
|
+
const shouldOverlapDeadCode = shouldRunDeadCode && deadCodeOverlapMode === "on";
|
|
40856
|
+
const deadCodeParseConcurrency = shouldOverlapDeadCode ? Math.max(1, Math.floor(scanConcurrency * DEAD_CODE_OVERLAP_PARSE_SHARE)) : void 0;
|
|
40857
|
+
const lintConcurrency = deadCodeParseConcurrency === void 0 ? scanConcurrency : Math.max(1, scanConcurrency - deadCodeParseConcurrency);
|
|
40858
|
+
const buildCollectDeadCode = (deadCodeTimeout) => runCollect(applyPerElementPipeline(deadCodeService.run({
|
|
40859
|
+
rootDirectory: scanDirectory,
|
|
40860
|
+
userConfig: resolvedConfig.config,
|
|
40861
|
+
parseConcurrency: deadCodeParseConcurrency,
|
|
40862
|
+
workerTimeoutMs: deadCodeTimeout.workerTimeoutMs
|
|
40863
|
+
}).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
|
|
40864
|
+
yield* set(deadCodeFailure, {
|
|
40865
|
+
didFail: true,
|
|
40866
|
+
reason: error.message
|
|
40867
|
+
});
|
|
40868
|
+
return empty$4;
|
|
40869
|
+
})))))).pipe(timeoutOption(deadCodeTimeout.phaseTimeoutMs), flatMap$2(match$3({
|
|
40870
|
+
onNone: () => set(deadCodeFailure, {
|
|
40871
|
+
didFail: true,
|
|
40872
|
+
reason: `Dead-code analysis exceeded ${Math.round(deadCodeTimeout.phaseTimeoutMs / MILLISECONDS_PER_SECOND)}s and was skipped.`
|
|
40873
|
+
}).pipe(as([])),
|
|
40874
|
+
onSome: succeed$2
|
|
40875
|
+
})));
|
|
40876
|
+
const overlapDeadCodeTimeout = resolveDeadCodeTimeout({
|
|
40877
|
+
sourceFileCount: project.sourceFileCount,
|
|
40878
|
+
deadCodeConcurrency: deadCodeParseConcurrency ?? scanConcurrency,
|
|
40879
|
+
fullConcurrency: scanConcurrency
|
|
40880
|
+
});
|
|
40881
|
+
const deadCodeFiber = shouldOverlapDeadCode ? yield* forkChild(buildCollectDeadCode({
|
|
40882
|
+
workerTimeoutMs: overlapDeadCodeTimeout.workerTimeoutMs,
|
|
40883
|
+
phaseTimeoutMs: resolveDeadCodePhaseTimeoutMs(overlapDeadCodeTimeout.phaseTimeoutMs)
|
|
40884
|
+
})) : null;
|
|
40031
40885
|
const scanProgress = yield* progressService.start("Scanning...");
|
|
40032
40886
|
const scanStartTime = Date.now();
|
|
40033
40887
|
let lastReportedTotalFileCount = 0;
|
|
40034
|
-
|
|
40888
|
+
let lintCacheHitFileCount = null;
|
|
40889
|
+
let lintCacheTotalFileCount = null;
|
|
40890
|
+
const baseLintStream = linterService.run({
|
|
40035
40891
|
rootDirectory: scanDirectory,
|
|
40036
40892
|
project,
|
|
40037
40893
|
includePaths: lintIncludePaths ?? void 0,
|
|
@@ -40045,6 +40901,10 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40045
40901
|
onFileProgress: (scannedFileCount, totalFileCount) => {
|
|
40046
40902
|
lastReportedTotalFileCount = totalFileCount;
|
|
40047
40903
|
runSync(scanProgress.update(`Scanning files (${scannedFileCount}/${totalFileCount})${workerCountSuffix}...`));
|
|
40904
|
+
},
|
|
40905
|
+
onCacheStats: (cacheHitFileCount, totalConsideredFileCount) => {
|
|
40906
|
+
lintCacheHitFileCount = cacheHitFileCount;
|
|
40907
|
+
lintCacheTotalFileCount = totalConsideredFileCount;
|
|
40048
40908
|
}
|
|
40049
40909
|
}).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
|
|
40050
40910
|
yield* set(lintFailure, {
|
|
@@ -40054,36 +40914,56 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40054
40914
|
reasonKind: error.reason._tag === "OxlintUnavailable" ? error.reason.kind : null
|
|
40055
40915
|
});
|
|
40056
40916
|
return empty$4;
|
|
40057
|
-
}))))
|
|
40917
|
+
}))));
|
|
40918
|
+
const lintCollected = yield* runCollect(applyPerElementPipeline(shouldOverlapDeadCode ? baseLintStream.pipe(provideService(OxlintConcurrency, lintConcurrency)) : baseLintStream)).pipe(timeoutOption(lintPhaseTimeoutMs), flatMap$2(match$3({
|
|
40919
|
+
onNone: () => set(lintFailure, {
|
|
40920
|
+
didFail: true,
|
|
40921
|
+
reason: `Lint analysis exceeded ${lintPhaseTimeoutMs / MILLISECONDS_PER_SECOND}s and was skipped.`,
|
|
40922
|
+
reasonTag: "OxlintBatchExceeded",
|
|
40923
|
+
reasonKind: null
|
|
40924
|
+
}).pipe(as([])),
|
|
40925
|
+
onSome: succeed$2
|
|
40926
|
+
})));
|
|
40058
40927
|
const lintFailureState = yield* get$2(lintFailure);
|
|
40059
40928
|
yield* afterLint(lintFailureState.didFail);
|
|
40060
40929
|
if (lintFailureState.didFail) yield* scanProgress.fail(formatLintFailText(lintFailureState.reasonTag, process.version));
|
|
40061
40930
|
const totalFileCount = lastReportedTotalFileCount || (lintIncludePaths?.length ?? project.sourceFileCount);
|
|
40062
40931
|
const scannedFilesLabel = `${totalFileCount} ${totalFileCount === 1 ? "file" : "files"}`;
|
|
40063
|
-
|
|
40064
|
-
|
|
40065
|
-
|
|
40066
|
-
|
|
40067
|
-
|
|
40068
|
-
|
|
40069
|
-
|
|
40070
|
-
|
|
40932
|
+
let deadCodeCollected = [];
|
|
40933
|
+
if (lintFailureState.didFail) {
|
|
40934
|
+
if (deadCodeFiber !== null) yield* interrupt(deadCodeFiber);
|
|
40935
|
+
} else if (shouldRunDeadCode) {
|
|
40936
|
+
yield* scanProgress.update(`Scanned ${scannedFilesLabel}, analyzing dead code...`);
|
|
40937
|
+
const sequentialDeadCodeTimeout = resolveDeadCodeTimeout({
|
|
40938
|
+
sourceFileCount: totalFileCount,
|
|
40939
|
+
deadCodeConcurrency: scanConcurrency,
|
|
40940
|
+
fullConcurrency: scanConcurrency
|
|
40071
40941
|
});
|
|
40072
|
-
|
|
40073
|
-
|
|
40074
|
-
|
|
40942
|
+
deadCodeCollected = deadCodeFiber !== null ? yield* join(deadCodeFiber) : yield* buildCollectDeadCode({
|
|
40943
|
+
workerTimeoutMs: sequentialDeadCodeTimeout.workerTimeoutMs,
|
|
40944
|
+
phaseTimeoutMs: resolveDeadCodePhaseTimeoutMs(sequentialDeadCodeTimeout.phaseTimeoutMs)
|
|
40945
|
+
});
|
|
40946
|
+
}
|
|
40947
|
+
const deadCodeFailureState = lintFailureState.didFail ? {
|
|
40948
|
+
didFail: false,
|
|
40949
|
+
reason: null
|
|
40950
|
+
} : yield* get$2(deadCodeFailure);
|
|
40075
40951
|
const scanElapsedMilliseconds = Date.now() - scanStartTime;
|
|
40076
40952
|
const scanElapsedSeconds = (scanElapsedMilliseconds / MILLISECONDS_PER_SECOND).toFixed(1);
|
|
40077
40953
|
if (!lintFailureState.didFail) if (deadCodeFailureState.didFail) yield* scanProgress.fail(DEAD_CODE_FAIL_TEXT);
|
|
40078
40954
|
else if (input.suppressScanSummary) yield* scanProgress.stop();
|
|
40079
40955
|
else yield* scanProgress.succeed(`Scanned ${scannedFilesLabel} in ${scanElapsedSeconds}s${workerCountSuffix}`);
|
|
40956
|
+
const supplyChainResult = yield* join(supplyChainFiber);
|
|
40957
|
+
const supplyChainCollected = supplyChainResult.diagnostics;
|
|
40958
|
+
const securityScanCollected = yield* join(securityScanFiber);
|
|
40080
40959
|
yield* reporterService.finalize;
|
|
40081
|
-
const finalDiagnostics = assignFixGroups([
|
|
40960
|
+
const finalDiagnostics = sortDiagnosticsStable(assignFixGroups([
|
|
40082
40961
|
...envCollected,
|
|
40962
|
+
...securityScanCollected,
|
|
40083
40963
|
...supplyChainCollected,
|
|
40084
40964
|
...lintCollected,
|
|
40085
40965
|
...deadCodeCollected
|
|
40086
|
-
]);
|
|
40966
|
+
]));
|
|
40087
40967
|
const githubViewerPermission = yield* join(githubViewerPermissionFiber);
|
|
40088
40968
|
const scoreMetadata = {
|
|
40089
40969
|
...repo !== null ? { repo } : {},
|
|
@@ -40119,9 +40999,14 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40119
40999
|
lintPartialFailures,
|
|
40120
41000
|
didDeadCodeFail: deadCodeFailureState.didFail,
|
|
40121
41001
|
deadCodeFailureReason: deadCodeFailureState.reason,
|
|
41002
|
+
deadCodeOverlapped: shouldOverlapDeadCode,
|
|
40122
41003
|
scannedFileCount: totalFileCount,
|
|
40123
41004
|
scannedFilePaths,
|
|
40124
|
-
scanElapsedMilliseconds
|
|
41005
|
+
scanElapsedMilliseconds,
|
|
41006
|
+
scanConcurrency,
|
|
41007
|
+
supplyChainOverlapTimedOut: supplyChainResult.timedOut,
|
|
41008
|
+
lintCacheHitFileCount,
|
|
41009
|
+
lintCacheTotalFileCount
|
|
40125
41010
|
};
|
|
40126
41011
|
}).pipe(withSpan("runInspect", { attributes: {
|
|
40127
41012
|
"inspect.directory": input.directory,
|
|
@@ -40129,7 +41014,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40129
41014
|
"inspect.runDeadCode": input.runDeadCode,
|
|
40130
41015
|
"inspect.isCi": input.isCi,
|
|
40131
41016
|
"inspect.scoreSurface": input.scoreSurface ?? "score"
|
|
40132
|
-
} }));
|
|
41017
|
+
} }), (scanProgram) => flatMap$2(ScanDeadlineMs, (scanDeadlineMs) => scanProgram.pipe(timeout(scanDeadlineMs), catchTag$1("TimeoutError", () => new ReactDoctorError({ reason: new ScanDeadlineExceeded({ detail: `${scanDeadlineMs / MILLISECONDS_PER_SECOND}s elapsed` }) })))));
|
|
40133
41018
|
const parseNodeVersion = (versionString) => {
|
|
40134
41019
|
const [major = 0, minor = 0, patch = 0] = versionString.replace(/^v/, "").trim().split(".").map(Number);
|
|
40135
41020
|
return {
|
|
@@ -40673,6 +41558,7 @@ const clearCaches = () => {
|
|
|
40673
41558
|
clearIgnorePatternsCache();
|
|
40674
41559
|
clearPackageRoleCache();
|
|
40675
41560
|
clearAutoSuppressionCaches();
|
|
41561
|
+
clearMinifiedFileCache();
|
|
40676
41562
|
};
|
|
40677
41563
|
const toJsonReport = (result, options) => buildJsonReport({
|
|
40678
41564
|
version: options.version,
|
|
@@ -40696,4 +41582,4 @@ const toJsonReport = (result, options) => buildJsonReport({
|
|
|
40696
41582
|
export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
|
|
40697
41583
|
|
|
40698
41584
|
//# sourceMappingURL=index.js.map
|
|
40699
|
-
//# debugId=
|
|
41585
|
+
//# debugId=b9d1a98e-f5ec-5357-a08b-f84864fdfa20
|