react-doctor 0.2.10 → 0.2.11-dev.d917f62
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-logger-BRBUS1pE.js → cli-logger-Df45H6Lw.js} +221 -130
- package/dist/cli.js +21 -14
- package/dist/index.d.ts +17 -8
- package/dist/index.js +225 -133
- package/package.json +4 -4
|
@@ -22,7 +22,6 @@ import * as Option from "effect/Option";
|
|
|
22
22
|
import * as Ref from "effect/Ref";
|
|
23
23
|
import * as Stream from "effect/Stream";
|
|
24
24
|
import * as Cache from "effect/Cache";
|
|
25
|
-
import { Worker } from "node:worker_threads";
|
|
26
25
|
import * as NodeChildProcessSpawner from "@effect/platform-node-shared/NodeChildProcessSpawner";
|
|
27
26
|
import * as NodeFileSystem from "@effect/platform-node-shared/NodeFileSystem";
|
|
28
27
|
import * as NodePath from "@effect/platform-node-shared/NodePath";
|
|
@@ -2698,6 +2697,21 @@ const findDependencyInfoFromMonorepoRoot = (directory) => {
|
|
|
2698
2697
|
framework: rootInfo.framework !== "unknown" ? rootInfo.framework : workspaceInfo.framework
|
|
2699
2698
|
};
|
|
2700
2699
|
};
|
|
2700
|
+
const someWorkspacePackageJson = (rootDirectory, rootPackageJson, predicate) => {
|
|
2701
|
+
if (predicate(rootPackageJson)) return true;
|
|
2702
|
+
const patterns = getWorkspacePatterns(rootDirectory, rootPackageJson);
|
|
2703
|
+
if (patterns.length === 0) return false;
|
|
2704
|
+
const visitedDirectories = /* @__PURE__ */ new Set();
|
|
2705
|
+
for (const pattern of patterns) {
|
|
2706
|
+
const directories = resolveWorkspaceDirectories(rootDirectory, pattern);
|
|
2707
|
+
for (const workspaceDirectory of directories) {
|
|
2708
|
+
if (visitedDirectories.has(workspaceDirectory)) continue;
|
|
2709
|
+
visitedDirectories.add(workspaceDirectory);
|
|
2710
|
+
if (predicate(readPackageJson(path.join(workspaceDirectory, "package.json")))) return true;
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
2713
|
+
return false;
|
|
2714
|
+
};
|
|
2701
2715
|
const NAMES = new Set([
|
|
2702
2716
|
"react-native",
|
|
2703
2717
|
"react-native-tvos",
|
|
@@ -2728,27 +2742,13 @@ const isPackageJsonReactNativeAware = (packageJson) => {
|
|
|
2728
2742
|
if (containsAnyReactNativeDependency(packageJson.optionalDependencies)) return true;
|
|
2729
2743
|
return false;
|
|
2730
2744
|
};
|
|
2731
|
-
const hasReactNativeWorkspaceAnywhere = (rootDirectory, rootPackageJson) =>
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
if (patterns.length === 0) return false;
|
|
2735
|
-
const visitedDirectories = /* @__PURE__ */ new Set();
|
|
2736
|
-
for (const pattern of patterns) {
|
|
2737
|
-
const directories = resolveWorkspaceDirectories(rootDirectory, pattern);
|
|
2738
|
-
for (const workspaceDirectory of directories) {
|
|
2739
|
-
if (visitedDirectories.has(workspaceDirectory)) continue;
|
|
2740
|
-
visitedDirectories.add(workspaceDirectory);
|
|
2741
|
-
if (isPackageJsonReactNativeAware(readPackageJson(path.join(workspaceDirectory, "package.json")))) return true;
|
|
2742
|
-
}
|
|
2743
|
-
}
|
|
2744
|
-
return false;
|
|
2745
|
-
};
|
|
2746
|
-
const hasPreact = (packageJson) => {
|
|
2747
|
-
return "preact" in {
|
|
2745
|
+
const hasReactNativeWorkspaceAnywhere = (rootDirectory, rootPackageJson) => someWorkspacePackageJson(rootDirectory, rootPackageJson, isPackageJsonReactNativeAware);
|
|
2746
|
+
const getPreactVersion = (packageJson) => {
|
|
2747
|
+
return {
|
|
2748
2748
|
...packageJson.peerDependencies,
|
|
2749
2749
|
...packageJson.dependencies,
|
|
2750
2750
|
...packageJson.devDependencies
|
|
2751
|
-
};
|
|
2751
|
+
}.preact ?? null;
|
|
2752
2752
|
};
|
|
2753
2753
|
const TANSTACK_QUERY_PACKAGES = new Set([
|
|
2754
2754
|
"@tanstack/react-query",
|
|
@@ -2763,6 +2763,16 @@ const hasTanStackQuery = (packageJson) => {
|
|
|
2763
2763
|
};
|
|
2764
2764
|
return Object.keys(allDependencies).some((packageName) => TANSTACK_QUERY_PACKAGES.has(packageName));
|
|
2765
2765
|
};
|
|
2766
|
+
const REANIMATED_DEPENDENCY_NAME = "react-native-reanimated";
|
|
2767
|
+
const isPackageJsonReanimatedAware = (packageJson) => {
|
|
2768
|
+
const allDependencies = {
|
|
2769
|
+
...packageJson.peerDependencies,
|
|
2770
|
+
...packageJson.dependencies,
|
|
2771
|
+
...packageJson.devDependencies,
|
|
2772
|
+
...packageJson.optionalDependencies
|
|
2773
|
+
};
|
|
2774
|
+
return Object.hasOwn(allDependencies, REANIMATED_DEPENDENCY_NAME);
|
|
2775
|
+
};
|
|
2766
2776
|
const hasUpperBoundOnlyPeerRange = (range) => {
|
|
2767
2777
|
if (typeof range !== "string") return false;
|
|
2768
2778
|
const normalizedRange = normalizeDependencyVersion(range);
|
|
@@ -2849,12 +2859,22 @@ const listManifestWorkspacePackages = (rootDirectory) => {
|
|
|
2849
2859
|
const nxPatterns = patterns.length > 0 ? [] : getNxWorkspaceDirectories(rootDirectory);
|
|
2850
2860
|
return toReactWorkspacePackages((patterns.length > 0 ? patterns : nxPatterns).flatMap((pattern) => resolveWorkspaceDirectories(rootDirectory, pattern)));
|
|
2851
2861
|
};
|
|
2862
|
+
const NON_PROJECT_DIRECTORIES = new Set([
|
|
2863
|
+
"AppData",
|
|
2864
|
+
"Application Data",
|
|
2865
|
+
"Library"
|
|
2866
|
+
]);
|
|
2867
|
+
const MAX_SCAN_DEPTH = 6;
|
|
2852
2868
|
const discoverReactSubprojectsByFilesystem = (rootDirectory) => {
|
|
2853
2869
|
const packages = [];
|
|
2854
|
-
const pendingDirectories = [
|
|
2870
|
+
const pendingDirectories = [{
|
|
2871
|
+
directory: rootDirectory,
|
|
2872
|
+
depth: 0
|
|
2873
|
+
}];
|
|
2855
2874
|
while (pendingDirectories.length > 0) {
|
|
2856
|
-
const
|
|
2857
|
-
if (!
|
|
2875
|
+
const current = pendingDirectories.pop();
|
|
2876
|
+
if (!current) continue;
|
|
2877
|
+
const { directory: currentDirectory, depth } = current;
|
|
2858
2878
|
const packageJsonPath = path.join(currentDirectory, "package.json");
|
|
2859
2879
|
if (isFile(packageJsonPath)) {
|
|
2860
2880
|
const packageJson = readPackageJson(packageJsonPath);
|
|
@@ -2866,10 +2886,14 @@ const discoverReactSubprojectsByFilesystem = (rootDirectory) => {
|
|
|
2866
2886
|
});
|
|
2867
2887
|
}
|
|
2868
2888
|
}
|
|
2889
|
+
if (depth >= MAX_SCAN_DEPTH) continue;
|
|
2869
2890
|
const entries = readDirectoryEntries(currentDirectory).toSorted((firstEntry, secondEntry) => firstEntry.name.localeCompare(secondEntry.name));
|
|
2870
2891
|
for (const entry of entries) {
|
|
2871
|
-
if (!entry.isDirectory() || entry.name.startsWith(".") || IGNORED_DIRECTORIES.has(entry.name)) continue;
|
|
2872
|
-
pendingDirectories.push(
|
|
2892
|
+
if (!entry.isDirectory() || entry.name.startsWith(".") || IGNORED_DIRECTORIES.has(entry.name) || NON_PROJECT_DIRECTORIES.has(entry.name)) continue;
|
|
2893
|
+
pendingDirectories.push({
|
|
2894
|
+
directory: path.join(currentDirectory, entry.name),
|
|
2895
|
+
depth: depth + 1
|
|
2896
|
+
});
|
|
2873
2897
|
}
|
|
2874
2898
|
}
|
|
2875
2899
|
return packages;
|
|
@@ -2937,6 +2961,8 @@ const discoverProject = (directory) => {
|
|
|
2937
2961
|
const hasTypeScript = fs.existsSync(path.join(directory, "tsconfig.json"));
|
|
2938
2962
|
const sourceFileCount = countSourceFiles(directory);
|
|
2939
2963
|
const hasReactNativeWorkspace = framework === "expo" || framework === "react-native" || hasReactNativeWorkspaceAnywhere(directory, packageJson);
|
|
2964
|
+
const hasReanimated = hasReactNativeWorkspace && someWorkspacePackageJson(directory, packageJson, isPackageJsonReanimatedAware);
|
|
2965
|
+
const preactVersion = getPreactVersion(packageJson);
|
|
2940
2966
|
const projectInfo = {
|
|
2941
2967
|
rootDirectory: directory,
|
|
2942
2968
|
projectName,
|
|
@@ -2947,13 +2973,16 @@ const discoverProject = (directory) => {
|
|
|
2947
2973
|
hasTypeScript,
|
|
2948
2974
|
hasReactCompiler: detectReactCompiler(directory, packageJson),
|
|
2949
2975
|
hasTanStackQuery: hasTanStackQuery(packageJson),
|
|
2950
|
-
|
|
2976
|
+
preactVersion,
|
|
2977
|
+
preactMajorVersion: parseReactMajor(preactVersion),
|
|
2951
2978
|
hasReactNativeWorkspace,
|
|
2979
|
+
hasReanimated,
|
|
2952
2980
|
sourceFileCount
|
|
2953
2981
|
};
|
|
2954
2982
|
cachedProjectInfos.set(directory, projectInfo);
|
|
2955
2983
|
return projectInfo;
|
|
2956
2984
|
};
|
|
2985
|
+
const isAnalyzableProject = (project) => project.reactVersion !== null || project.preactVersion !== null;
|
|
2957
2986
|
const MAJOR_MINOR_PATTERN = /(\d{1,4})\.(\d{1,4})/;
|
|
2958
2987
|
const MAJOR_ONLY_PATTERN = /(\d{1,4})/;
|
|
2959
2988
|
const UPPER_BOUND_COMPARATOR_PATTERN = /<=?\s{0,8}\d{1,4}(?:\.\d{1,4}){0,2}(?:-[^\s,|]+)?/g;
|
|
@@ -3923,17 +3952,26 @@ const layerOtlp = Layer.unwrap(Effect.gen(function* () {
|
|
|
3923
3952
|
headers
|
|
3924
3953
|
}).pipe(Layer.provide(FetchHttpClient.layer));
|
|
3925
3954
|
}).pipe(Effect.orDie));
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3955
|
+
/**
|
|
3956
|
+
* Per-batch oxlint wall-clock budget. Reads from the env var on
|
|
3957
|
+
* startup so the eval harness can raise the budget under sandbox
|
|
3958
|
+
* microVMs without recompiling react-doctor. Tests override via
|
|
3959
|
+
* `Layer.succeed(OxlintSpawnTimeoutMs, ...)`.
|
|
3960
|
+
*/
|
|
3961
|
+
var OxlintSpawnTimeoutMs = class extends Context.Reference("react-doctor/OxlintSpawnTimeoutMs", { defaultValue: () => {
|
|
3929
3962
|
const raw = process.env["REACT_DOCTOR_OXLINT_SPAWN_TIMEOUT_MS"];
|
|
3930
3963
|
if (raw === void 0) return OXLINT_SPAWN_TIMEOUT_MS;
|
|
3931
3964
|
const parsed = Number(raw);
|
|
3932
3965
|
if (!Number.isFinite(parsed) || parsed <= 0) return OXLINT_SPAWN_TIMEOUT_MS;
|
|
3933
3966
|
return parsed;
|
|
3934
|
-
} });
|
|
3935
|
-
|
|
3936
|
-
|
|
3967
|
+
} }) {};
|
|
3968
|
+
/**
|
|
3969
|
+
* Hard cap on combined stdout+stderr bytes per oxlint batch. The
|
|
3970
|
+
* subprocess gets SIGKILL'd if it produces more; the recovery path
|
|
3971
|
+
* suggests narrowing the scan with --diff. Override via Layer in
|
|
3972
|
+
* tests that exercise the cap behavior.
|
|
3973
|
+
*/
|
|
3974
|
+
var OxlintOutputMaxBytes = class extends Context.Reference("react-doctor/OxlintOutputMaxBytes", { defaultValue: () => OXLINT_OUTPUT_MAX_BYTES }) {};
|
|
3937
3975
|
const DIAGNOSTIC_SURFACES = [
|
|
3938
3976
|
"cli",
|
|
3939
3977
|
"prComment",
|
|
@@ -4539,47 +4577,57 @@ const collectIgnorePatterns = (rootDirectory) => {
|
|
|
4539
4577
|
};
|
|
4540
4578
|
const TSCONFIG_FILENAMES$1 = ["tsconfig.json", "tsconfig.base.json"];
|
|
4541
4579
|
const DEAD_CODE_WORKER_SCRIPT = `
|
|
4542
|
-
const
|
|
4580
|
+
const inputChunks = [];
|
|
4581
|
+
process.stdin.on("data", (chunk) => inputChunks.push(chunk));
|
|
4582
|
+
process.stdin.on("end", () => {
|
|
4583
|
+
const workerInput = JSON.parse(Buffer.concat(inputChunks).toString("utf8"));
|
|
4543
4584
|
|
|
4544
|
-
const normalizeResult = (result) => ({
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
});
|
|
4585
|
+
const normalizeResult = (result) => ({
|
|
4586
|
+
unusedFiles: result.unusedFiles.map((unusedFile) => ({
|
|
4587
|
+
path: unusedFile.path,
|
|
4588
|
+
})),
|
|
4589
|
+
unusedExports: result.unusedExports.map((unusedExport) => ({
|
|
4590
|
+
path: unusedExport.path,
|
|
4591
|
+
name: unusedExport.name,
|
|
4592
|
+
line: unusedExport.line,
|
|
4593
|
+
column: unusedExport.column,
|
|
4594
|
+
isTypeOnly: unusedExport.isTypeOnly,
|
|
4595
|
+
})),
|
|
4596
|
+
unusedDependencies: result.unusedDependencies.map((unusedDependency) => ({
|
|
4597
|
+
name: unusedDependency.name,
|
|
4598
|
+
isDevDependency: unusedDependency.isDevDependency,
|
|
4599
|
+
})),
|
|
4600
|
+
circularDependencies: result.circularDependencies.map((cycle) => ({
|
|
4601
|
+
files: cycle.files,
|
|
4602
|
+
})),
|
|
4603
|
+
});
|
|
4563
4604
|
|
|
4564
|
-
const serializeError = (error) =>
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4605
|
+
const serializeError = (error) =>
|
|
4606
|
+
error instanceof Error
|
|
4607
|
+
? { name: error.name, message: error.message, stack: error.stack }
|
|
4608
|
+
: { message: String(error) };
|
|
4568
4609
|
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
}
|
|
4610
|
+
const emit = (message) => {
|
|
4611
|
+
process.stdout.write(JSON.stringify(message), () => process.exit(0));
|
|
4612
|
+
};
|
|
4613
|
+
|
|
4614
|
+
(async () => {
|
|
4615
|
+
try {
|
|
4616
|
+
const { analyze, defineConfig } = await import(workerInput.deslopJsModuleSpecifier);
|
|
4617
|
+
const config = {
|
|
4618
|
+
rootDir: workerInput.rootDirectory,
|
|
4619
|
+
...(workerInput.tsConfigPath ? { tsConfigPath: workerInput.tsConfigPath } : {}),
|
|
4620
|
+
...(workerInput.ignorePatterns.length > 0
|
|
4621
|
+
? { ignorePatterns: workerInput.ignorePatterns }
|
|
4622
|
+
: {}),
|
|
4623
|
+
};
|
|
4624
|
+
const result = await analyze(defineConfig(config));
|
|
4625
|
+
emit({ ok: true, result: normalizeResult(result) });
|
|
4626
|
+
} catch (error) {
|
|
4627
|
+
emit({ ok: false, error: serializeError(error) });
|
|
4628
|
+
}
|
|
4629
|
+
})();
|
|
4630
|
+
});
|
|
4583
4631
|
`;
|
|
4584
4632
|
const resolveTsConfigPath = (rootDirectory) => {
|
|
4585
4633
|
for (const filename of TSCONFIG_FILENAMES$1) {
|
|
@@ -4702,43 +4750,54 @@ const buildDeadCodeWorkerError = (workerError) => {
|
|
|
4702
4750
|
return error;
|
|
4703
4751
|
};
|
|
4704
4752
|
const createDeadCodeWorker = (input) => {
|
|
4705
|
-
const
|
|
4706
|
-
|
|
4707
|
-
|
|
4753
|
+
const child = spawn(process.execPath, ["-e", DEAD_CODE_WORKER_SCRIPT], {
|
|
4754
|
+
stdio: [
|
|
4755
|
+
"pipe",
|
|
4756
|
+
"pipe",
|
|
4757
|
+
"pipe"
|
|
4758
|
+
],
|
|
4759
|
+
windowsHide: true
|
|
4708
4760
|
});
|
|
4761
|
+
const stdoutChunks = [];
|
|
4762
|
+
const stderrChunks = [];
|
|
4763
|
+
child.stdout.on("data", (chunk) => stdoutChunks.push(chunk));
|
|
4764
|
+
child.stderr.on("data", (chunk) => stderrChunks.push(chunk));
|
|
4709
4765
|
let didSettle = false;
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4766
|
+
const result = new Promise((resolve, reject) => {
|
|
4767
|
+
const settle = (callback) => {
|
|
4768
|
+
if (didSettle) return;
|
|
4769
|
+
didSettle = true;
|
|
4770
|
+
callback();
|
|
4771
|
+
};
|
|
4772
|
+
child.once("error", (error) => {
|
|
4773
|
+
settle(() => reject(error));
|
|
4774
|
+
});
|
|
4775
|
+
child.once("close", (exitCode) => {
|
|
4776
|
+
const stdout = Buffer.concat(stdoutChunks).toString("utf8").trim();
|
|
4777
|
+
if (stdout.length === 0) {
|
|
4778
|
+
const stderr = Buffer.concat(stderrChunks).toString("utf8").trim();
|
|
4779
|
+
settle(() => reject(/* @__PURE__ */ new Error(`Dead-code worker exited with code ${exitCode ?? "null"}${stderr ? `: ${stderr}` : ""}.`)));
|
|
4780
|
+
return;
|
|
4781
|
+
}
|
|
4782
|
+
try {
|
|
4783
|
+
const parsedMessage = parseDeadCodeWorkerMessage(JSON.parse(stdout));
|
|
4784
|
+
if (parsedMessage.ok) {
|
|
4785
|
+
settle(() => resolve(parsedMessage.result));
|
|
4786
|
+
return;
|
|
4728
4787
|
}
|
|
4729
|
-
|
|
4730
|
-
|
|
4788
|
+
settle(() => reject(buildDeadCodeWorkerError(parsedMessage.error)));
|
|
4789
|
+
} catch (error) {
|
|
4731
4790
|
settle(() => reject(error));
|
|
4732
|
-
}
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4791
|
+
}
|
|
4792
|
+
});
|
|
4793
|
+
});
|
|
4794
|
+
child.stdin.on("error", () => {});
|
|
4795
|
+
child.stdin.end(JSON.stringify(input));
|
|
4796
|
+
return {
|
|
4797
|
+
result,
|
|
4738
4798
|
terminate: () => {
|
|
4739
4799
|
didSettle = true;
|
|
4740
|
-
|
|
4741
|
-
return worker.terminate();
|
|
4800
|
+
child.kill("SIGKILL");
|
|
4742
4801
|
}
|
|
4743
4802
|
};
|
|
4744
4803
|
};
|
|
@@ -4980,8 +5039,15 @@ var Git = class Git extends Context.Service()("react-doctor/Git") {
|
|
|
4980
5039
|
env: input.env,
|
|
4981
5040
|
extendEnv: true
|
|
4982
5041
|
}));
|
|
5042
|
+
const maxStdoutBytes = input.maxStdoutBytes;
|
|
5043
|
+
const stdoutByteCount = yield* Ref.make(0);
|
|
5044
|
+
const stdoutStream = maxStdoutBytes === void 0 ? handle.stdout : handle.stdout.pipe(Stream.tap((chunk) => Ref.updateAndGet(stdoutByteCount, (total) => total + chunk.length).pipe(Effect.flatMap((total) => total > maxStdoutBytes ? Effect.fail(new ReactDoctorError({ reason: new GitInvocationFailed({
|
|
5045
|
+
args: [...input.args],
|
|
5046
|
+
directory: input.directory,
|
|
5047
|
+
cause: /* @__PURE__ */ new Error(`git stdout exceeded ${maxStdoutBytes} bytes`)
|
|
5048
|
+
}) })) : Effect.void))));
|
|
4983
5049
|
const [stdout, stderr, status] = yield* Effect.all([
|
|
4984
|
-
Stream.mkString(Stream.decodeText(
|
|
5050
|
+
Stream.mkString(Stream.decodeText(stdoutStream)),
|
|
4985
5051
|
Stream.mkString(Stream.decodeText(handle.stderr)),
|
|
4986
5052
|
handle.exitCode
|
|
4987
5053
|
], { concurrency: 3 });
|
|
@@ -5143,7 +5209,12 @@ var Git = class Git extends Context.Service()("react-doctor/Git") {
|
|
|
5143
5209
|
if (result.status !== 0) return [];
|
|
5144
5210
|
return splitNullSeparated(result.stdout);
|
|
5145
5211
|
})),
|
|
5146
|
-
showStagedContent: (directory, relativePath) =>
|
|
5212
|
+
showStagedContent: (directory, relativePath, options) => runCommand({
|
|
5213
|
+
command: "git",
|
|
5214
|
+
args: ["show", `:${relativePath}`],
|
|
5215
|
+
directory,
|
|
5216
|
+
maxStdoutBytes: options?.maxBufferBytes
|
|
5217
|
+
}).pipe(Effect.map((result) => result.status === 0 ? result.stdout : null)),
|
|
5147
5218
|
grep: (input) => Effect.gen(function* () {
|
|
5148
5219
|
const args = ["grep"];
|
|
5149
5220
|
if (input.listMatchingFiles ?? true) args.push("-l");
|
|
@@ -5151,7 +5222,12 @@ var Git = class Git extends Context.Service()("react-doctor/Git") {
|
|
|
5151
5222
|
if (input.extendedRegexp ?? false) args.push("-E");
|
|
5152
5223
|
args.push(input.pattern);
|
|
5153
5224
|
if (input.includePaths && input.includePaths.length > 0) args.push("--", ...input.includePaths);
|
|
5154
|
-
const result = yield*
|
|
5225
|
+
const result = yield* runCommand({
|
|
5226
|
+
command: "git",
|
|
5227
|
+
args,
|
|
5228
|
+
directory: input.directory,
|
|
5229
|
+
maxStdoutBytes: input.maxBufferBytes
|
|
5230
|
+
});
|
|
5155
5231
|
if (result.status === 128) return null;
|
|
5156
5232
|
return {
|
|
5157
5233
|
status: result.status,
|
|
@@ -5399,7 +5475,8 @@ const buildCapabilities = (project) => {
|
|
|
5399
5475
|
if (project.framework === "expo" || project.framework === "react-native" || project.hasReactNativeWorkspace) capabilities.add("react-native");
|
|
5400
5476
|
const reactMajor = project.reactMajorVersion;
|
|
5401
5477
|
if (reactMajor !== null) {
|
|
5402
|
-
|
|
5478
|
+
const cappedReactMajor = Math.min(reactMajor, 30);
|
|
5479
|
+
for (let major = 17; major <= cappedReactMajor; major++) capabilities.add(`react:${major}`);
|
|
5403
5480
|
if (reactMajor >= 19) {
|
|
5404
5481
|
if (isReactAtLeast(parseReactMajorMinor(project.reactVersion), {
|
|
5405
5482
|
major: 19,
|
|
@@ -5417,8 +5494,13 @@ const buildCapabilities = (project) => {
|
|
|
5417
5494
|
if (project.hasReactCompiler) capabilities.add("react-compiler");
|
|
5418
5495
|
if (project.hasTanStackQuery) capabilities.add("tanstack-query");
|
|
5419
5496
|
if (project.hasTypeScript) capabilities.add("typescript");
|
|
5420
|
-
if (project.
|
|
5497
|
+
if (project.preactVersion !== null) {
|
|
5421
5498
|
capabilities.add("preact");
|
|
5499
|
+
const preactMajor = project.preactMajorVersion;
|
|
5500
|
+
if (preactMajor !== null) {
|
|
5501
|
+
const cappedPreactMajor = Math.min(preactMajor, 20);
|
|
5502
|
+
for (let major = 10; major <= cappedPreactMajor; major++) capabilities.add(`preact:${major}`);
|
|
5503
|
+
}
|
|
5422
5504
|
if (project.reactVersion === null) capabilities.add("pure-preact");
|
|
5423
5505
|
}
|
|
5424
5506
|
return capabilities;
|
|
@@ -5675,6 +5757,13 @@ const buildNoSecretsRecommendation = (project, fallbackRecommendation) => {
|
|
|
5675
5757
|
if (!publicEnvPrefix) return fallbackRecommendation;
|
|
5676
5758
|
return `Move secrets to server-only code. In ${formatFrameworkName(project.framework)}, only \`${publicEnvPrefix}\` env vars are exposed to the browser, and they must not contain secrets`;
|
|
5677
5759
|
};
|
|
5760
|
+
const REANIMATED_SHARED_VALUE_HINT = "If this is a Reanimated shared value, prefer its React Compiler-compatible `.get()` / `.set()` accessors over `.value` — https://docs.swmansion.com/react-native-reanimated/docs/core/useSharedValue/#react-compiler-support";
|
|
5761
|
+
const appendReanimatedSharedValueHint = (help, rule, project) => {
|
|
5762
|
+
if (rule !== "immutability") return help;
|
|
5763
|
+
if (!project.hasReanimated) return help;
|
|
5764
|
+
if (!help) return REANIMATED_SHARED_VALUE_HINT;
|
|
5765
|
+
return `${help}\n\n${REANIMATED_SHARED_VALUE_HINT}`;
|
|
5766
|
+
};
|
|
5678
5767
|
const REACT_MODULE_SOURCE = "react";
|
|
5679
5768
|
const REQUIRE_IDENTIFIER = "require";
|
|
5680
5769
|
const USE_IDENTIFIER = "use";
|
|
@@ -5998,7 +6087,7 @@ const getRuleCategory = (ruleName) => reactDoctorPlugin.rules[ruleName]?.categor
|
|
|
5998
6087
|
const cleanDiagnosticMessage = (message, help, plugin, rule, project) => {
|
|
5999
6088
|
if (plugin === "react-hooks-js") return {
|
|
6000
6089
|
message: REACT_COMPILER_MESSAGE,
|
|
6001
|
-
help: message.replace(FILEPATH_WITH_LOCATION_PATTERN, "").trim() || help
|
|
6090
|
+
help: appendReanimatedSharedValueHint(message.replace(FILEPATH_WITH_LOCATION_PATTERN, "").trim() || help, rule, project)
|
|
6002
6091
|
};
|
|
6003
6092
|
return {
|
|
6004
6093
|
message: message.replace(FILEPATH_WITH_LOCATION_PATTERN, "").trim() || message,
|
|
@@ -6067,13 +6156,6 @@ const SANITIZED_ENV = (() => {
|
|
|
6067
6156
|
}
|
|
6068
6157
|
return sanitized;
|
|
6069
6158
|
})();
|
|
6070
|
-
const OXLINT_SPAWN_TIMEOUT_MS$1 = (() => {
|
|
6071
|
-
const raw = process.env["REACT_DOCTOR_OXLINT_SPAWN_TIMEOUT_MS"];
|
|
6072
|
-
if (raw === void 0) return OXLINT_SPAWN_TIMEOUT_MS;
|
|
6073
|
-
const parsed = Number(raw);
|
|
6074
|
-
if (!Number.isFinite(parsed) || parsed <= 0) return OXLINT_SPAWN_TIMEOUT_MS;
|
|
6075
|
-
return parsed;
|
|
6076
|
-
})();
|
|
6077
6159
|
/**
|
|
6078
6160
|
* Spawn one oxlint subprocess with hard ceilings on wall time and
|
|
6079
6161
|
* output size. Returns stdout on success; raises a tagged
|
|
@@ -6090,7 +6172,7 @@ const OXLINT_SPAWN_TIMEOUT_MS$1 = (() => {
|
|
|
6090
6172
|
* The first three are splittable (the caller's binary-split retry
|
|
6091
6173
|
* shrinks the batch and re-spawns); the fourth isn't.
|
|
6092
6174
|
*/
|
|
6093
|
-
const spawnOxlint = (args, rootDirectory, nodeBinaryPath) => new Promise((resolve, reject) => {
|
|
6175
|
+
const spawnOxlint = (args, rootDirectory, nodeBinaryPath, spawnTimeoutMs = OXLINT_SPAWN_TIMEOUT_MS, outputMaxBytes = OXLINT_OUTPUT_MAX_BYTES) => new Promise((resolve, reject) => {
|
|
6094
6176
|
const child = spawn(nodeBinaryPath, args, {
|
|
6095
6177
|
cwd: rootDirectory,
|
|
6096
6178
|
env: SANITIZED_ENV
|
|
@@ -6099,9 +6181,9 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath) => new Promise((resolv
|
|
|
6099
6181
|
child.kill("SIGKILL");
|
|
6100
6182
|
reject(new ReactDoctorError({ reason: new OxlintBatchExceeded({
|
|
6101
6183
|
kind: "timeout",
|
|
6102
|
-
detail: `${
|
|
6184
|
+
detail: `${spawnTimeoutMs / 1e3}s budget exceeded`
|
|
6103
6185
|
}) }));
|
|
6104
|
-
},
|
|
6186
|
+
}, spawnTimeoutMs);
|
|
6105
6187
|
timeoutHandle.unref?.();
|
|
6106
6188
|
const stdoutBuffers = [];
|
|
6107
6189
|
const stderrBuffers = [];
|
|
@@ -6111,7 +6193,7 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath) => new Promise((resolv
|
|
|
6111
6193
|
const killIfTooLarge = (incomingBytes, isStdout) => {
|
|
6112
6194
|
if (isStdout) stdoutByteCount += incomingBytes;
|
|
6113
6195
|
else stderrByteCount += incomingBytes;
|
|
6114
|
-
if (stdoutByteCount + stderrByteCount >
|
|
6196
|
+
if (stdoutByteCount + stderrByteCount > outputMaxBytes && !didKillForSize) {
|
|
6115
6197
|
didKillForSize = true;
|
|
6116
6198
|
child.kill("SIGKILL");
|
|
6117
6199
|
return true;
|
|
@@ -6137,7 +6219,7 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath) => new Promise((resolv
|
|
|
6137
6219
|
if (didKillForSize) {
|
|
6138
6220
|
reject(new ReactDoctorError({ reason: new OxlintBatchExceeded({
|
|
6139
6221
|
kind: "output-too-large",
|
|
6140
|
-
detail: `exceeded ${
|
|
6222
|
+
detail: `exceeded ${outputMaxBytes} bytes — scan a smaller subset with --diff or --staged`
|
|
6141
6223
|
}) }));
|
|
6142
6224
|
return;
|
|
6143
6225
|
}
|
|
@@ -6178,7 +6260,7 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath) => new Promise((resolv
|
|
|
6178
6260
|
* with a slimmer config in that case.
|
|
6179
6261
|
*/
|
|
6180
6262
|
const spawnLintBatches = async (input) => {
|
|
6181
|
-
const { baseArgs, fileBatches, rootDirectory, nodeBinaryPath, project, onPartialFailure, onFileProgress } = input;
|
|
6263
|
+
const { baseArgs, fileBatches, rootDirectory, nodeBinaryPath, project, onPartialFailure, onFileProgress, spawnTimeoutMs, outputMaxBytes } = input;
|
|
6182
6264
|
const totalFileCount = fileBatches.reduce((sum, batch) => sum + batch.length, 0);
|
|
6183
6265
|
const allDiagnostics = [];
|
|
6184
6266
|
const droppedFiles = [];
|
|
@@ -6186,7 +6268,7 @@ const spawnLintBatches = async (input) => {
|
|
|
6186
6268
|
const spawnLintBatch = async (batch) => {
|
|
6187
6269
|
const batchArgs = [...baseArgs, ...batch];
|
|
6188
6270
|
try {
|
|
6189
|
-
return parseOxlintOutput(await spawnOxlint(batchArgs, rootDirectory, nodeBinaryPath), project, rootDirectory);
|
|
6271
|
+
return parseOxlintOutput(await spawnOxlint(batchArgs, rootDirectory, nodeBinaryPath, spawnTimeoutMs, outputMaxBytes), project, rootDirectory);
|
|
6190
6272
|
} catch (error) {
|
|
6191
6273
|
if (!isSplittableReactDoctorError(error)) throw error;
|
|
6192
6274
|
if (batch.length <= 1) {
|
|
@@ -6289,13 +6371,11 @@ const writeOxlintConfig = (configPath, configToWrite) => {
|
|
|
6289
6371
|
* 6. always restore disable directives + clean up the temp dir
|
|
6290
6372
|
*/
|
|
6291
6373
|
const runOxlint = async (options) => {
|
|
6292
|
-
const { rootDirectory, project, includePaths, nodeBinaryPath = process.execPath, customRulesOnly = false, respectInlineDisables = true, adoptExistingLintConfig = true, ignoredTags = /* @__PURE__ */ new Set(), userConfig, configSourceDirectory = rootDirectory, onPartialFailure } = options;
|
|
6374
|
+
const { rootDirectory, project, includePaths, nodeBinaryPath = process.execPath, customRulesOnly = false, respectInlineDisables = true, adoptExistingLintConfig = true, ignoredTags = /* @__PURE__ */ new Set(), userConfig, configSourceDirectory = rootDirectory, onPartialFailure, spawnTimeoutMs, outputMaxBytes } = options;
|
|
6293
6375
|
const serverAuthFunctionNames = Array.isArray(userConfig?.serverAuthFunctionNames) ? userConfig.serverAuthFunctionNames.filter((entry) => typeof entry === "string" && entry.length > 0) : void 0;
|
|
6294
6376
|
const severityControls = buildRuleSeverityControls(userConfig);
|
|
6295
6377
|
validateRuleRegistration();
|
|
6296
6378
|
if (includePaths !== void 0 && includePaths.length === 0) return [];
|
|
6297
|
-
const configDirectory = fs.mkdtempSync(path.join(os.tmpdir(), "react-doctor-oxlintrc-"));
|
|
6298
|
-
const configPath = path.join(configDirectory, "oxlintrc.json");
|
|
6299
6379
|
const pluginPath = resolvePluginPath();
|
|
6300
6380
|
const extendsPaths = (adoptExistingLintConfig && !customRulesOnly ? detectUserLintConfigPaths(rootDirectory) : []).filter(canOxlintExtendConfig);
|
|
6301
6381
|
const userPlugins = resolveUserPlugins(userConfig?.plugins, configSourceDirectory);
|
|
@@ -6310,6 +6390,8 @@ const runOxlint = async (options) => {
|
|
|
6310
6390
|
userPlugins
|
|
6311
6391
|
});
|
|
6312
6392
|
const restoreDisableDirectives = respectInlineDisables ? () => {} : await neutralizeDisableDirectives(rootDirectory, includePaths);
|
|
6393
|
+
const configDirectory = fs.mkdtempSync(path.join(os.tmpdir(), "react-doctor-oxlintrc-"));
|
|
6394
|
+
const configPath = path.join(configDirectory, "oxlintrc.json");
|
|
6313
6395
|
try {
|
|
6314
6396
|
const baseArgs = [
|
|
6315
6397
|
resolveOxlintBinary(),
|
|
@@ -6336,7 +6418,9 @@ const runOxlint = async (options) => {
|
|
|
6336
6418
|
nodeBinaryPath,
|
|
6337
6419
|
project,
|
|
6338
6420
|
onPartialFailure,
|
|
6339
|
-
onFileProgress: options.onFileProgress
|
|
6421
|
+
onFileProgress: options.onFileProgress,
|
|
6422
|
+
spawnTimeoutMs,
|
|
6423
|
+
outputMaxBytes
|
|
6340
6424
|
});
|
|
6341
6425
|
writeOxlintConfig(configPath, buildConfig(extendsPaths));
|
|
6342
6426
|
try {
|
|
@@ -6402,6 +6486,8 @@ var Linter = class Linter extends Context.Service()("react-doctor/Linter") {
|
|
|
6402
6486
|
*/
|
|
6403
6487
|
static layerOxlint = Layer.succeed(Linter, Linter.of({ run: (input) => Stream.unwrap(Effect.fn("Linter.run")(function* () {
|
|
6404
6488
|
const partialFailures = yield* LintPartialFailures;
|
|
6489
|
+
const spawnTimeoutMs = yield* OxlintSpawnTimeoutMs;
|
|
6490
|
+
const outputMaxBytes = yield* OxlintOutputMaxBytes;
|
|
6405
6491
|
const collectedFailures = [];
|
|
6406
6492
|
const diagnostics = yield* Effect.tryPromise({
|
|
6407
6493
|
try: () => runOxlint({
|
|
@@ -6418,7 +6504,9 @@ var Linter = class Linter extends Context.Service()("react-doctor/Linter") {
|
|
|
6418
6504
|
onPartialFailure: (reason) => {
|
|
6419
6505
|
collectedFailures.push(reason);
|
|
6420
6506
|
},
|
|
6421
|
-
onFileProgress: input.onFileProgress
|
|
6507
|
+
onFileProgress: input.onFileProgress,
|
|
6508
|
+
spawnTimeoutMs,
|
|
6509
|
+
outputMaxBytes
|
|
6422
6510
|
}),
|
|
6423
6511
|
catch: ensureReactDoctorError
|
|
6424
6512
|
});
|
|
@@ -6716,7 +6804,7 @@ const runInspect = (input, hooks = {}) => Effect.gen(function* () {
|
|
|
6716
6804
|
const resolvedConfig = yield* configService.resolve(input.directory);
|
|
6717
6805
|
const scanDirectory = resolvedConfig.resolvedDirectory;
|
|
6718
6806
|
const project = yield* projectService.discover(scanDirectory);
|
|
6719
|
-
if (project
|
|
6807
|
+
if (!isAnalyzableProject(project)) return yield* new ReactDoctorError({ reason: new NoReactDependency({ directory: scanDirectory }) });
|
|
6720
6808
|
const [repo, sha, defaultBranch] = yield* Effect.all([
|
|
6721
6809
|
gitService.githubRepo(scanDirectory).pipe(Effect.orElseSucceed(() => null)),
|
|
6722
6810
|
gitService.headSha(scanDirectory).pipe(Effect.orElseSucceed(() => null)),
|
|
@@ -6744,7 +6832,8 @@ const runInspect = (input, hooks = {}) => Effect.gen(function* () {
|
|
|
6744
6832
|
const lintFailure = yield* Ref.make({
|
|
6745
6833
|
didFail: false,
|
|
6746
6834
|
reason: null,
|
|
6747
|
-
reasonTag: null
|
|
6835
|
+
reasonTag: null,
|
|
6836
|
+
reasonKind: null
|
|
6748
6837
|
});
|
|
6749
6838
|
const deadCodeFailure = yield* Ref.make({
|
|
6750
6839
|
didFail: false,
|
|
@@ -6766,13 +6855,14 @@ const runInspect = (input, hooks = {}) => Effect.gen(function* () {
|
|
|
6766
6855
|
configSourceDirectory: resolvedConfig.configSourceDirectory ?? void 0,
|
|
6767
6856
|
onFileProgress: (scannedFileCount, totalFileCount) => {
|
|
6768
6857
|
lastReportedTotalFileCount = totalFileCount;
|
|
6769
|
-
Effect.runSync(scanProgress.update(`Scanning (${scannedFileCount}/${totalFileCount})...`));
|
|
6858
|
+
Effect.runSync(scanProgress.update(`Scanning files (${scannedFileCount}/${totalFileCount})...`));
|
|
6770
6859
|
}
|
|
6771
6860
|
}).pipe(Stream.catchTag("ReactDoctorError", (error) => Stream.unwrap(Effect.gen(function* () {
|
|
6772
6861
|
yield* Ref.set(lintFailure, {
|
|
6773
6862
|
didFail: true,
|
|
6774
6863
|
reason: error.message,
|
|
6775
|
-
reasonTag: error.reason._tag
|
|
6864
|
+
reasonTag: error.reason._tag,
|
|
6865
|
+
reasonKind: error.reason._tag === "OxlintUnavailable" ? error.reason.kind : null
|
|
6776
6866
|
});
|
|
6777
6867
|
return Stream.empty;
|
|
6778
6868
|
}))));
|
|
@@ -6832,6 +6922,7 @@ const runInspect = (input, hooks = {}) => Effect.gen(function* () {
|
|
|
6832
6922
|
didLintFail: lintFailureState.didFail,
|
|
6833
6923
|
lintFailureReason: lintFailureState.reason,
|
|
6834
6924
|
lintFailureReasonTag: lintFailureState.reasonTag,
|
|
6925
|
+
lintFailureReasonKind: lintFailureState.reasonKind,
|
|
6835
6926
|
lintPartialFailures,
|
|
6836
6927
|
didDeadCodeFail: deadCodeFailureState.didFail,
|
|
6837
6928
|
deadCodeFailureReason: deadCodeFailureState.reason
|
|
@@ -7318,4 +7409,4 @@ const cliLogger = {
|
|
|
7318
7409
|
//#endregion
|
|
7319
7410
|
export { isReactDoctorError as A, filterSourceFiles as C, groupBy as D, getDiffInfo as E, runInspect as F, toRelativePath as I, listWorkspacePackages as M, resolveScanTarget as N, highlighter as O, restoreLegacyThrow as P, filterDiagnosticsForSurface as S, formatReactDoctorError as T, Score as _, DeadCode as a, buildJsonReportError as b, LintPartialFailures as c, OXLINT_NODE_REQUIREMENT as d, Progress as f, SKILL_NAME as g, SHARE_BASE_URL as h, Config as i, layerOtlp as j, isMonorepoRoot as k, Linter as l, Reporter as m, cli_logger_exports as n, Files as o, Project as p, CANONICAL_GITHUB_URL as r, Git as s, cliLogger as t, NodeResolver as u, StagedFiles as v, formatErrorChain as w, discoverReactSubprojects as x, buildJsonReport as y };
|
|
7320
7411
|
|
|
7321
|
-
//# sourceMappingURL=cli-logger-
|
|
7412
|
+
//# sourceMappingURL=cli-logger-Df45H6Lw.js.map
|