react-doctor 0.5.7-dev.9f733f7 → 0.5.8-dev.f4e8e4b
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 +1329 -289
- package/dist/index.d.ts +15 -1
- package/dist/index.js +903 -135
- package/dist/lsp.js +903 -135
- 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]="33508ee6-c977-5b5f-8585-9939928ce74d")}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
|
/**
|
|
@@ -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,7 @@ 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 PER_WORKER_MEM_BUDGET_BYTES = 1024 * 1024 * 1024;
|
|
33594
33698
|
const DEFAULT_BRANCH_CANDIDATES = ["main", "master"];
|
|
33595
33699
|
const ADOPTABLE_LINT_CONFIG_FILENAMES = [".oxlintrc.json", ".eslintrc.json"];
|
|
33596
33700
|
const GIT_SHOW_MAX_BUFFER_BYTES = 10 * 1024 * 1024;
|
|
@@ -33640,7 +33744,16 @@ const STAGED_FILES_PROJECT_CONFIG_FILENAMES = [
|
|
|
33640
33744
|
];
|
|
33641
33745
|
const OXLINT_OUTPUT_MAX_BYTES = 50 * 1024 * 1024;
|
|
33642
33746
|
const OXLINT_SPAWN_TIMEOUT_MS = 6e4;
|
|
33747
|
+
const NODE_COMPILE_CACHE_DIR_NAME = "node-compile-cache";
|
|
33748
|
+
const DEAD_CODE_WORKER_TIMEOUT_MS = 12e4;
|
|
33749
|
+
const OXLINT_SPLIT_TOTAL_BUDGET_MS = 18e4;
|
|
33750
|
+
const DEAD_CODE_PHASE_TIMEOUT_MS = 15e4;
|
|
33751
|
+
const LINT_PHASE_TIMEOUT_MS = 3e5;
|
|
33752
|
+
const SCAN_TOTAL_DEADLINE_MS = 9e5;
|
|
33643
33753
|
const DEAD_CODE_WORKER_MAX_OLD_SPACE_MB = 8192;
|
|
33754
|
+
const DEAD_CODE_TIMEOUT_CEILING_MS = 6e5;
|
|
33755
|
+
const DEAD_CODE_PHASE_TIMEOUT_OVER_WORKER_MS = 3e4;
|
|
33756
|
+
const DEAD_CODE_OVERLAP_PARSE_SHARE = .4;
|
|
33644
33757
|
const RECOMMENDED_PNPM_MINIMUM_RELEASE_AGE_MINUTES = 10080;
|
|
33645
33758
|
const REACT_SERVER_DOM_PACKAGES = [
|
|
33646
33759
|
"react-server-dom-webpack",
|
|
@@ -33675,9 +33788,13 @@ const CONFIG_CACHE_TTL_MS = 300 * 1e3;
|
|
|
33675
33788
|
const SOCKET_FREE_PURL_API_BASE = "https://firewall-api.socket.dev/purl";
|
|
33676
33789
|
const SOCKET_PACKAGE_PAGE_BASE = "https://socket.dev/npm/package";
|
|
33677
33790
|
const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
|
|
33791
|
+
const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
|
|
33792
|
+
const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
|
|
33678
33793
|
const SUPPLY_CHAIN_PLUGIN = "socket";
|
|
33679
33794
|
const SUPPLY_CHAIN_RULE = "low-supply-chain-score";
|
|
33680
33795
|
const SUPPLY_CHAIN_CATEGORY = "Security";
|
|
33796
|
+
const SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS = 9e4;
|
|
33797
|
+
const SUPPLY_CHAIN_CACHE_SUBDIR = "supply-chain";
|
|
33681
33798
|
const SUPPLY_CHAIN_IGNORED_PACKAGES = new Set(["next"]);
|
|
33682
33799
|
const TSCONFIG_FILENAME = "tsconfig.json";
|
|
33683
33800
|
const isRelativeExtendsValue = (extendsValue) => extendsValue.startsWith("./") || extendsValue.startsWith("../") || Path.isAbsolute(extendsValue);
|
|
@@ -35036,6 +35153,11 @@ var OxlintBatchExceeded = class extends TaggedErrorClass()("OxlintBatchExceeded"
|
|
|
35036
35153
|
}
|
|
35037
35154
|
}
|
|
35038
35155
|
};
|
|
35156
|
+
var ScanDeadlineExceeded = class extends TaggedErrorClass()("ScanDeadlineExceeded", { detail: String$1 }) {
|
|
35157
|
+
get message() {
|
|
35158
|
+
return `Scan exceeded its overall time budget: ${this.detail}`;
|
|
35159
|
+
}
|
|
35160
|
+
};
|
|
35039
35161
|
var OxlintSpawnFailed = class extends TaggedErrorClass()("OxlintSpawnFailed", { cause: Unknown }) {
|
|
35040
35162
|
get message() {
|
|
35041
35163
|
return `Failed to run oxlint: ${pretty(fail$6(this.cause))}`;
|
|
@@ -35099,6 +35221,7 @@ var GitBaseBranchInvalid = class extends TaggedErrorClass()("GitBaseBranchInvali
|
|
|
35099
35221
|
const ReactDoctorErrorReason = Union([
|
|
35100
35222
|
OxlintUnavailable,
|
|
35101
35223
|
OxlintBatchExceeded,
|
|
35224
|
+
ScanDeadlineExceeded,
|
|
35102
35225
|
OxlintSpawnFailed,
|
|
35103
35226
|
OxlintOutputUnparseable,
|
|
35104
35227
|
ConfigParseFailed,
|
|
@@ -35171,15 +35294,105 @@ const layerOtlp = unwrap$3(gen(function* () {
|
|
|
35171
35294
|
}).pipe(provide$2(layer$8));
|
|
35172
35295
|
}).pipe(orDie));
|
|
35173
35296
|
/**
|
|
35174
|
-
*
|
|
35175
|
-
* `
|
|
35176
|
-
|
|
35297
|
+
* Read a positive-millisecond timeout from an env var, falling back to
|
|
35298
|
+
* `defaultMs` when the var is unset, non-finite, or not strictly positive.
|
|
35299
|
+
*/
|
|
35300
|
+
const readPositiveEnvMs = (envVarName, defaultMs) => {
|
|
35301
|
+
const rawValue = process.env[envVarName];
|
|
35302
|
+
if (rawValue === void 0) return defaultMs;
|
|
35303
|
+
const parsedValue = Number(rawValue);
|
|
35304
|
+
if (!Number.isFinite(parsedValue) || parsedValue <= 0) return defaultMs;
|
|
35305
|
+
return parsedValue;
|
|
35306
|
+
};
|
|
35307
|
+
const CGROUP_V2_MEMORY_MAX_PATH = "/sys/fs/cgroup/memory.max";
|
|
35308
|
+
const CGROUP_V1_MEMORY_LIMIT_PATH = "/sys/fs/cgroup/memory/memory.limit_in_bytes";
|
|
35309
|
+
const CGROUP_UNLIMITED_SENTINEL_BYTES = Number.MAX_SAFE_INTEGER;
|
|
35310
|
+
/**
|
|
35311
|
+
* Parses one raw cgroup memory-limit file value into a positive byte count, or
|
|
35312
|
+
* `undefined` when it represents "no limit" (the v2 `"max"` literal, an empty
|
|
35313
|
+
* read, a non-positive / non-finite value, or v1's near-2^63 unlimited
|
|
35314
|
+
* sentinel). Pure and exported so the classification is unit-testable without
|
|
35315
|
+
* touching the filesystem.
|
|
35316
|
+
*/
|
|
35317
|
+
const parseCgroupMemoryLimitBytes = (raw) => {
|
|
35318
|
+
if (raw === void 0) return void 0;
|
|
35319
|
+
const trimmed = raw.trim();
|
|
35320
|
+
if (trimmed === "" || trimmed === "max") return void 0;
|
|
35321
|
+
const parsed = Number(trimmed);
|
|
35322
|
+
if (!Number.isFinite(parsed) || parsed <= 0 || parsed >= CGROUP_UNLIMITED_SENTINEL_BYTES) return;
|
|
35323
|
+
return parsed;
|
|
35324
|
+
};
|
|
35325
|
+
const CGROUP_MEMORY_LIMIT_PATHS = [CGROUP_V2_MEMORY_MAX_PATH, CGROUP_V1_MEMORY_LIMIT_PATH];
|
|
35326
|
+
/**
|
|
35327
|
+
* Reads this process's cgroup memory limit in bytes from the first candidate
|
|
35328
|
+
* path that yields a real limit, or `undefined` when none does — no cgroup, no
|
|
35329
|
+
* limit, or the files are unreadable (e.g. macOS / Windows dev machines).
|
|
35330
|
+
* `os.totalmem()` reports the HOST total and ignores cgroup memory limits, so a
|
|
35331
|
+
* memory-constrained container over-reports total memory; `resolveAutoScan-
|
|
35332
|
+
* Concurrency` takes `min(totalmem, this)` to honor the limit.
|
|
35333
|
+
*
|
|
35334
|
+
* The cgroup v2 read is the mount-root `memory.max`, which IS the container's
|
|
35335
|
+
* limit under the standard cgroup-namespace setup CI runners use (the
|
|
35336
|
+
* container's own cgroup is the root of its namespaced view). A process in a
|
|
35337
|
+
* non-namespaced nested/delegated cgroup whose root reads `"max"` is not
|
|
35338
|
+
* detected here and falls back to the host total; the EAGAIN/ENOMEM serial
|
|
35339
|
+
* replay in `spawnLintBatches` remains the runtime backstop for that case.
|
|
35340
|
+
*
|
|
35341
|
+
* `candidatePaths` is injectable so tests exercise the v2-wins-over-v1
|
|
35342
|
+
* precedence, the skip-unreadable fallback, and the all-missing case without a
|
|
35343
|
+
* real `/sys/fs/cgroup`.
|
|
35344
|
+
*/
|
|
35345
|
+
const readCgroupMemoryLimitBytes = (candidatePaths = CGROUP_MEMORY_LIMIT_PATHS) => {
|
|
35346
|
+
for (const limitPath of candidatePaths) {
|
|
35347
|
+
let raw;
|
|
35348
|
+
try {
|
|
35349
|
+
raw = fs.readFileSync(limitPath, "utf8");
|
|
35350
|
+
} catch {
|
|
35351
|
+
continue;
|
|
35352
|
+
}
|
|
35353
|
+
const limitBytes = parseCgroupMemoryLimitBytes(raw);
|
|
35354
|
+
if (limitBytes !== void 0) return limitBytes;
|
|
35355
|
+
}
|
|
35356
|
+
};
|
|
35357
|
+
/**
|
|
35358
|
+
* Clamps a requested lint worker count to `[MIN_SCAN_CONCURRENCY,
|
|
35359
|
+
* HARD_MAX_SCAN_CONCURRENCY]` as a finite integer. This is the explicit-pin and
|
|
35360
|
+
* spawn-boundary clamp — the memory-and-core-budgeted auto count comes from
|
|
35361
|
+
* `resolveAutoScanConcurrency`. Out-of-range or non-finite requests degrade to
|
|
35177
35362
|
* `MIN_SCAN_CONCURRENCY` rather than oversubscribing or running zero workers.
|
|
35178
35363
|
*/
|
|
35179
35364
|
const resolveScanConcurrency = (requested) => {
|
|
35180
|
-
|
|
35181
|
-
|
|
35182
|
-
|
|
35365
|
+
if (!Number.isFinite(requested) || requested < 1) return 1;
|
|
35366
|
+
return Math.min(Math.floor(requested), 32);
|
|
35367
|
+
};
|
|
35368
|
+
const readSystemFacts = () => ({
|
|
35369
|
+
availableCores: os.availableParallelism(),
|
|
35370
|
+
totalMemoryBytes: os.totalmem(),
|
|
35371
|
+
cgroupMemoryLimitBytes: readCgroupMemoryLimitBytes()
|
|
35372
|
+
});
|
|
35373
|
+
/**
|
|
35374
|
+
* Auto lint-worker count: the smaller of the (cgroup-CPU-aware) core count and
|
|
35375
|
+
* the number of `PER_WORKER_MEM_BUDGET_BYTES` workers that fit in available
|
|
35376
|
+
* memory, then clamped to `[MIN, HARD_MAX]` by `resolveScanConcurrency`.
|
|
35377
|
+
*
|
|
35378
|
+
* `os.availableParallelism()` already respects cgroup CPU quotas, so the core
|
|
35379
|
+
* term needs no help. Available memory is `os.totalmem()` floored by the cgroup
|
|
35380
|
+
* memory limit — `os.freemem()` is deliberately NOT used: it excludes
|
|
35381
|
+
* reclaimable page cache and reads near-zero on macOS / cache-heavy Linux, which
|
|
35382
|
+
* would collapse the auto path to a single worker. `os.totalmem()` reports the
|
|
35383
|
+
* host total even inside a container, so the cgroup limit (read directly,
|
|
35384
|
+
* because Node doesn't fold it into `totalmem()`) is the real ceiling there.
|
|
35385
|
+
*
|
|
35386
|
+
* `facts` is injectable so tests exercise core-bound, memory-bound, cgroup-
|
|
35387
|
+
* limited, and ceiling cases without mocking `os` or the filesystem.
|
|
35388
|
+
*/
|
|
35389
|
+
const resolveAutoScanConcurrency = (facts = readSystemFacts()) => {
|
|
35390
|
+
const availableMemoryBytes = Math.min(facts.totalMemoryBytes, facts.cgroupMemoryLimitBytes ?? Number.POSITIVE_INFINITY);
|
|
35391
|
+
const memoryBoundedWorkers = Math.floor(availableMemoryBytes / PER_WORKER_MEM_BUDGET_BYTES);
|
|
35392
|
+
return resolveScanConcurrency(Math.min(facts.availableCores, memoryBoundedWorkers));
|
|
35393
|
+
};
|
|
35394
|
+
const resolveLintBatchOrdering = () => {
|
|
35395
|
+
return process.env["REACT_DOCTOR_LINT_BATCH_ORDERING"]?.trim().toLowerCase() === "cost" ? "cost" : "arrival";
|
|
35183
35396
|
};
|
|
35184
35397
|
/**
|
|
35185
35398
|
* Per-batch oxlint wall-clock budget. Reads from the env var on
|
|
@@ -35187,11 +35400,38 @@ const resolveScanConcurrency = (requested) => {
|
|
|
35187
35400
|
* microVMs without recompiling react-doctor. Tests override via
|
|
35188
35401
|
* `Layer.succeed(OxlintSpawnTimeoutMs, ...)`.
|
|
35189
35402
|
*/
|
|
35190
|
-
var OxlintSpawnTimeoutMs = class extends Reference("react-doctor/OxlintSpawnTimeoutMs", { defaultValue: () => {
|
|
35191
|
-
|
|
35192
|
-
|
|
35403
|
+
var OxlintSpawnTimeoutMs = class extends Reference("react-doctor/OxlintSpawnTimeoutMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_OXLINT_SPAWN_TIMEOUT_MS", OXLINT_SPAWN_TIMEOUT_MS) }) {};
|
|
35404
|
+
/**
|
|
35405
|
+
* Effect-side cap on the lint phase. The env var lets CI / eval runners
|
|
35406
|
+
* raise the phase budget for slow large repos without recompiling.
|
|
35407
|
+
* Tests override via `Layer.succeed(LintPhaseTimeoutMs, ...)`.
|
|
35408
|
+
*/
|
|
35409
|
+
var LintPhaseTimeoutMs = class extends Reference("react-doctor/LintPhaseTimeoutMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_LINT_PHASE_TIMEOUT_MS", LINT_PHASE_TIMEOUT_MS) }) {};
|
|
35410
|
+
/**
|
|
35411
|
+
* Effect-side cap on the dead-code phase, sitting above the in-worker
|
|
35412
|
+
* timeout as a runtime-independent backstop. The env var raises it for
|
|
35413
|
+
* type-heavy projects; tests override via
|
|
35414
|
+
* `Layer.succeed(DeadCodePhaseTimeoutMs, ...)`.
|
|
35415
|
+
*/
|
|
35416
|
+
var DeadCodePhaseTimeoutMs = class extends Reference("react-doctor/DeadCodePhaseTimeoutMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_DEAD_CODE_PHASE_TIMEOUT_MS", DEAD_CODE_PHASE_TIMEOUT_MS) }) {};
|
|
35417
|
+
/**
|
|
35418
|
+
* Overall scan deadline backstop, bounding everything the per-phase
|
|
35419
|
+
* timeouts don't (wedged git / IO). The env var raises it for very
|
|
35420
|
+
* large repos; tests override via `Layer.succeed(ScanDeadlineMs, ...)`.
|
|
35421
|
+
*/
|
|
35422
|
+
var ScanDeadlineMs = class extends Reference("react-doctor/ScanDeadlineMs", { defaultValue: () => readPositiveEnvMs("REACT_DOCTOR_SCAN_DEADLINE_MS", SCAN_TOTAL_DEADLINE_MS) }) {};
|
|
35423
|
+
/**
|
|
35424
|
+
* Wall-clock budget for the supply-chain check when it runs on a background
|
|
35425
|
+
* fiber overlapping the lint pass. Reads from the env var on startup so the
|
|
35426
|
+
* eval harness can raise the budget under sandbox microVMs (slower network)
|
|
35427
|
+
* without recompiling react-doctor. Tests override via
|
|
35428
|
+
* `Layer.succeed(SupplyChainOverlapTimeoutMs, ...)`.
|
|
35429
|
+
*/
|
|
35430
|
+
var SupplyChainOverlapTimeoutMs = class extends Reference("react-doctor/SupplyChainOverlapTimeoutMs", { defaultValue: () => {
|
|
35431
|
+
const raw = process.env["REACT_DOCTOR_SUPPLY_CHAIN_TIMEOUT_MS"];
|
|
35432
|
+
if (raw === void 0) return SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS;
|
|
35193
35433
|
const parsed = Number(raw);
|
|
35194
|
-
if (!Number.isFinite(parsed) || parsed <= 0) return
|
|
35434
|
+
if (!Number.isFinite(parsed) || parsed <= 0) return SUPPLY_CHAIN_OVERLAP_TIMEOUT_MS;
|
|
35195
35435
|
return parsed;
|
|
35196
35436
|
} }) {};
|
|
35197
35437
|
/**
|
|
@@ -35202,31 +35442,93 @@ var OxlintSpawnTimeoutMs = class extends Reference("react-doctor/OxlintSpawnTime
|
|
|
35202
35442
|
*/
|
|
35203
35443
|
var OxlintOutputMaxBytes = class extends Reference("react-doctor/OxlintOutputMaxBytes", { defaultValue: () => OXLINT_OUTPUT_MAX_BYTES }) {};
|
|
35204
35444
|
/**
|
|
35205
|
-
* Number of oxlint subprocesses the lint pass runs in parallel. Defaults
|
|
35206
|
-
*
|
|
35207
|
-
* the box
|
|
35208
|
-
*
|
|
35209
|
-
*
|
|
35210
|
-
*
|
|
35211
|
-
*
|
|
35212
|
-
*
|
|
35213
|
-
*
|
|
35214
|
-
*
|
|
35445
|
+
* Number of oxlint subprocesses the lint pass runs in parallel. Defaults to a
|
|
35446
|
+
* memory-and-core-budgeted auto count (`resolveAutoScanConcurrency`) so large
|
|
35447
|
+
* repos scan fast out of the box without OOMing the native binding on a
|
|
35448
|
+
* high-core / low-memory box; `spawnLintBatches` transparently falls back to a
|
|
35449
|
+
* single worker if a parallel run still exhausts system resources. The CLI's
|
|
35450
|
+
* `--no-parallel` flag forces serial via `Layer.succeed`; the
|
|
35451
|
+
* `REACT_DOCTOR_PARALLEL` env var seeds the default for programmatic / CI
|
|
35452
|
+
* callers that never touch the flag — parallelism is opt-OUT, so only the
|
|
35453
|
+
* explicit serial values pin one worker:
|
|
35454
|
+
*
|
|
35455
|
+
* - unset / `auto` / `true` / `on` → memory-and-core-budgeted auto count
|
|
35215
35456
|
* - `0` / `false` / `off` → `1` (serial)
|
|
35216
35457
|
* - a positive integer → that many workers (clamped)
|
|
35217
|
-
* - any other value →
|
|
35458
|
+
* - any other value → memory-and-core-budgeted auto count
|
|
35218
35459
|
*
|
|
35219
35460
|
* The resolved value is always within
|
|
35220
|
-
* `[MIN_SCAN_CONCURRENCY,
|
|
35461
|
+
* `[MIN_SCAN_CONCURRENCY, HARD_MAX_SCAN_CONCURRENCY]`.
|
|
35221
35462
|
*/
|
|
35222
35463
|
var OxlintConcurrency = class extends Reference("react-doctor/OxlintConcurrency", { defaultValue: () => {
|
|
35223
35464
|
const raw = process.env["REACT_DOCTOR_PARALLEL"];
|
|
35224
|
-
if (raw === void 0) return
|
|
35465
|
+
if (raw === void 0) return resolveAutoScanConcurrency();
|
|
35225
35466
|
const normalized = raw.trim().toLowerCase();
|
|
35226
35467
|
if (normalized === "0" || normalized === "false" || normalized === "off") return 1;
|
|
35227
35468
|
const parsed = Number.parseInt(normalized, 10);
|
|
35228
35469
|
if (Number.isInteger(parsed) && parsed > 0) return resolveScanConcurrency(parsed);
|
|
35229
|
-
return
|
|
35470
|
+
return resolveAutoScanConcurrency();
|
|
35471
|
+
} }) {};
|
|
35472
|
+
/**
|
|
35473
|
+
* Three-state control for overlapping the dead-code pass with the lint pass —
|
|
35474
|
+
* forking dead-code as a child fiber that runs DURING lint instead of strictly
|
|
35475
|
+
* after it.
|
|
35476
|
+
*
|
|
35477
|
+
* - `"auto"` (default) / `"off"` → strictly SEQUENTIAL: dead-code runs after
|
|
35478
|
+
* lint with the full core budget. Both deslop's parse pool and the oxlint
|
|
35479
|
+
* pool are CPU-bound and each size themselves to all cores, so overlapping
|
|
35480
|
+
* them only oversubscribes (~2x the cores) and starves the parse pass past
|
|
35481
|
+
* its timeout — for no wall-clock win, since there are no spare cores to
|
|
35482
|
+
* absorb the second pass. Sequential is both faster per-phase and safe.
|
|
35483
|
+
* - `"on"` → force the overlap anyway. The orchestrator then SPLITS the core
|
|
35484
|
+
* budget (`DEAD_CODE_OVERLAP_PARSE_SHARE`): deslop's parse pool is capped
|
|
35485
|
+
* and lint shrinks to the remainder, so the two sum to the cores instead of
|
|
35486
|
+
* doubling them, and the dead-code timeout scales up for the reduced share.
|
|
35487
|
+
*
|
|
35488
|
+
* Seeded from `REACT_DOCTOR_DEAD_CODE_OVERLAP` so operators get a redeploy-free
|
|
35489
|
+
* switch; tests pin it via `Layer.succeed(DeadCodeOverlap, ...)`.
|
|
35490
|
+
*/
|
|
35491
|
+
var DeadCodeOverlap = class extends Reference("react-doctor/DeadCodeOverlap", { defaultValue: () => {
|
|
35492
|
+
const raw = process.env["REACT_DOCTOR_DEAD_CODE_OVERLAP"]?.trim().toLowerCase();
|
|
35493
|
+
if (raw === "on" || raw === "true" || raw === "1") return "on";
|
|
35494
|
+
if (raw === "off" || raw === "false" || raw === "0") return "off";
|
|
35495
|
+
return "auto";
|
|
35496
|
+
} }) {};
|
|
35497
|
+
/**
|
|
35498
|
+
* How the full-scan lint pass orders its file batches. `"arrival"` (the
|
|
35499
|
+
* default) keeps `git ls-files` discovery order. `"cost"` opts into LPT (feed
|
|
35500
|
+
* the largest files first); set `REACT_DOCTOR_LINT_BATCH_ORDERING=cost`. NOTE:
|
|
35501
|
+
* `cost` is OFF by default because the current sort-desc-then-chunk-100 packs
|
|
35502
|
+
* the heaviest files into one wave-1 batch — on size-skewed repos that mega-
|
|
35503
|
+
* batch is a straggler (and can trip the per-batch timeout + split), measurably
|
|
35504
|
+
* regressing the common full-scan case. LPT needs the heavy files SPREAD across
|
|
35505
|
+
* batches before `cost` earns the default. Tests override via
|
|
35506
|
+
* `Layer.succeed(LintBatchOrdering, ...)`. Diff / staged scans never reach this
|
|
35507
|
+
* — they pass user-scoped `includePaths` that skip discovery and stay in
|
|
35508
|
+
* arrival order; only the full-scan branch reads it.
|
|
35509
|
+
*/
|
|
35510
|
+
var LintBatchOrdering = class extends Reference("react-doctor/LintBatchOrdering", { defaultValue: resolveLintBatchOrdering }) {};
|
|
35511
|
+
const CACHE_DISABLED_VALUES = new Set(["1", "true"]);
|
|
35512
|
+
/**
|
|
35513
|
+
* Whether the per-file lint cache (`runners/oxlint/file-lint-cache.ts`) is
|
|
35514
|
+
* active. Defaults ON — repeat scans re-lint only the files whose content
|
|
35515
|
+
* changed, and correctness is guaranteed byte-identical to a cold scan by the
|
|
35516
|
+
* always-fresh cross-file sidecar. Opt-OUT, two knobs (matching the whole-repo
|
|
35517
|
+
* scan cache's `REACT_DOCTOR_NO_CACHE`):
|
|
35518
|
+
*
|
|
35519
|
+
* - `REACT_DOCTOR_NO_CACHE` — the global off-switch; disables BOTH the
|
|
35520
|
+
* whole-repo scan cache and this per-file cache.
|
|
35521
|
+
* - `REACT_DOCTOR_NO_FILE_CACHE` — granular: bust only the per-file cache
|
|
35522
|
+
* while keeping the whole-repo short-circuit.
|
|
35523
|
+
*
|
|
35524
|
+
* Tests override via `Layer.succeed(PerFileLintCacheEnabled, false)`.
|
|
35525
|
+
*/
|
|
35526
|
+
var PerFileLintCacheEnabled = class extends Reference("react-doctor/PerFileLintCacheEnabled", { defaultValue: () => {
|
|
35527
|
+
const noCache = process.env["REACT_DOCTOR_NO_CACHE"]?.toLowerCase() ?? "";
|
|
35528
|
+
const noFileCache = process.env["REACT_DOCTOR_NO_FILE_CACHE"]?.toLowerCase() ?? "";
|
|
35529
|
+
if (CACHE_DISABLED_VALUES.has(noCache)) return false;
|
|
35530
|
+
if (CACHE_DISABLED_VALUES.has(noFileCache)) return false;
|
|
35531
|
+
return true;
|
|
35230
35532
|
} }) {};
|
|
35231
35533
|
const DIAGNOSTIC_SURFACES = [
|
|
35232
35534
|
"cli",
|
|
@@ -35632,6 +35934,8 @@ const assignFixGroups = (diagnostics) => {
|
|
|
35632
35934
|
};
|
|
35633
35935
|
});
|
|
35634
35936
|
};
|
|
35937
|
+
const compareStrings = (left, right) => left < right ? -1 : left > right ? 1 : 0;
|
|
35938
|
+
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
35939
|
const getDirectDependencyNames = (packageJson) => new Set([...Object.keys(packageJson.dependencies ?? {}), ...Object.keys(packageJson.devDependencies ?? {})]);
|
|
35636
35940
|
const buildExpoCheckContext = (rootDirectory, expoVersion) => {
|
|
35637
35941
|
const packageJson = readPackageJson(Path.join(rootDirectory, "package.json"));
|
|
@@ -36931,6 +37235,22 @@ process.stdin.on("end", () => {
|
|
|
36931
37235
|
...(workerInput.ignorePatterns.length > 0
|
|
36932
37236
|
? { ignorePatterns: workerInput.ignorePatterns }
|
|
36933
37237
|
: {}),
|
|
37238
|
+
// We consume only deslop's GRAPH-based findings (unusedFiles, unusedExports,
|
|
37239
|
+
// unusedDependencies, circularDependencies). Everything else deslop can compute
|
|
37240
|
+
// is pure wasted work for us, and it's the bulk of the runtime:
|
|
37241
|
+
// - semantic: a full TS Program for unusedTypes/enum/class-members/
|
|
37242
|
+
// misclassifiedDependencies (~37-45% of the phase).
|
|
37243
|
+
// - reportCodeQuality: the duplicate-block, complexity, feature-flag,
|
|
37244
|
+
// TypeScript-smell, private-type-leak and re-export-cycle detectors. These
|
|
37245
|
+
// are the single most expensive pass — duplicate-block detection alone was
|
|
37246
|
+
// ~83s of a ~130s Sentry scan — so skipping them is an ~8.5x dead-code
|
|
37247
|
+
// speedup on a large repo.
|
|
37248
|
+
// Both are provably safe: the consumed graph findings are computed by their own
|
|
37249
|
+
// detectors, independent of these passes (confirmed byte-identical on
|
|
37250
|
+
// excalidraw + mui-material + sentry). tsConfigPath stays — the module resolver
|
|
37251
|
+
// needs it for path-alias resolution in the import graph.
|
|
37252
|
+
semantic: { enabled: false },
|
|
37253
|
+
reportCodeQuality: false,
|
|
36934
37254
|
};
|
|
36935
37255
|
const result = await analyze(defineConfig(config));
|
|
36936
37256
|
emit({ ok: true, result: normalizeResult(result) });
|
|
@@ -37060,7 +37380,11 @@ const createDeadCodeWorker = (input) => {
|
|
|
37060
37380
|
"pipe",
|
|
37061
37381
|
"pipe"
|
|
37062
37382
|
],
|
|
37063
|
-
windowsHide: true
|
|
37383
|
+
windowsHide: true,
|
|
37384
|
+
env: input.parseConcurrency === void 0 ? process.env : {
|
|
37385
|
+
...process.env,
|
|
37386
|
+
DESLOP_PARSE_CONCURRENCY: String(input.parseConcurrency)
|
|
37387
|
+
}
|
|
37064
37388
|
});
|
|
37065
37389
|
const stdoutChunks = [];
|
|
37066
37390
|
const stderrChunks = [];
|
|
@@ -37105,28 +37429,25 @@ const createDeadCodeWorker = (input) => {
|
|
|
37105
37429
|
}
|
|
37106
37430
|
};
|
|
37107
37431
|
};
|
|
37108
|
-
const runDeadCodeWorkerWithTimeout = (handle, timeoutMs) => new Promise((resolve, reject) => {
|
|
37432
|
+
const runDeadCodeWorkerWithTimeout = (handle, timeoutMs, abortSignal) => new Promise((resolve, reject) => {
|
|
37109
37433
|
let didSettle = false;
|
|
37110
|
-
const
|
|
37111
|
-
if (didSettle) return;
|
|
37112
|
-
didSettle = true;
|
|
37113
|
-
handle.terminate?.();
|
|
37114
|
-
reject(/* @__PURE__ */ new Error(`Dead-code worker timed out after ${timeoutMs / MILLISECONDS_PER_SECOND}s.`));
|
|
37115
|
-
}, timeoutMs);
|
|
37116
|
-
timeoutHandle.unref?.();
|
|
37117
|
-
handle.result.then((value) => {
|
|
37118
|
-
if (didSettle) return;
|
|
37119
|
-
didSettle = true;
|
|
37120
|
-
clearTimeout(timeoutHandle);
|
|
37121
|
-
handle.terminate?.();
|
|
37122
|
-
resolve(value);
|
|
37123
|
-
}, (error) => {
|
|
37434
|
+
const settle = (finish) => {
|
|
37124
37435
|
if (didSettle) return;
|
|
37125
37436
|
didSettle = true;
|
|
37126
37437
|
clearTimeout(timeoutHandle);
|
|
37438
|
+
abortSignal?.removeEventListener("abort", onAbort);
|
|
37127
37439
|
handle.terminate?.();
|
|
37128
|
-
|
|
37129
|
-
}
|
|
37440
|
+
finish();
|
|
37441
|
+
};
|
|
37442
|
+
const onAbort = () => settle(() => reject(/* @__PURE__ */ new Error("Dead-code worker aborted.")));
|
|
37443
|
+
const timeoutHandle = setTimeout(() => settle(() => reject(/* @__PURE__ */ new Error(`Dead-code worker timed out after ${timeoutMs / MILLISECONDS_PER_SECOND}s.`))), timeoutMs);
|
|
37444
|
+
timeoutHandle.unref?.();
|
|
37445
|
+
if (abortSignal?.aborted) {
|
|
37446
|
+
onAbort();
|
|
37447
|
+
return;
|
|
37448
|
+
}
|
|
37449
|
+
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
37450
|
+
handle.result.then((value) => settle(() => resolve(value)), (error) => settle(() => reject(error)));
|
|
37130
37451
|
});
|
|
37131
37452
|
const checkDeadCode = async (options) => {
|
|
37132
37453
|
const rootDirectory = toCanonicalPath(options.rootDirectory);
|
|
@@ -37138,8 +37459,9 @@ const checkDeadCode = async (options) => {
|
|
|
37138
37459
|
entryPatterns,
|
|
37139
37460
|
tsConfigPath: resolveTsConfigPath(rootDirectory),
|
|
37140
37461
|
ignorePatterns,
|
|
37141
|
-
deslopJsModuleSpecifier: options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js")
|
|
37142
|
-
|
|
37462
|
+
deslopJsModuleSpecifier: options.deslopJsModuleSpecifier ?? import.meta.resolve("deslop-js"),
|
|
37463
|
+
parseConcurrency: options.parseConcurrency
|
|
37464
|
+
}), options.workerTimeoutMs ?? 12e4, options.abortSignal));
|
|
37143
37465
|
const toRelative = (filePath) => toRelativeFilePath(rootDirectory, filePath);
|
|
37144
37466
|
const diagnostics = [];
|
|
37145
37467
|
for (const unusedFile of result.unusedFiles) diagnostics.push({
|
|
@@ -37237,7 +37559,37 @@ const isDiagnosticOnSurface = (diagnostic, surface, config) => {
|
|
|
37237
37559
|
return true;
|
|
37238
37560
|
};
|
|
37239
37561
|
const filterDiagnosticsForSurface = (diagnostics, surface, config) => diagnostics.filter((diagnostic) => isDiagnosticOnSurface(diagnostic, surface, config));
|
|
37240
|
-
|
|
37562
|
+
/**
|
|
37563
|
+
* Budget for the dead-code phase, scaled to the work. deslop's graph build is
|
|
37564
|
+
* CPU-bound and roughly linear in file count, so a fixed 120s cap is too tight
|
|
37565
|
+
* for a large repo (where the pass legitimately runs that long) and is then
|
|
37566
|
+
* tipped over by any concurrent load — silently dropping every dead-code
|
|
37567
|
+
* finding. Scaling the budget with file count (and inversely with the core
|
|
37568
|
+
* share when overlapped) lets the pass complete, while the ceiling still
|
|
37569
|
+
* reclaims a genuinely wedged worker. Returns the in-worker SIGKILL deadline
|
|
37570
|
+
* and the Effect-side phase backstop that sits a margin above it.
|
|
37571
|
+
*/
|
|
37572
|
+
const resolveDeadCodeTimeout = (input) => {
|
|
37573
|
+
const coreShareFactor = Math.max(1, input.fullConcurrency / Math.max(1, input.deadCodeConcurrency));
|
|
37574
|
+
const workerTimeoutMs = Math.min(DEAD_CODE_TIMEOUT_CEILING_MS, Math.max(DEAD_CODE_WORKER_TIMEOUT_MS, Math.ceil(input.sourceFileCount * 30 * coreShareFactor)));
|
|
37575
|
+
return {
|
|
37576
|
+
workerTimeoutMs,
|
|
37577
|
+
phaseTimeoutMs: workerTimeoutMs + DEAD_CODE_PHASE_TIMEOUT_OVER_WORKER_MS
|
|
37578
|
+
};
|
|
37579
|
+
};
|
|
37580
|
+
const collectSizedSourceFiles = (rootDirectory, relativePaths) => {
|
|
37581
|
+
const entries = [];
|
|
37582
|
+
for (const relativePath of relativePaths) {
|
|
37583
|
+
const absolutePath = Path.resolve(rootDirectory, relativePath);
|
|
37584
|
+
const sizeBytes = statSourceFileSize(absolutePath);
|
|
37585
|
+
if (isLargeMinifiedFile(absolutePath, sizeBytes)) continue;
|
|
37586
|
+
entries.push({
|
|
37587
|
+
path: relativePath,
|
|
37588
|
+
sizeBytes: sizeBytes ?? 0
|
|
37589
|
+
});
|
|
37590
|
+
}
|
|
37591
|
+
return entries;
|
|
37592
|
+
};
|
|
37241
37593
|
const listSourceFilesViaGit = (rootDirectory) => {
|
|
37242
37594
|
const result = spawnSync("git", [
|
|
37243
37595
|
"ls-files",
|
|
@@ -37270,7 +37622,8 @@ const listSourceFilesViaFilesystem = (rootDirectory) => {
|
|
|
37270
37622
|
}
|
|
37271
37623
|
return filePaths;
|
|
37272
37624
|
};
|
|
37273
|
-
const
|
|
37625
|
+
const listSourceFilesWithSize = (rootDirectory) => collectSizedSourceFiles(rootDirectory, listSourceFilesViaGit(rootDirectory) ?? listSourceFilesViaFilesystem(rootDirectory));
|
|
37626
|
+
const listSourceFiles = (rootDirectory) => listSourceFilesWithSize(rootDirectory).map((entry) => entry.path);
|
|
37274
37627
|
const resolveLintIncludePaths = (rootDirectory, userConfig, project) => {
|
|
37275
37628
|
if (!Array.isArray(userConfig?.ignore?.files) || userConfig.ignore.files.length === 0) return;
|
|
37276
37629
|
const ignoredPatterns = compileIgnoredFilePatterns(userConfig);
|
|
@@ -37313,9 +37666,12 @@ var Config = class Config extends Service()("react-doctor/Config") {
|
|
|
37313
37666
|
var DeadCode = class DeadCode extends Service()("react-doctor/DeadCode") {
|
|
37314
37667
|
static layerNode = succeed$3(DeadCode, DeadCode.of({ run: (input) => unwrap(fn("DeadCode.run")(function* () {
|
|
37315
37668
|
return yield* tryPromise({
|
|
37316
|
-
try: () => checkDeadCode({
|
|
37669
|
+
try: (signal) => checkDeadCode({
|
|
37317
37670
|
rootDirectory: input.rootDirectory,
|
|
37318
|
-
userConfig: input.userConfig
|
|
37671
|
+
userConfig: input.userConfig,
|
|
37672
|
+
parseConcurrency: input.parseConcurrency,
|
|
37673
|
+
workerTimeoutMs: input.workerTimeoutMs,
|
|
37674
|
+
abortSignal: signal
|
|
37319
37675
|
}),
|
|
37320
37676
|
catch: (cause) => new ReactDoctorError({ reason: new DeadCodeAnalysisFailed({ cause }) })
|
|
37321
37677
|
}).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)));
|
|
@@ -38028,6 +38384,14 @@ const neutralizeDisableDirectives = async (rootDirectory, includePaths) => {
|
|
|
38028
38384
|
process.removeListener("exit", onExit);
|
|
38029
38385
|
};
|
|
38030
38386
|
};
|
|
38387
|
+
const ROOT_DIRECTORY_PLACEHOLDER = "<root>";
|
|
38388
|
+
const normalizeConfigForHash = (config) => {
|
|
38389
|
+
const clone = JSON.parse(JSON.stringify(config));
|
|
38390
|
+
if (clone?.settings?.["react-doctor"]) clone.settings["react-doctor"].rootDirectory = ROOT_DIRECTORY_PLACEHOLDER;
|
|
38391
|
+
if (Array.isArray(clone?.jsPlugins)) clone.jsPlugins = clone.jsPlugins.map((_, index) => `<plugin:${index}>`);
|
|
38392
|
+
return clone;
|
|
38393
|
+
};
|
|
38394
|
+
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");
|
|
38031
38395
|
/**
|
|
38032
38396
|
* Loads a plugin module via the local require resolver and extracts
|
|
38033
38397
|
* `(name, ruleNames)` from either `module.exports.meta + rules` or
|
|
@@ -38054,16 +38418,16 @@ const readPluginShape = (pluginSpecifier, loadModule) => {
|
|
|
38054
38418
|
ruleNames: new Set(Object.keys(rules))
|
|
38055
38419
|
};
|
|
38056
38420
|
};
|
|
38057
|
-
const bundledRequire = createRequire(import.meta.url);
|
|
38421
|
+
const bundledRequire$1 = createRequire(import.meta.url);
|
|
38058
38422
|
const resolveReactHooksJsPlugin = (hasReactCompiler, customRulesOnly) => {
|
|
38059
38423
|
if (!hasReactCompiler || customRulesOnly) return null;
|
|
38060
38424
|
let pluginSpecifier;
|
|
38061
38425
|
try {
|
|
38062
|
-
pluginSpecifier = bundledRequire.resolve("eslint-plugin-react-hooks");
|
|
38426
|
+
pluginSpecifier = bundledRequire$1.resolve("eslint-plugin-react-hooks");
|
|
38063
38427
|
} catch {
|
|
38064
38428
|
return null;
|
|
38065
38429
|
}
|
|
38066
|
-
const { ruleNames } = readPluginShape(pluginSpecifier, (spec) => bundledRequire(spec));
|
|
38430
|
+
const { ruleNames } = readPluginShape(pluginSpecifier, (spec) => bundledRequire$1(spec));
|
|
38067
38431
|
return {
|
|
38068
38432
|
entry: {
|
|
38069
38433
|
name: "react-hooks-js",
|
|
@@ -38182,8 +38546,8 @@ const buildUserPluginRules = (userPlugin, severityControls) => {
|
|
|
38182
38546
|
}
|
|
38183
38547
|
return enabled;
|
|
38184
38548
|
};
|
|
38185
|
-
const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false }) => {
|
|
38186
|
-
const reactHooksJsPlugin = disableReactHooksJsPlugin ? null : resolveReactHooksJsPlugin(project.hasReactCompiler, customRulesOnly);
|
|
38549
|
+
const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, extendsPaths = [], ignoredTags = /* @__PURE__ */ new Set(), serverAuthFunctionNames, severityControls, userPlugins = [], disableReactHooksJsPlugin = false, ruleSelection }) => {
|
|
38550
|
+
const reactHooksJsPlugin = disableReactHooksJsPlugin || ruleSelection === "sidecar" ? null : resolveReactHooksJsPlugin(project.hasReactCompiler, customRulesOnly);
|
|
38187
38551
|
const reactCompilerRules = reactHooksJsPlugin ? applyRuleSeverityControls(filterRulesToAvailable(REACT_COMPILER_RULES, "react-hooks-js", reactHooksJsPlugin.availableRuleNames), severityControls) : {};
|
|
38188
38552
|
const jsPlugins = [];
|
|
38189
38553
|
if (reactHooksJsPlugin) jsPlugins.push(reactHooksJsPlugin.entry);
|
|
@@ -38192,6 +38556,8 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
38192
38556
|
for (const registryEntry of REACT_DOCTOR_RULES) {
|
|
38193
38557
|
const rule = reactDoctorPlugin.rules[registryEntry.id];
|
|
38194
38558
|
if (!rule) continue;
|
|
38559
|
+
if (ruleSelection === "cacheable" && CROSS_FILE_RULE_IDS.has(registryEntry.id)) continue;
|
|
38560
|
+
if (ruleSelection === "sidecar" && !CROSS_FILE_RULE_IDS.has(registryEntry.id)) continue;
|
|
38195
38561
|
if (rule.scan !== void 0) continue;
|
|
38196
38562
|
if (customRulesOnly && registryEntry.originallyExternal) continue;
|
|
38197
38563
|
if (rule.framework !== "global" && !rule.requires) continue;
|
|
@@ -38206,7 +38572,7 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
38206
38572
|
enabledReactDoctorRules[registryEntry.key] = severity;
|
|
38207
38573
|
}
|
|
38208
38574
|
const userPluginRules = {};
|
|
38209
|
-
for (const userPlugin of userPlugins) {
|
|
38575
|
+
if (ruleSelection !== "sidecar") for (const userPlugin of userPlugins) {
|
|
38210
38576
|
Object.assign(userPluginRules, buildUserPluginRules(userPlugin, severityControls));
|
|
38211
38577
|
jsPlugins.push(userPlugin.entry);
|
|
38212
38578
|
}
|
|
@@ -38236,6 +38602,100 @@ const createOxlintConfig = ({ pluginPath, project, customRulesOnly = false, exte
|
|
|
38236
38602
|
}
|
|
38237
38603
|
};
|
|
38238
38604
|
};
|
|
38605
|
+
const atomicWriteJson = (filePath, value) => {
|
|
38606
|
+
try {
|
|
38607
|
+
NFS.mkdirSync(Path.dirname(filePath), { recursive: true });
|
|
38608
|
+
const temporaryPath = `${filePath}.${process.pid}.tmp`;
|
|
38609
|
+
NFS.writeFileSync(temporaryPath, JSON.stringify(value));
|
|
38610
|
+
NFS.renameSync(temporaryPath, filePath);
|
|
38611
|
+
} catch {
|
|
38612
|
+
return;
|
|
38613
|
+
}
|
|
38614
|
+
};
|
|
38615
|
+
const failOpenReadJson = (filePath, fallback) => {
|
|
38616
|
+
try {
|
|
38617
|
+
return JSON.parse(NFS.readFileSync(filePath, "utf8"));
|
|
38618
|
+
} catch {
|
|
38619
|
+
return fallback;
|
|
38620
|
+
}
|
|
38621
|
+
};
|
|
38622
|
+
const validateDiagnostic = decodeUnknownSync(Diagnostic);
|
|
38623
|
+
const decodeFileDiagnostics = (raw) => {
|
|
38624
|
+
if (!Array.isArray(raw)) return null;
|
|
38625
|
+
try {
|
|
38626
|
+
for (const entry of raw) validateDiagnostic(entry);
|
|
38627
|
+
return raw;
|
|
38628
|
+
} catch {
|
|
38629
|
+
return null;
|
|
38630
|
+
}
|
|
38631
|
+
};
|
|
38632
|
+
const emptyCache = () => ({
|
|
38633
|
+
version: 1,
|
|
38634
|
+
rulesets: {}
|
|
38635
|
+
});
|
|
38636
|
+
const loadRulesetEntries = (cacheFilePath, rulesetHash) => {
|
|
38637
|
+
const entries = /* @__PURE__ */ new Map();
|
|
38638
|
+
const persisted = failOpenReadJson(cacheFilePath, emptyCache());
|
|
38639
|
+
if (persisted.version !== 1 || !isRecord(persisted.rulesets)) return entries;
|
|
38640
|
+
const bucket = persisted.rulesets[rulesetHash];
|
|
38641
|
+
if (!isRecord(bucket) || !isRecord(bucket.files)) return entries;
|
|
38642
|
+
for (const [fileKey, rawDiagnostics] of Object.entries(bucket.files)) {
|
|
38643
|
+
const decoded = decodeFileDiagnostics(rawDiagnostics);
|
|
38644
|
+
if (decoded !== null) entries.set(fileKey, decoded);
|
|
38645
|
+
}
|
|
38646
|
+
return entries;
|
|
38647
|
+
};
|
|
38648
|
+
const createFileLintCache = (cacheDirectory, rulesetHash) => {
|
|
38649
|
+
const cacheFilePath = Path.join(cacheDirectory, FILE_LINT_CACHE_FILENAME);
|
|
38650
|
+
const entries = loadRulesetEntries(cacheFilePath, rulesetHash);
|
|
38651
|
+
return {
|
|
38652
|
+
lookup: (fileKey) => entries.get(fileKey) ?? null,
|
|
38653
|
+
store: (fileKey, diagnostics) => {
|
|
38654
|
+
entries.delete(fileKey);
|
|
38655
|
+
entries.set(fileKey, diagnostics);
|
|
38656
|
+
},
|
|
38657
|
+
persist: () => {
|
|
38658
|
+
const onDisk = failOpenReadJson(cacheFilePath, emptyCache());
|
|
38659
|
+
const rulesets = onDisk.version === 1 && isRecord(onDisk.rulesets) ? { ...onDisk.rulesets } : {};
|
|
38660
|
+
const existingBucket = rulesets[rulesetHash];
|
|
38661
|
+
const existingFiles = isRecord(existingBucket) && isRecord(existingBucket.files) ? existingBucket.files : {};
|
|
38662
|
+
const ourFiles = {};
|
|
38663
|
+
for (const [fileKey, diagnostics] of entries) ourFiles[fileKey] = diagnostics;
|
|
38664
|
+
const cappedEntries = Object.entries({
|
|
38665
|
+
...existingFiles,
|
|
38666
|
+
...ourFiles
|
|
38667
|
+
}).slice(-FILE_LINT_CACHE_MAX_FILE_COUNT);
|
|
38668
|
+
rulesets[rulesetHash] = {
|
|
38669
|
+
updatedAtMs: Date.now(),
|
|
38670
|
+
files: Object.fromEntries(cappedEntries)
|
|
38671
|
+
};
|
|
38672
|
+
const keptHashes = Object.entries(rulesets).sort(([, first], [, second]) => second.updatedAtMs - first.updatedAtMs).slice(0, 8).map(([hash]) => hash);
|
|
38673
|
+
const prunedRulesets = {};
|
|
38674
|
+
for (const hash of keptHashes) prunedRulesets[hash] = rulesets[hash];
|
|
38675
|
+
atomicWriteJson(cacheFilePath, {
|
|
38676
|
+
version: 1,
|
|
38677
|
+
rulesets: prunedRulesets
|
|
38678
|
+
});
|
|
38679
|
+
}
|
|
38680
|
+
};
|
|
38681
|
+
};
|
|
38682
|
+
const bundledRequire = createRequire(import.meta.url);
|
|
38683
|
+
const TOOLCHAIN_PACKAGE_SPECIFIERS = [
|
|
38684
|
+
"oxlint/package.json",
|
|
38685
|
+
"oxlint-plugin-react-doctor/package.json",
|
|
38686
|
+
"eslint-plugin-react-hooks/package.json"
|
|
38687
|
+
];
|
|
38688
|
+
const resolveOxlintToolchainVersions = () => {
|
|
38689
|
+
const versions = [`node=${process.version}`];
|
|
38690
|
+
for (const specifier of TOOLCHAIN_PACKAGE_SPECIFIERS) try {
|
|
38691
|
+
const packageJson = bundledRequire(specifier);
|
|
38692
|
+
const version = typeof packageJson.version === "string" ? packageJson.version : "unknown";
|
|
38693
|
+
versions.push(`${specifier}=${version}`);
|
|
38694
|
+
} catch {
|
|
38695
|
+
versions.push(`${specifier}=missing`);
|
|
38696
|
+
}
|
|
38697
|
+
return versions;
|
|
38698
|
+
};
|
|
38239
38699
|
const esmRequire = createRequire(import.meta.url);
|
|
38240
38700
|
const resolveOxlintBinary = () => {
|
|
38241
38701
|
const oxlintMainPath = esmRequire.resolve("oxlint");
|
|
@@ -38917,15 +39377,19 @@ const parseOxlintOutput = (stdout, project, rootDirectory) => {
|
|
|
38917
39377
|
};
|
|
38918
39378
|
});
|
|
38919
39379
|
};
|
|
38920
|
-
const
|
|
38921
|
-
const
|
|
38922
|
-
for (const [name, value] of Object.entries(
|
|
39380
|
+
const buildOxlintChildEnv = (sourceEnv) => {
|
|
39381
|
+
const childEnv = {};
|
|
39382
|
+
for (const [name, value] of Object.entries(sourceEnv)) {
|
|
38923
39383
|
if (name === "NODE_OPTIONS" || name === "NODE_DEBUG") continue;
|
|
38924
39384
|
if (name.startsWith("npm_config_")) continue;
|
|
38925
|
-
|
|
39385
|
+
childEnv[name] = value;
|
|
38926
39386
|
}
|
|
38927
|
-
|
|
38928
|
-
|
|
39387
|
+
const isCompileCacheDisabled = Boolean(sourceEnv.NODE_DISABLE_COMPILE_CACHE);
|
|
39388
|
+
const isCompileCacheAlreadySet = childEnv.NODE_COMPILE_CACHE !== void 0;
|
|
39389
|
+
if (!isCompileCacheDisabled && !isCompileCacheAlreadySet) childEnv.NODE_COMPILE_CACHE = Path.join(os.tmpdir(), NODE_COMPILE_CACHE_DIR_NAME);
|
|
39390
|
+
return childEnv;
|
|
39391
|
+
};
|
|
39392
|
+
const SANITIZED_ENV = buildOxlintChildEnv(process.env);
|
|
38929
39393
|
/**
|
|
38930
39394
|
* Spawn one oxlint subprocess with hard ceilings on wall time and
|
|
38931
39395
|
* output size. Returns stdout on success; raises a tagged
|
|
@@ -38942,7 +39406,11 @@ const SANITIZED_ENV = (() => {
|
|
|
38942
39406
|
* The first three are splittable (the caller's binary-split retry
|
|
38943
39407
|
* shrinks the batch and re-spawns); the fourth isn't.
|
|
38944
39408
|
*/
|
|
38945
|
-
const spawnOxlint = (args, rootDirectory, nodeBinaryPath, spawnTimeoutMs = OXLINT_SPAWN_TIMEOUT_MS, outputMaxBytes = OXLINT_OUTPUT_MAX_BYTES) => new Promise((resolve, reject) => {
|
|
39409
|
+
const spawnOxlint = (args, rootDirectory, nodeBinaryPath, spawnTimeoutMs = OXLINT_SPAWN_TIMEOUT_MS, outputMaxBytes = OXLINT_OUTPUT_MAX_BYTES, abortSignal) => new Promise((resolve, reject) => {
|
|
39410
|
+
if (abortSignal?.aborted) {
|
|
39411
|
+
reject(new ReactDoctorError({ reason: new OxlintSpawnFailed({ cause: "lint phase aborted" }) }));
|
|
39412
|
+
return;
|
|
39413
|
+
}
|
|
38946
39414
|
const child = spawn(nodeBinaryPath, args, {
|
|
38947
39415
|
cwd: rootDirectory,
|
|
38948
39416
|
env: SANITIZED_ENV,
|
|
@@ -38952,7 +39420,14 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath, spawnTimeoutMs = OXLIN
|
|
|
38952
39420
|
"pipe"
|
|
38953
39421
|
]
|
|
38954
39422
|
});
|
|
39423
|
+
const onAbort = () => {
|
|
39424
|
+
child.kill("SIGKILL");
|
|
39425
|
+
reject(new ReactDoctorError({ reason: new OxlintSpawnFailed({ cause: "lint phase aborted" }) }));
|
|
39426
|
+
};
|
|
39427
|
+
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
39428
|
+
const clearAbortListener = () => abortSignal?.removeEventListener("abort", onAbort);
|
|
38955
39429
|
const timeoutHandle = setTimeout(() => {
|
|
39430
|
+
clearAbortListener();
|
|
38956
39431
|
child.kill("SIGKILL");
|
|
38957
39432
|
reject(new ReactDoctorError({ reason: new OxlintBatchExceeded({
|
|
38958
39433
|
kind: "timeout",
|
|
@@ -38987,10 +39462,12 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath, spawnTimeoutMs = OXLIN
|
|
|
38987
39462
|
});
|
|
38988
39463
|
child.on("error", (error) => {
|
|
38989
39464
|
clearTimeout(timeoutHandle);
|
|
39465
|
+
clearAbortListener();
|
|
38990
39466
|
reject(new ReactDoctorError({ reason: new OxlintSpawnFailed({ cause: error }) }));
|
|
38991
39467
|
});
|
|
38992
39468
|
child.on("close", (_code, signal) => {
|
|
38993
39469
|
clearTimeout(timeoutHandle);
|
|
39470
|
+
clearAbortListener();
|
|
38994
39471
|
if (didKillForSize) {
|
|
38995
39472
|
reject(new ReactDoctorError({ reason: new OxlintBatchExceeded({
|
|
38996
39473
|
kind: "output-too-large",
|
|
@@ -39057,26 +39534,28 @@ const isParallelismRelatedSpawnError = (error) => {
|
|
|
39057
39534
|
* loop with a slimmer config in that case.
|
|
39058
39535
|
*/
|
|
39059
39536
|
const spawnLintBatches = async (input) => {
|
|
39060
|
-
const { baseArgs, fileBatches, rootDirectory, nodeBinaryPath, project, onPartialFailure, onFileProgress, spawnTimeoutMs, outputMaxBytes } = input;
|
|
39537
|
+
const { baseArgs, fileBatches, rootDirectory, nodeBinaryPath, project, onPartialFailure, onFileProgress, spawnTimeoutMs, outputMaxBytes, splitTotalBudgetMs = OXLINT_SPLIT_TOTAL_BUDGET_MS, splitMaxDepth = 8, signal } = input;
|
|
39061
39538
|
const requestedConcurrency = resolveScanConcurrency(input.concurrency ?? 1);
|
|
39062
39539
|
const totalFileCount = fileBatches.reduce((sum, batch) => sum + batch.length, 0);
|
|
39063
39540
|
const runBatchPass = async (concurrency) => {
|
|
39064
39541
|
const allDiagnostics = [];
|
|
39065
39542
|
const droppedFiles = [];
|
|
39066
39543
|
let firstDropReason = null;
|
|
39067
|
-
const
|
|
39544
|
+
const splitDeadlineMs = Date.now() + splitTotalBudgetMs;
|
|
39545
|
+
const spawnLintBatch = async (batch, depth) => {
|
|
39068
39546
|
const batchArgs = [...baseArgs, ...batch];
|
|
39069
39547
|
try {
|
|
39070
|
-
return parseOxlintOutput(await spawnOxlint(batchArgs, rootDirectory, nodeBinaryPath, spawnTimeoutMs, outputMaxBytes), project, rootDirectory);
|
|
39548
|
+
return parseOxlintOutput(await spawnOxlint(batchArgs, rootDirectory, nodeBinaryPath, spawnTimeoutMs, outputMaxBytes, signal), project, rootDirectory);
|
|
39071
39549
|
} catch (error) {
|
|
39072
39550
|
if (!isSplittableReactDoctorError(error)) throw error;
|
|
39073
|
-
|
|
39551
|
+
const splitBudgetExhausted = Date.now() >= splitDeadlineMs || depth >= splitMaxDepth;
|
|
39552
|
+
if (batch.length <= 1 || splitBudgetExhausted) {
|
|
39074
39553
|
droppedFiles.push(...batch);
|
|
39075
|
-
if (firstDropReason === null) firstDropReason = error.message;
|
|
39554
|
+
if (firstDropReason === null) firstDropReason = splitBudgetExhausted && batch.length > 1 ? `${error.message} (split budget exhausted after ${splitMaxDepth} levels / ${splitTotalBudgetMs / MILLISECONDS_PER_SECOND}s)` : error.message;
|
|
39076
39555
|
return [];
|
|
39077
39556
|
}
|
|
39078
39557
|
const splitIndex = Math.ceil(batch.length / 2);
|
|
39079
|
-
return [...await spawnLintBatch(batch.slice(0, splitIndex)), ...await spawnLintBatch(batch.slice(splitIndex))];
|
|
39558
|
+
return [...await spawnLintBatch(batch.slice(0, splitIndex), depth + 1), ...await spawnLintBatch(batch.slice(splitIndex), depth + 1)];
|
|
39080
39559
|
}
|
|
39081
39560
|
};
|
|
39082
39561
|
let startedFileCount = 0;
|
|
@@ -39093,7 +39572,7 @@ const spawnLintBatches = async (input) => {
|
|
|
39093
39572
|
try {
|
|
39094
39573
|
const batchResults = await mapWithConcurrency(fileBatches, concurrency, async (batch) => {
|
|
39095
39574
|
startedFileCount += batch.length;
|
|
39096
|
-
const batchDiagnostics = await spawnLintBatch(batch);
|
|
39575
|
+
const batchDiagnostics = await spawnLintBatch(batch, 0);
|
|
39097
39576
|
scannedFileCount += batch.length;
|
|
39098
39577
|
if (onFileProgress) {
|
|
39099
39578
|
displayedFileCount = Math.min(Math.max(displayedFileCount, scannedFileCount), totalFileCount);
|
|
@@ -39154,6 +39633,22 @@ const validateRuleRegistration = () => {
|
|
|
39154
39633
|
].filter((entry) => entry !== null).join("; ");
|
|
39155
39634
|
console.warn(`[react-doctor] rule-registration drift: ${detail}`);
|
|
39156
39635
|
};
|
|
39636
|
+
const hashFileContents = (filePath) => {
|
|
39637
|
+
try {
|
|
39638
|
+
return crypto.createHash("sha1").update(NFS.readFileSync(filePath)).digest("hex");
|
|
39639
|
+
} catch {
|
|
39640
|
+
return null;
|
|
39641
|
+
}
|
|
39642
|
+
};
|
|
39643
|
+
const projectCacheSubdir = (projectDirectory) => crypto.createHash("sha256").update(projectDirectory).digest("hex").slice(0, 16);
|
|
39644
|
+
const resolveReactDoctorCacheDir = (projectDirectory) => {
|
|
39645
|
+
const cacheDirOverride = process.env["REACT_DOCTOR_CACHE_DIR"]?.trim();
|
|
39646
|
+
if (cacheDirOverride) return Path.join(cacheDirOverride, projectCacheSubdir(projectDirectory));
|
|
39647
|
+
const nodeModulesDirectory = Path.join(projectDirectory, "node_modules");
|
|
39648
|
+
if (NFS.existsSync(nodeModulesDirectory)) return Path.join(nodeModulesDirectory, ".cache", "react-doctor");
|
|
39649
|
+
return Path.join(os.tmpdir(), "react-doctor-cache", projectCacheSubdir(projectDirectory));
|
|
39650
|
+
};
|
|
39651
|
+
const sortSourceFilesByCost = (entries) => [...entries].sort((left, right) => right.sizeBytes - left.sizeBytes).map((entry) => entry.path);
|
|
39157
39652
|
/**
|
|
39158
39653
|
* Atomically (re)writes the generated oxlintrc.json. Used twice in
|
|
39159
39654
|
* the runner: once for the primary scan, once for the
|
|
@@ -39212,7 +39707,7 @@ const reactHooksJsPluginDropNote = (error) => {
|
|
|
39212
39707
|
* 6. always restore disable directives + clean up the temp dir
|
|
39213
39708
|
*/
|
|
39214
39709
|
const runOxlint = async (options) => {
|
|
39215
|
-
const { rootDirectory, project, includePaths, nodeBinaryPath = process.execPath, customRulesOnly = false, respectInlineDisables = true, adoptExistingLintConfig = true, ignoredTags = /* @__PURE__ */ new Set(), userConfig, configSourceDirectory = rootDirectory, onPartialFailure, spawnTimeoutMs, outputMaxBytes } = options;
|
|
39710
|
+
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;
|
|
39216
39711
|
const serverAuthFunctionNames = Array.isArray(userConfig?.serverAuthFunctionNames) ? userConfig.serverAuthFunctionNames.filter((entry) => typeof entry === "string" && entry.length > 0) : void 0;
|
|
39217
39712
|
const severityControls = buildRuleSeverityControls(userConfig);
|
|
39218
39713
|
validateRuleRegistration();
|
|
@@ -39229,30 +39724,156 @@ const runOxlint = async (options) => {
|
|
|
39229
39724
|
serverAuthFunctionNames,
|
|
39230
39725
|
severityControls,
|
|
39231
39726
|
userPlugins,
|
|
39232
|
-
disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin
|
|
39727
|
+
disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin,
|
|
39728
|
+
ruleSelection: overrides.ruleSelection
|
|
39233
39729
|
});
|
|
39234
39730
|
const restoreDisableDirectives = respectInlineDisables ? () => {} : await neutralizeDisableDirectives(rootDirectory, includePaths);
|
|
39235
39731
|
const configDirectory = NFS.mkdtempSync(Path.join(os.tmpdir(), "react-doctor-oxlintrc-"));
|
|
39236
39732
|
const configPath = Path.join(configDirectory, "oxlintrc.json");
|
|
39237
39733
|
try {
|
|
39238
|
-
const
|
|
39239
|
-
|
|
39240
|
-
|
|
39241
|
-
configPath,
|
|
39242
|
-
"--format",
|
|
39243
|
-
"json"
|
|
39244
|
-
];
|
|
39734
|
+
const oxlintBinary = resolveOxlintBinary();
|
|
39735
|
+
const sharedArgs = [];
|
|
39736
|
+
let tsconfigContent = null;
|
|
39245
39737
|
if (project.hasTypeScript) {
|
|
39246
39738
|
const tsconfigRelativePath = resolveTsConfigRelativePath(rootDirectory);
|
|
39247
|
-
if (tsconfigRelativePath)
|
|
39739
|
+
if (tsconfigRelativePath) {
|
|
39740
|
+
sharedArgs.push("--tsconfig", tsconfigRelativePath);
|
|
39741
|
+
try {
|
|
39742
|
+
tsconfigContent = NFS.readFileSync(Path.resolve(rootDirectory, tsconfigRelativePath), "utf8");
|
|
39743
|
+
} catch {
|
|
39744
|
+
tsconfigContent = null;
|
|
39745
|
+
}
|
|
39746
|
+
}
|
|
39248
39747
|
}
|
|
39249
39748
|
const combinedPatterns = collectIgnorePatterns(rootDirectory);
|
|
39250
39749
|
if (combinedPatterns.length > 0) {
|
|
39251
39750
|
const combinedIgnorePath = Path.join(configDirectory, "combined.ignore");
|
|
39252
39751
|
NFS.writeFileSync(combinedIgnorePath, `${combinedPatterns.join("\n")}\n`);
|
|
39253
|
-
|
|
39752
|
+
sharedArgs.push("--ignore-path", combinedIgnorePath);
|
|
39753
|
+
}
|
|
39754
|
+
const makeBaseArgs = (oxlintConfigPath) => [
|
|
39755
|
+
oxlintBinary,
|
|
39756
|
+
"-c",
|
|
39757
|
+
oxlintConfigPath,
|
|
39758
|
+
"--format",
|
|
39759
|
+
"json",
|
|
39760
|
+
...sharedArgs
|
|
39761
|
+
];
|
|
39762
|
+
const discoverScanFiles = () => lintBatchOrdering === "cost" ? sortSourceFilesByCost(listSourceFilesWithSize(rootDirectory)) : listSourceFiles(rootDirectory);
|
|
39763
|
+
const candidateFiles = includePaths !== void 0 ? includePaths : discoverScanFiles();
|
|
39764
|
+
const runConfigOverFiles = async (buildConfigForPass, configFileName, files, fileProgress) => {
|
|
39765
|
+
if (files.length === 0) return {
|
|
39766
|
+
diagnostics: [],
|
|
39767
|
+
didDropReactHooksJsPlugin: false,
|
|
39768
|
+
hadPartialFailure: false
|
|
39769
|
+
};
|
|
39770
|
+
let hadPartialFailure = false;
|
|
39771
|
+
const reportPartialFailure = (reason) => {
|
|
39772
|
+
hadPartialFailure = true;
|
|
39773
|
+
onPartialFailure?.(reason);
|
|
39774
|
+
};
|
|
39775
|
+
const passConfigPath = Path.join(configDirectory, configFileName);
|
|
39776
|
+
const passBaseArgs = makeBaseArgs(passConfigPath);
|
|
39777
|
+
const passFileBatches = batchIncludePaths(passBaseArgs, files);
|
|
39778
|
+
const spawnPass = () => spawnLintBatches({
|
|
39779
|
+
baseArgs: passBaseArgs,
|
|
39780
|
+
fileBatches: passFileBatches,
|
|
39781
|
+
rootDirectory,
|
|
39782
|
+
nodeBinaryPath,
|
|
39783
|
+
project,
|
|
39784
|
+
onPartialFailure: reportPartialFailure,
|
|
39785
|
+
onFileProgress: fileProgress,
|
|
39786
|
+
spawnTimeoutMs,
|
|
39787
|
+
outputMaxBytes,
|
|
39788
|
+
concurrency: options.concurrency,
|
|
39789
|
+
signal: options.signal
|
|
39790
|
+
});
|
|
39791
|
+
writeOxlintConfig(passConfigPath, buildConfigForPass({}));
|
|
39792
|
+
try {
|
|
39793
|
+
return {
|
|
39794
|
+
diagnostics: await spawnPass(),
|
|
39795
|
+
didDropReactHooksJsPlugin: false,
|
|
39796
|
+
hadPartialFailure
|
|
39797
|
+
};
|
|
39798
|
+
} catch (error) {
|
|
39799
|
+
const reactHooksJsDropNote = reactHooksJsPluginDropNote(error);
|
|
39800
|
+
if (reactHooksJsDropNote === null) throw error;
|
|
39801
|
+
writeOxlintConfig(passConfigPath, buildConfigForPass({ disableReactHooksJsPlugin: true }));
|
|
39802
|
+
const diagnostics = await spawnPass();
|
|
39803
|
+
reportPartialFailure(reactHooksJsDropNote);
|
|
39804
|
+
return {
|
|
39805
|
+
diagnostics,
|
|
39806
|
+
didDropReactHooksJsPlugin: true,
|
|
39807
|
+
hadPartialFailure
|
|
39808
|
+
};
|
|
39809
|
+
}
|
|
39810
|
+
};
|
|
39811
|
+
if (perFileLintCacheEnabled && respectInlineDisables && !project.hasReactCompiler && extendsPaths.length === 0 && userPlugins.length === 0) {
|
|
39812
|
+
const rulesetHash = computeRulesetHash({
|
|
39813
|
+
config: buildConfig({
|
|
39814
|
+
extendsPaths: [],
|
|
39815
|
+
ruleSelection: "cacheable"
|
|
39816
|
+
}),
|
|
39817
|
+
toolchainVersions: resolveOxlintToolchainVersions(),
|
|
39818
|
+
ignorePatterns: combinedPatterns,
|
|
39819
|
+
tsconfigContent
|
|
39820
|
+
});
|
|
39821
|
+
const cache = createFileLintCache(resolveReactDoctorCacheDir(rootDirectory), rulesetHash);
|
|
39822
|
+
const cacheKeyByFile = /* @__PURE__ */ new Map();
|
|
39823
|
+
const missFiles = [];
|
|
39824
|
+
const replayedDiagnostics = [];
|
|
39825
|
+
for (const candidateFile of candidateFiles) {
|
|
39826
|
+
const contentHash = hashFileContents(Path.resolve(rootDirectory, candidateFile));
|
|
39827
|
+
if (contentHash === null) {
|
|
39828
|
+
missFiles.push(candidateFile);
|
|
39829
|
+
continue;
|
|
39830
|
+
}
|
|
39831
|
+
const cacheKey = `${candidateFile.replaceAll("\\", "/")}${contentHash}`;
|
|
39832
|
+
cacheKeyByFile.set(candidateFile, cacheKey);
|
|
39833
|
+
const cachedDiagnostics = cache.lookup(cacheKey);
|
|
39834
|
+
if (cachedDiagnostics === null) missFiles.push(candidateFile);
|
|
39835
|
+
else replayedDiagnostics.push(...cachedDiagnostics);
|
|
39836
|
+
}
|
|
39837
|
+
const cacheHitFileCount = candidateFiles.length - missFiles.length;
|
|
39838
|
+
const cacheableResult = await runConfigOverFiles((overrides) => buildConfig({
|
|
39839
|
+
extendsPaths: [],
|
|
39840
|
+
ruleSelection: "cacheable",
|
|
39841
|
+
disableReactHooksJsPlugin: overrides.disableReactHooksJsPlugin
|
|
39842
|
+
}), "oxlintrc.cacheable.json", missFiles, void 0);
|
|
39843
|
+
const sidecarResult = await runConfigOverFiles(() => buildConfig({
|
|
39844
|
+
extendsPaths: [],
|
|
39845
|
+
ruleSelection: "sidecar"
|
|
39846
|
+
}), "oxlintrc.sidecar.json", candidateFiles, options.onFileProgress);
|
|
39847
|
+
onCacheStats?.(cacheHitFileCount, candidateFiles.length);
|
|
39848
|
+
const missFileByNormalizedPath = /* @__PURE__ */ new Map();
|
|
39849
|
+
for (const missFile of missFiles) missFileByNormalizedPath.set(missFile.replaceAll("\\", "/"), missFile);
|
|
39850
|
+
const freshDiagnosticsByFile = /* @__PURE__ */ new Map();
|
|
39851
|
+
let isAttributionSound = true;
|
|
39852
|
+
for (const diagnostic of cacheableResult.diagnostics) {
|
|
39853
|
+
const missFile = missFileByNormalizedPath.get(diagnostic.filePath);
|
|
39854
|
+
if (missFile === void 0) {
|
|
39855
|
+
isAttributionSound = false;
|
|
39856
|
+
break;
|
|
39857
|
+
}
|
|
39858
|
+
const fileDiagnostics = freshDiagnosticsByFile.get(missFile) ?? [];
|
|
39859
|
+
fileDiagnostics.push(diagnostic);
|
|
39860
|
+
freshDiagnosticsByFile.set(missFile, fileDiagnostics);
|
|
39861
|
+
}
|
|
39862
|
+
if (!cacheableResult.didDropReactHooksJsPlugin && !cacheableResult.hadPartialFailure && isAttributionSound) {
|
|
39863
|
+
for (const missFile of missFiles) {
|
|
39864
|
+
const cacheKey = cacheKeyByFile.get(missFile);
|
|
39865
|
+
if (cacheKey !== void 0) cache.store(cacheKey, freshDiagnosticsByFile.get(missFile) ?? []);
|
|
39866
|
+
}
|
|
39867
|
+
cache.persist();
|
|
39868
|
+
}
|
|
39869
|
+
return dedupeDiagnostics([
|
|
39870
|
+
...replayedDiagnostics,
|
|
39871
|
+
...cacheableResult.diagnostics,
|
|
39872
|
+
...sidecarResult.diagnostics
|
|
39873
|
+
]);
|
|
39254
39874
|
}
|
|
39255
|
-
const
|
|
39875
|
+
const baseArgs = makeBaseArgs(configPath);
|
|
39876
|
+
const fileBatches = batchIncludePaths(baseArgs, candidateFiles);
|
|
39256
39877
|
const runBatches = () => spawnLintBatches({
|
|
39257
39878
|
baseArgs,
|
|
39258
39879
|
fileBatches,
|
|
@@ -39263,7 +39884,8 @@ const runOxlint = async (options) => {
|
|
|
39263
39884
|
onFileProgress: options.onFileProgress,
|
|
39264
39885
|
spawnTimeoutMs,
|
|
39265
39886
|
outputMaxBytes,
|
|
39266
|
-
concurrency: options.concurrency
|
|
39887
|
+
concurrency: options.concurrency,
|
|
39888
|
+
signal: options.signal
|
|
39267
39889
|
});
|
|
39268
39890
|
writeOxlintConfig(configPath, buildConfig({ extendsPaths }));
|
|
39269
39891
|
try {
|
|
@@ -39342,9 +39964,11 @@ var Linter = class Linter extends Service()("react-doctor/Linter") {
|
|
|
39342
39964
|
const spawnTimeoutMs = yield* OxlintSpawnTimeoutMs;
|
|
39343
39965
|
const outputMaxBytes = yield* OxlintOutputMaxBytes;
|
|
39344
39966
|
const concurrency = yield* OxlintConcurrency;
|
|
39967
|
+
const lintBatchOrdering = yield* LintBatchOrdering;
|
|
39968
|
+
const perFileLintCacheEnabled = yield* PerFileLintCacheEnabled;
|
|
39345
39969
|
const collectedFailures = [];
|
|
39346
39970
|
const diagnostics = yield* tryPromise({
|
|
39347
|
-
try: () => runOxlint({
|
|
39971
|
+
try: (signal) => runOxlint({
|
|
39348
39972
|
rootDirectory: input.rootDirectory,
|
|
39349
39973
|
project: input.project,
|
|
39350
39974
|
includePaths: input.includePaths ? [...input.includePaths] : void 0,
|
|
@@ -39359,9 +39983,13 @@ var Linter = class Linter extends Service()("react-doctor/Linter") {
|
|
|
39359
39983
|
collectedFailures.push(reason);
|
|
39360
39984
|
},
|
|
39361
39985
|
onFileProgress: input.onFileProgress,
|
|
39986
|
+
perFileLintCacheEnabled,
|
|
39987
|
+
onCacheStats: input.onCacheStats,
|
|
39362
39988
|
spawnTimeoutMs,
|
|
39363
39989
|
outputMaxBytes,
|
|
39364
|
-
concurrency
|
|
39990
|
+
concurrency,
|
|
39991
|
+
signal,
|
|
39992
|
+
lintBatchOrdering
|
|
39365
39993
|
}),
|
|
39366
39994
|
catch: ensureReactDoctorError
|
|
39367
39995
|
});
|
|
@@ -39753,14 +40381,49 @@ const parseArtifactFromBody = (body) => {
|
|
|
39753
40381
|
}
|
|
39754
40382
|
return null;
|
|
39755
40383
|
};
|
|
39756
|
-
const
|
|
40384
|
+
const isSupplyChainCacheDisabled = () => {
|
|
40385
|
+
const noCache = process.env["REACT_DOCTOR_NO_CACHE"]?.toLowerCase() ?? "";
|
|
40386
|
+
return noCache === "1" || noCache === "true";
|
|
40387
|
+
};
|
|
40388
|
+
const supplyChainCacheFile = (cacheDirectory, dependency) => {
|
|
40389
|
+
const purlHash = crypto.createHash("sha256").update(toPurl(dependency)).digest("hex").slice(0, 16);
|
|
40390
|
+
return Path.join(cacheDirectory, SUPPLY_CHAIN_CACHE_SUBDIR, `${purlHash}.json`);
|
|
40391
|
+
};
|
|
40392
|
+
const readCachedSocketBody = (cacheFile) => {
|
|
40393
|
+
try {
|
|
40394
|
+
const entry = JSON.parse(NFS.readFileSync(cacheFile, "utf-8"));
|
|
40395
|
+
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;
|
|
40396
|
+
} catch {}
|
|
40397
|
+
return null;
|
|
40398
|
+
};
|
|
40399
|
+
const writeCachedSocketBody = (cacheFile, body) => {
|
|
40400
|
+
try {
|
|
40401
|
+
NFS.mkdirSync(Path.dirname(cacheFile), { recursive: true });
|
|
40402
|
+
NFS.writeFileSync(cacheFile, JSON.stringify({
|
|
40403
|
+
fetchedAtMs: Date.now(),
|
|
40404
|
+
body
|
|
40405
|
+
}));
|
|
40406
|
+
} catch {}
|
|
40407
|
+
};
|
|
40408
|
+
const fetchSocketArtifact = (dependency, cacheDirectory) => tryPromise(async (signal) => {
|
|
40409
|
+
const cacheFile = cacheDirectory === null ? null : supplyChainCacheFile(cacheDirectory, dependency);
|
|
40410
|
+
if (cacheFile !== null) {
|
|
40411
|
+
const cachedBody = readCachedSocketBody(cacheFile);
|
|
40412
|
+
if (cachedBody !== null) {
|
|
40413
|
+
const cachedArtifact = parseArtifactFromBody(cachedBody);
|
|
40414
|
+
if (cachedArtifact !== null) return cachedArtifact;
|
|
40415
|
+
}
|
|
40416
|
+
}
|
|
39757
40417
|
const requestUrl = `${SOCKET_FREE_PURL_API_BASE}/${encodeURIComponent(toPurl(dependency))}`;
|
|
39758
40418
|
const response = await fetch(requestUrl, {
|
|
39759
40419
|
headers: { "User-Agent": SOCKET_FREE_USER_AGENT },
|
|
39760
40420
|
signal
|
|
39761
40421
|
});
|
|
39762
40422
|
if (!response.ok) return null;
|
|
39763
|
-
|
|
40423
|
+
const body = await response.text();
|
|
40424
|
+
const artifact = parseArtifactFromBody(body);
|
|
40425
|
+
if (artifact !== null && cacheFile !== null) writeCachedSocketBody(cacheFile, body);
|
|
40426
|
+
return artifact;
|
|
39764
40427
|
}).pipe(timeout(FETCH_TIMEOUT_MS), orElseSucceed(() => null), tap$1((artifact) => {
|
|
39765
40428
|
const scoreAttributes = {};
|
|
39766
40429
|
if (artifact !== null) {
|
|
@@ -39865,7 +40528,8 @@ const checkSupplyChain = (input) => gen(function* () {
|
|
|
39865
40528
|
const packageJsonPath = Path.join(input.rootDirectory, "package.json");
|
|
39866
40529
|
const dependencies = collectDependenciesToScore(readPackageJson(packageJsonPath), readPackageJsonText(packageJsonPath), options.includeDevDependencies);
|
|
39867
40530
|
if (dependencies.length === 0) return [];
|
|
39868
|
-
const
|
|
40531
|
+
const cacheDirectory = isSupplyChainCacheDisabled() ? null : resolveReactDoctorCacheDir(input.rootDirectory);
|
|
40532
|
+
const artifacts = yield* forEach$1(dependencies, (dependency) => fetchSocketArtifact(dependency, cacheDirectory), { concurrency: 8 }).pipe(timeoutOption(input.totalTimeoutMs ?? 9e4), map$3((maybeArtifacts) => getOrElse$1(maybeArtifacts, () => [])));
|
|
39869
40533
|
const diagnostics = [];
|
|
39870
40534
|
for (let index = 0; index < dependencies.length; index += 1) {
|
|
39871
40535
|
const artifact = artifacts[index];
|
|
@@ -39890,6 +40554,10 @@ const checkSupplyChain = (input) => gen(function* () {
|
|
|
39890
40554
|
* The underlying `checkSupplyChain` Effect is total/fail-open — per-package
|
|
39891
40555
|
* timeouts and network failures recover to "skip" — so the stream never
|
|
39892
40556
|
* fails, mirroring `DeadCode`'s stream shape so the two compose the same way.
|
|
40557
|
+
* The orchestrator (`run-inspect.ts`) consumes this stream on a background
|
|
40558
|
+
* fiber whose network time overlaps the lint pass, joined under a generous
|
|
40559
|
+
* wall-clock budget; a budget expiry is the same fail-open outcome as a Socket
|
|
40560
|
+
* outage.
|
|
39893
40561
|
*/
|
|
39894
40562
|
var SupplyChain = class SupplyChain extends Service()("react-doctor/SupplyChain") {
|
|
39895
40563
|
static layerNode = succeed$3(SupplyChain, SupplyChain.of({ run: (input) => unwrap(checkSupplyChain(input).pipe(map$3((diagnostics) => fromIterable$1(diagnostics)), withSpan("SupplyChain.run"))) }));
|
|
@@ -39948,18 +40616,42 @@ const formatLintFailText = (reasonTag, nodeVersion) => {
|
|
|
39948
40616
|
*
|
|
39949
40617
|
* Phases:
|
|
39950
40618
|
*
|
|
39951
|
-
* 1. Config.resolve(directory) → Project.discover → Git metadata
|
|
40619
|
+
* 1. Config.resolve(directory) → Project.discover → Git metadata.
|
|
40620
|
+
* The GitHub viewer-permission lookup is forked onto a background
|
|
40621
|
+
* fiber here and joined late (it feeds score metadata, not
|
|
40622
|
+
* diagnostics).
|
|
39952
40623
|
* 2. beforeLint hook (e.g. CLI renders the project-detection block)
|
|
39953
40624
|
* 3. environment checks (reduced-motion + pnpm hardening +
|
|
39954
|
-
* expo/react-native + security scan)
|
|
39955
|
-
* 4.
|
|
39956
|
-
*
|
|
39957
|
-
*
|
|
39958
|
-
*
|
|
39959
|
-
*
|
|
39960
|
-
*
|
|
39961
|
-
*
|
|
39962
|
-
*
|
|
40625
|
+
* expo/react-native + security scan), collected synchronously
|
|
40626
|
+
* 4. The supply-chain check (Socket.dev) is forked onto a background
|
|
40627
|
+
* fiber so its ~100% network-bound time overlaps the ~100%
|
|
40628
|
+
* CPU/subprocess-bound lint pass below, collapsing two serial
|
|
40629
|
+
* phases into roughly `max(supplyChain, lint)`. It is capped by
|
|
40630
|
+
* `SupplyChainOverlapTimeoutMs` (measured from fork) so a hung
|
|
40631
|
+
* socket can't drag out its join; on timeout it fails open to no
|
|
40632
|
+
* diagnostics — the same outcome class as a Socket outage.
|
|
40633
|
+
* 5. Linter.run runs; DeadCode.run runs concurrently (forked child
|
|
40634
|
+
* fiber) ONLY when the memory gate has headroom to run the 8 GB
|
|
40635
|
+
* dead-code child alongside the oxlint workers — or when overlap is
|
|
40636
|
+
* forced via REACT_DOCTOR_DEAD_CODE_OVERLAP. Otherwise dead-code
|
|
40637
|
+
* runs sequentially after lint, exactly as it did pre-overlap. The
|
|
40638
|
+
* fiber is joined (or interrupted, SIGKILLing its worker, on lint
|
|
40639
|
+
* failure) before diagnostics are concatenated. The afterLint hook
|
|
40640
|
+
* fires between lint and dead-code. Progress spinner labels AND the
|
|
40641
|
+
* final diagnostic / score order stay independent of execution
|
|
40642
|
+
* order, so terminal output is identical either way; supply-chain
|
|
40643
|
+
* rides alongside without a spinner.
|
|
40644
|
+
* 6. Join the supply-chain fiber, then assemble the diagnostics in a
|
|
40645
|
+
* FIXED order (env, supply-chain, lint, dead-code) so the output is
|
|
40646
|
+
* byte-identical regardless of which fiber settled first. The
|
|
40647
|
+
* viewer-permission fiber is joined later, during score-metadata
|
|
40648
|
+
* assembly (it feeds score metadata, not diagnostics). The per-element
|
|
40649
|
+
* `Reporter.emit` side-channel now interleaves supply-chain with lint
|
|
40650
|
+
* emits, so capture-order assertions must target the deterministic
|
|
40651
|
+
* concat below, not emit order (production `Reporter.layerNoop` makes
|
|
40652
|
+
* emit a no-op).
|
|
40653
|
+
* 7. Reporter.finalize
|
|
40654
|
+
* 8. Score.compute against the surface-filtered diagnostic set
|
|
39963
40655
|
*
|
|
39964
40656
|
* The orchestrator owns spinner lifecycle via `Progress`; callers
|
|
39965
40657
|
* choose `Progress.layerOra(...)` for CLI feedback or
|
|
@@ -40017,10 +40709,21 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40017
40709
|
ignoredTags: input.ignoredTags
|
|
40018
40710
|
})
|
|
40019
40711
|
])));
|
|
40020
|
-
const
|
|
40712
|
+
const shouldRunSupplyChain = !isDiffMode || (input.supplyChainManifestChanged ?? false);
|
|
40713
|
+
const supplyChainOverlapTimeout = yield* SupplyChainOverlapTimeoutMs;
|
|
40714
|
+
const supplyChainFiber = yield* forkChild(shouldRunSupplyChain ? runCollect(applyPerElementPipeline(supplyChainService.run({
|
|
40021
40715
|
rootDirectory: scanDirectory,
|
|
40022
40716
|
userConfig: resolvedConfig.config
|
|
40023
|
-
})))
|
|
40717
|
+
}))).pipe(map$3((diagnostics) => ({
|
|
40718
|
+
diagnostics,
|
|
40719
|
+
timedOut: false
|
|
40720
|
+
})), timeout(supplyChainOverlapTimeout), orElseSucceed(() => ({
|
|
40721
|
+
diagnostics: [],
|
|
40722
|
+
timedOut: true
|
|
40723
|
+
}))) : succeed$2({
|
|
40724
|
+
diagnostics: [],
|
|
40725
|
+
timedOut: false
|
|
40726
|
+
}));
|
|
40024
40727
|
const lintFailure = yield* make$13({
|
|
40025
40728
|
didFail: false,
|
|
40026
40729
|
reason: null,
|
|
@@ -40031,12 +40734,49 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40031
40734
|
didFail: false,
|
|
40032
40735
|
reason: null
|
|
40033
40736
|
});
|
|
40034
|
-
const scanConcurrency = yield* OxlintConcurrency;
|
|
40737
|
+
const scanConcurrency = resolveScanConcurrency(yield* OxlintConcurrency);
|
|
40738
|
+
const lintPhaseTimeoutMs = yield* LintPhaseTimeoutMs;
|
|
40739
|
+
const deadCodePhaseTimeoutMs = yield* DeadCodePhaseTimeoutMs;
|
|
40740
|
+
const resolveDeadCodePhaseTimeoutMs = (scaledPhaseTimeoutMs) => deadCodePhaseTimeoutMs === 15e4 ? scaledPhaseTimeoutMs : deadCodePhaseTimeoutMs;
|
|
40035
40741
|
const workerCountSuffix = scanConcurrency > 1 ? ` ${highlighter.dim(`[~${scanConcurrency} workers]`)}` : "";
|
|
40742
|
+
const shouldRunDeadCode = input.runDeadCode && !isDiffMode && (showWarnings || deadCodeMaySurfaceWhenWarningsHidden(resolvedConfig.config));
|
|
40743
|
+
const deadCodeOverlapMode = yield* DeadCodeOverlap;
|
|
40744
|
+
const shouldOverlapDeadCode = shouldRunDeadCode && deadCodeOverlapMode === "on";
|
|
40745
|
+
const deadCodeParseConcurrency = shouldOverlapDeadCode ? Math.max(1, Math.floor(scanConcurrency * DEAD_CODE_OVERLAP_PARSE_SHARE)) : void 0;
|
|
40746
|
+
const lintConcurrency = deadCodeParseConcurrency === void 0 ? scanConcurrency : Math.max(1, scanConcurrency - deadCodeParseConcurrency);
|
|
40747
|
+
const buildCollectDeadCode = (deadCodeTimeout) => runCollect(applyPerElementPipeline(deadCodeService.run({
|
|
40748
|
+
rootDirectory: scanDirectory,
|
|
40749
|
+
userConfig: resolvedConfig.config,
|
|
40750
|
+
parseConcurrency: deadCodeParseConcurrency,
|
|
40751
|
+
workerTimeoutMs: deadCodeTimeout.workerTimeoutMs
|
|
40752
|
+
}).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
|
|
40753
|
+
yield* set(deadCodeFailure, {
|
|
40754
|
+
didFail: true,
|
|
40755
|
+
reason: error.message
|
|
40756
|
+
});
|
|
40757
|
+
return empty$4;
|
|
40758
|
+
})))))).pipe(timeoutOption(deadCodeTimeout.phaseTimeoutMs), flatMap$2(match$3({
|
|
40759
|
+
onNone: () => set(deadCodeFailure, {
|
|
40760
|
+
didFail: true,
|
|
40761
|
+
reason: `Dead-code analysis exceeded ${Math.round(deadCodeTimeout.phaseTimeoutMs / MILLISECONDS_PER_SECOND)}s and was skipped.`
|
|
40762
|
+
}).pipe(as([])),
|
|
40763
|
+
onSome: succeed$2
|
|
40764
|
+
})));
|
|
40765
|
+
const overlapDeadCodeTimeout = resolveDeadCodeTimeout({
|
|
40766
|
+
sourceFileCount: project.sourceFileCount,
|
|
40767
|
+
deadCodeConcurrency: deadCodeParseConcurrency ?? scanConcurrency,
|
|
40768
|
+
fullConcurrency: scanConcurrency
|
|
40769
|
+
});
|
|
40770
|
+
const deadCodeFiber = shouldOverlapDeadCode ? yield* forkChild(buildCollectDeadCode({
|
|
40771
|
+
workerTimeoutMs: overlapDeadCodeTimeout.workerTimeoutMs,
|
|
40772
|
+
phaseTimeoutMs: resolveDeadCodePhaseTimeoutMs(overlapDeadCodeTimeout.phaseTimeoutMs)
|
|
40773
|
+
})) : null;
|
|
40036
40774
|
const scanProgress = yield* progressService.start("Scanning...");
|
|
40037
40775
|
const scanStartTime = Date.now();
|
|
40038
40776
|
let lastReportedTotalFileCount = 0;
|
|
40039
|
-
|
|
40777
|
+
let lintCacheHitFileCount = null;
|
|
40778
|
+
let lintCacheTotalFileCount = null;
|
|
40779
|
+
const baseLintStream = linterService.run({
|
|
40040
40780
|
rootDirectory: scanDirectory,
|
|
40041
40781
|
project,
|
|
40042
40782
|
includePaths: lintIncludePaths ?? void 0,
|
|
@@ -40050,6 +40790,10 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40050
40790
|
onFileProgress: (scannedFileCount, totalFileCount) => {
|
|
40051
40791
|
lastReportedTotalFileCount = totalFileCount;
|
|
40052
40792
|
runSync(scanProgress.update(`Scanning files (${scannedFileCount}/${totalFileCount})${workerCountSuffix}...`));
|
|
40793
|
+
},
|
|
40794
|
+
onCacheStats: (cacheHitFileCount, totalConsideredFileCount) => {
|
|
40795
|
+
lintCacheHitFileCount = cacheHitFileCount;
|
|
40796
|
+
lintCacheTotalFileCount = totalConsideredFileCount;
|
|
40053
40797
|
}
|
|
40054
40798
|
}).pipe(catchTag("ReactDoctorError", (error) => unwrap(gen(function* () {
|
|
40055
40799
|
yield* set(lintFailure, {
|
|
@@ -40059,36 +40803,54 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40059
40803
|
reasonKind: error.reason._tag === "OxlintUnavailable" ? error.reason.kind : null
|
|
40060
40804
|
});
|
|
40061
40805
|
return empty$4;
|
|
40062
|
-
}))))
|
|
40806
|
+
}))));
|
|
40807
|
+
const lintCollected = yield* runCollect(applyPerElementPipeline(shouldOverlapDeadCode ? baseLintStream.pipe(provideService(OxlintConcurrency, lintConcurrency)) : baseLintStream)).pipe(timeoutOption(lintPhaseTimeoutMs), flatMap$2(match$3({
|
|
40808
|
+
onNone: () => set(lintFailure, {
|
|
40809
|
+
didFail: true,
|
|
40810
|
+
reason: `Lint analysis exceeded ${lintPhaseTimeoutMs / MILLISECONDS_PER_SECOND}s and was skipped.`,
|
|
40811
|
+
reasonTag: "OxlintBatchExceeded",
|
|
40812
|
+
reasonKind: null
|
|
40813
|
+
}).pipe(as([])),
|
|
40814
|
+
onSome: succeed$2
|
|
40815
|
+
})));
|
|
40063
40816
|
const lintFailureState = yield* get$2(lintFailure);
|
|
40064
40817
|
yield* afterLint(lintFailureState.didFail);
|
|
40065
40818
|
if (lintFailureState.didFail) yield* scanProgress.fail(formatLintFailText(lintFailureState.reasonTag, process.version));
|
|
40066
40819
|
const totalFileCount = lastReportedTotalFileCount || (lintIncludePaths?.length ?? project.sourceFileCount);
|
|
40067
40820
|
const scannedFilesLabel = `${totalFileCount} ${totalFileCount === 1 ? "file" : "files"}`;
|
|
40068
|
-
|
|
40069
|
-
|
|
40070
|
-
|
|
40071
|
-
|
|
40072
|
-
|
|
40073
|
-
|
|
40074
|
-
|
|
40075
|
-
|
|
40821
|
+
let deadCodeCollected = [];
|
|
40822
|
+
if (lintFailureState.didFail) {
|
|
40823
|
+
if (deadCodeFiber !== null) yield* interrupt(deadCodeFiber);
|
|
40824
|
+
} else if (shouldRunDeadCode) {
|
|
40825
|
+
yield* scanProgress.update(`Scanned ${scannedFilesLabel}, analyzing dead code...`);
|
|
40826
|
+
const sequentialDeadCodeTimeout = resolveDeadCodeTimeout({
|
|
40827
|
+
sourceFileCount: totalFileCount,
|
|
40828
|
+
deadCodeConcurrency: scanConcurrency,
|
|
40829
|
+
fullConcurrency: scanConcurrency
|
|
40076
40830
|
});
|
|
40077
|
-
|
|
40078
|
-
|
|
40079
|
-
|
|
40831
|
+
deadCodeCollected = deadCodeFiber !== null ? yield* join(deadCodeFiber) : yield* buildCollectDeadCode({
|
|
40832
|
+
workerTimeoutMs: sequentialDeadCodeTimeout.workerTimeoutMs,
|
|
40833
|
+
phaseTimeoutMs: resolveDeadCodePhaseTimeoutMs(sequentialDeadCodeTimeout.phaseTimeoutMs)
|
|
40834
|
+
});
|
|
40835
|
+
}
|
|
40836
|
+
const deadCodeFailureState = lintFailureState.didFail ? {
|
|
40837
|
+
didFail: false,
|
|
40838
|
+
reason: null
|
|
40839
|
+
} : yield* get$2(deadCodeFailure);
|
|
40080
40840
|
const scanElapsedMilliseconds = Date.now() - scanStartTime;
|
|
40081
40841
|
const scanElapsedSeconds = (scanElapsedMilliseconds / MILLISECONDS_PER_SECOND).toFixed(1);
|
|
40082
40842
|
if (!lintFailureState.didFail) if (deadCodeFailureState.didFail) yield* scanProgress.fail(DEAD_CODE_FAIL_TEXT);
|
|
40083
40843
|
else if (input.suppressScanSummary) yield* scanProgress.stop();
|
|
40084
40844
|
else yield* scanProgress.succeed(`Scanned ${scannedFilesLabel} in ${scanElapsedSeconds}s${workerCountSuffix}`);
|
|
40845
|
+
const supplyChainResult = yield* join(supplyChainFiber);
|
|
40846
|
+
const supplyChainCollected = supplyChainResult.diagnostics;
|
|
40085
40847
|
yield* reporterService.finalize;
|
|
40086
|
-
const finalDiagnostics = assignFixGroups([
|
|
40848
|
+
const finalDiagnostics = sortDiagnosticsStable(assignFixGroups([
|
|
40087
40849
|
...envCollected,
|
|
40088
40850
|
...supplyChainCollected,
|
|
40089
40851
|
...lintCollected,
|
|
40090
40852
|
...deadCodeCollected
|
|
40091
|
-
]);
|
|
40853
|
+
]));
|
|
40092
40854
|
const githubViewerPermission = yield* join(githubViewerPermissionFiber);
|
|
40093
40855
|
const scoreMetadata = {
|
|
40094
40856
|
...repo !== null ? { repo } : {},
|
|
@@ -40124,9 +40886,14 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40124
40886
|
lintPartialFailures,
|
|
40125
40887
|
didDeadCodeFail: deadCodeFailureState.didFail,
|
|
40126
40888
|
deadCodeFailureReason: deadCodeFailureState.reason,
|
|
40889
|
+
deadCodeOverlapped: shouldOverlapDeadCode,
|
|
40127
40890
|
scannedFileCount: totalFileCount,
|
|
40128
40891
|
scannedFilePaths,
|
|
40129
|
-
scanElapsedMilliseconds
|
|
40892
|
+
scanElapsedMilliseconds,
|
|
40893
|
+
scanConcurrency,
|
|
40894
|
+
supplyChainOverlapTimedOut: supplyChainResult.timedOut,
|
|
40895
|
+
lintCacheHitFileCount,
|
|
40896
|
+
lintCacheTotalFileCount
|
|
40130
40897
|
};
|
|
40131
40898
|
}).pipe(withSpan("runInspect", { attributes: {
|
|
40132
40899
|
"inspect.directory": input.directory,
|
|
@@ -40134,7 +40901,7 @@ const runInspect = (input, hooks = {}) => gen(function* () {
|
|
|
40134
40901
|
"inspect.runDeadCode": input.runDeadCode,
|
|
40135
40902
|
"inspect.isCi": input.isCi,
|
|
40136
40903
|
"inspect.scoreSurface": input.scoreSurface ?? "score"
|
|
40137
|
-
} }));
|
|
40904
|
+
} }), (scanProgram) => flatMap$2(ScanDeadlineMs, (scanDeadlineMs) => scanProgram.pipe(timeout(scanDeadlineMs), catchTag$1("TimeoutError", () => new ReactDoctorError({ reason: new ScanDeadlineExceeded({ detail: `${scanDeadlineMs / MILLISECONDS_PER_SECOND}s elapsed` }) })))));
|
|
40138
40905
|
const parseNodeVersion = (versionString) => {
|
|
40139
40906
|
const [major = 0, minor = 0, patch = 0] = versionString.replace(/^v/, "").trim().split(".").map(Number);
|
|
40140
40907
|
return {
|
|
@@ -40678,6 +41445,7 @@ const clearCaches = () => {
|
|
|
40678
41445
|
clearIgnorePatternsCache();
|
|
40679
41446
|
clearPackageRoleCache();
|
|
40680
41447
|
clearAutoSuppressionCaches();
|
|
41448
|
+
clearMinifiedFileCache();
|
|
40681
41449
|
};
|
|
40682
41450
|
const toJsonReport = (result, options) => buildJsonReport({
|
|
40683
41451
|
version: options.version,
|
|
@@ -40701,4 +41469,4 @@ const toJsonReport = (result, options) => buildJsonReport({
|
|
|
40701
41469
|
export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
|
|
40702
41470
|
|
|
40703
41471
|
//# sourceMappingURL=index.js.map
|
|
40704
|
-
//# debugId=
|
|
41472
|
+
//# debugId=33508ee6-c977-5b5f-8585-9939928ce74d
|