react-doctor 0.0.29 → 0.0.30

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/index.js CHANGED
@@ -12,11 +12,18 @@ import { fileURLToPath } from "node:url";
12
12
  const SOURCE_FILE_PATTERN = /\.(tsx?|jsx?)$/;
13
13
  const JSX_FILE_PATTERN = /\.(tsx|jsx)$/;
14
14
  const ERROR_PREVIEW_LENGTH_CHARS = 200;
15
+ const PERFECT_SCORE = 100;
16
+ const SCORE_GOOD_THRESHOLD = 75;
17
+ const SCORE_OK_THRESHOLD = 50;
15
18
  const SCORE_API_URL = "https://www.react.doctor/api/score";
16
19
  const FETCH_TIMEOUT_MS = 1e4;
17
20
  const GIT_LS_FILES_MAX_BUFFER_BYTES = 50 * 1024 * 1024;
18
21
  const SPAWN_ARGS_MAX_LENGTH_CHARS = 24e3;
19
22
  const DEFAULT_BRANCH_CANDIDATES = ["main", "master"];
23
+ const ERROR_RULE_PENALTY = 1.5;
24
+ const WARNING_RULE_PENALTY = .75;
25
+ const ERROR_ESTIMATED_FIX_RATE = .85;
26
+ const WARNING_ESTIMATED_FIX_RATE = .8;
20
27
  const MAX_KNIP_RETRIES = 5;
21
28
  const AMI_WEBSITE_URL = "https://ami.dev";
22
29
  const AMI_INSTALL_URL = `${AMI_WEBSITE_URL}/install.sh`;
@@ -71,6 +78,46 @@ const proxyFetch = async (url, init) => {
71
78
 
72
79
  //#endregion
73
80
  //#region src/utils/calculate-score.ts
81
+ const getScoreLabel = (score) => {
82
+ if (score >= SCORE_GOOD_THRESHOLD) return "Great";
83
+ if (score >= SCORE_OK_THRESHOLD) return "Needs work";
84
+ return "Critical";
85
+ };
86
+ const countUniqueRules = (diagnostics) => {
87
+ const errorRules = /* @__PURE__ */ new Set();
88
+ const warningRules = /* @__PURE__ */ new Set();
89
+ for (const diagnostic of diagnostics) {
90
+ const ruleKey = `${diagnostic.plugin}/${diagnostic.rule}`;
91
+ if (diagnostic.severity === "error") errorRules.add(ruleKey);
92
+ else warningRules.add(ruleKey);
93
+ }
94
+ return {
95
+ errorRuleCount: errorRules.size,
96
+ warningRuleCount: warningRules.size
97
+ };
98
+ };
99
+ const scoreFromRuleCounts = (errorRuleCount, warningRuleCount) => {
100
+ const penalty = errorRuleCount * ERROR_RULE_PENALTY + warningRuleCount * WARNING_RULE_PENALTY;
101
+ return Math.max(0, Math.round(PERFECT_SCORE - penalty));
102
+ };
103
+ const estimateScoreLocally = (diagnostics) => {
104
+ const { errorRuleCount, warningRuleCount } = countUniqueRules(diagnostics);
105
+ const currentScore = scoreFromRuleCounts(errorRuleCount, warningRuleCount);
106
+ const estimatedScore = scoreFromRuleCounts(Math.round(errorRuleCount * (1 - ERROR_ESTIMATED_FIX_RATE)), Math.round(warningRuleCount * (1 - WARNING_ESTIMATED_FIX_RATE)));
107
+ return {
108
+ currentScore,
109
+ currentLabel: getScoreLabel(currentScore),
110
+ estimatedScore,
111
+ estimatedLabel: getScoreLabel(estimatedScore)
112
+ };
113
+ };
114
+ const calculateScoreLocally = (diagnostics) => {
115
+ const { currentScore, currentLabel } = estimateScoreLocally(diagnostics);
116
+ return {
117
+ score: currentScore,
118
+ label: currentLabel
119
+ };
120
+ };
74
121
  const calculateScore = async (diagnostics) => {
75
122
  try {
76
123
  const response = await proxyFetch(SCORE_API_URL, {
@@ -78,10 +125,10 @@ const calculateScore = async (diagnostics) => {
78
125
  headers: { "Content-Type": "application/json" },
79
126
  body: JSON.stringify({ diagnostics })
80
127
  });
81
- if (!response.ok) return null;
128
+ if (!response.ok) return calculateScoreLocally(diagnostics);
82
129
  return await response.json();
83
130
  } catch {
84
- return null;
131
+ return calculateScoreLocally(diagnostics);
85
132
  }
86
133
  };
87
134
 
@@ -89,13 +136,33 @@ const calculateScore = async (diagnostics) => {
89
136
  //#region src/plugin/constants.ts
90
137
  const MOTION_LIBRARY_PACKAGES = new Set(["framer-motion", "motion"]);
91
138
 
139
+ //#endregion
140
+ //#region src/utils/is-file.ts
141
+ const isFile = (filePath) => {
142
+ try {
143
+ return fs.statSync(filePath).isFile();
144
+ } catch {
145
+ return false;
146
+ }
147
+ };
148
+
92
149
  //#endregion
93
150
  //#region src/utils/read-package-json.ts
94
- const readPackageJson = (packageJsonPath) => JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
151
+ const readPackageJson = (packageJsonPath) => {
152
+ try {
153
+ return JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
154
+ } catch (error) {
155
+ if (error instanceof Error && "code" in error) {
156
+ const { code } = error;
157
+ if (code === "EISDIR" || code === "EACCES") return {};
158
+ }
159
+ throw error;
160
+ }
161
+ };
95
162
 
96
163
  //#endregion
97
164
  //#region src/utils/check-reduced-motion.ts
98
- const REDUCED_MOTION_GREP_PATTERN = "prefers-reduced-motion|useReducedMotion";
165
+ const REDUCED_MOTION_GREP_PATTERN = "prefers-reduced-motion|useReducedMotion|MotionConfig|reducedMotion";
99
166
  const REDUCED_MOTION_FILE_GLOBS = "\"*.ts\" \"*.tsx\" \"*.js\" \"*.jsx\" \"*.css\" \"*.scss\"";
100
167
  const MISSING_REDUCED_MOTION_DIAGNOSTIC = {
101
168
  filePath: "package.json",
@@ -111,7 +178,7 @@ const MISSING_REDUCED_MOTION_DIAGNOSTIC = {
111
178
  };
112
179
  const checkReducedMotion = (rootDirectory) => {
113
180
  const packageJsonPath = path.join(rootDirectory, "package.json");
114
- if (!fs.existsSync(packageJsonPath)) return [];
181
+ if (!isFile(packageJsonPath)) return [];
115
182
  let hasMotionLibrary = false;
116
183
  try {
117
184
  const packageJson = readPackageJson(packageJsonPath);
@@ -139,7 +206,7 @@ const checkReducedMotion = (rootDirectory) => {
139
206
  //#region src/utils/match-glob-pattern.ts
140
207
  const REGEX_SPECIAL_CHARACTERS = /[.+^${}()|[\]\\]/g;
141
208
  const compileGlobPattern = (pattern) => {
142
- const normalizedPattern = pattern.replace(/\\/g, "/");
209
+ const normalizedPattern = pattern.replace(/\\/g, "/").replace(/^\//, "");
143
210
  let regexSource = "^";
144
211
  let characterIndex = 0;
145
212
  while (characterIndex < normalizedPattern.length) if (normalizedPattern[characterIndex] === "*" && normalizedPattern[characterIndex + 1] === "*") if (normalizedPattern[characterIndex + 2] === "/") {
@@ -177,26 +244,67 @@ const filterIgnoredDiagnostics = (diagnostics, config) => {
177
244
  return true;
178
245
  });
179
246
  };
247
+ const DISABLE_NEXT_LINE_PATTERN = /\/\/\s*react-doctor-disable-next-line\b(?:\s+(.+))?/;
248
+ const DISABLE_LINE_PATTERN = /\/\/\s*react-doctor-disable-line\b(?:\s+(.+))?/;
249
+ const isRuleSuppressed = (commentRules, ruleId) => {
250
+ if (!commentRules?.trim()) return true;
251
+ return commentRules.split(/[,\s]+/).some((rule) => rule.trim() === ruleId);
252
+ };
253
+ const filterInlineSuppressions = (diagnostics, rootDirectory) => {
254
+ const fileLineCache = /* @__PURE__ */ new Map();
255
+ const getFileLines = (filePath) => {
256
+ const cached = fileLineCache.get(filePath);
257
+ if (cached !== void 0) return cached;
258
+ const absolutePath = path.isAbsolute(filePath) ? filePath : path.join(rootDirectory, filePath);
259
+ try {
260
+ const lines = fs.readFileSync(absolutePath, "utf-8").split("\n");
261
+ fileLineCache.set(filePath, lines);
262
+ return lines;
263
+ } catch {
264
+ fileLineCache.set(filePath, null);
265
+ return null;
266
+ }
267
+ };
268
+ return diagnostics.filter((diagnostic) => {
269
+ if (diagnostic.line <= 0) return true;
270
+ const lines = getFileLines(diagnostic.filePath);
271
+ if (!lines) return true;
272
+ const ruleId = `${diagnostic.plugin}/${diagnostic.rule}`;
273
+ const currentLine = lines[diagnostic.line - 1];
274
+ if (currentLine) {
275
+ const lineMatch = currentLine.match(DISABLE_LINE_PATTERN);
276
+ if (lineMatch && isRuleSuppressed(lineMatch[1], ruleId)) return false;
277
+ }
278
+ if (diagnostic.line >= 2) {
279
+ const prevLine = lines[diagnostic.line - 2];
280
+ if (prevLine) {
281
+ const nextLineMatch = prevLine.match(DISABLE_NEXT_LINE_PATTERN);
282
+ if (nextLineMatch && isRuleSuppressed(nextLineMatch[1], ruleId)) return false;
283
+ }
284
+ }
285
+ return true;
286
+ });
287
+ };
180
288
 
181
289
  //#endregion
182
290
  //#region src/utils/combine-diagnostics.ts
183
291
  const computeJsxIncludePaths = (includePaths) => includePaths.length > 0 ? includePaths.filter((filePath) => JSX_FILE_PATTERN.test(filePath)) : void 0;
184
292
  const combineDiagnostics = (lintDiagnostics, deadCodeDiagnostics, directory, isDiffMode, userConfig) => {
185
- const allDiagnostics = [
293
+ const merged = [
186
294
  ...lintDiagnostics,
187
295
  ...deadCodeDiagnostics,
188
296
  ...isDiffMode ? [] : checkReducedMotion(directory)
189
297
  ];
190
- return userConfig ? filterIgnoredDiagnostics(allDiagnostics, userConfig) : allDiagnostics;
298
+ return filterInlineSuppressions(userConfig ? filterIgnoredDiagnostics(merged, userConfig) : merged, directory);
191
299
  };
192
300
 
193
301
  //#endregion
194
302
  //#region src/utils/find-monorepo-root.ts
195
303
  const isMonorepoRoot = (directory) => {
196
- if (fs.existsSync(path.join(directory, "pnpm-workspace.yaml"))) return true;
197
- if (fs.existsSync(path.join(directory, "nx.json"))) return true;
304
+ if (isFile(path.join(directory, "pnpm-workspace.yaml"))) return true;
305
+ if (isFile(path.join(directory, "nx.json"))) return true;
198
306
  const packageJsonPath = path.join(directory, "package.json");
199
- if (!fs.existsSync(packageJsonPath)) return false;
307
+ if (!isFile(packageJsonPath)) return false;
200
308
  const packageJson = readPackageJson(packageJsonPath);
201
309
  return Array.isArray(packageJson.workspaces) || Boolean(packageJson.workspaces?.packages);
202
310
  };
@@ -209,6 +317,10 @@ const findMonorepoRoot = (startDirectory) => {
209
317
  return null;
210
318
  };
211
319
 
320
+ //#endregion
321
+ //#region src/utils/is-plain-object.ts
322
+ const isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
323
+
212
324
  //#endregion
213
325
  //#region src/utils/discover-project.ts
214
326
  const REACT_COMPILER_PACKAGES = new Set([
@@ -297,16 +409,37 @@ const detectFramework = (dependencies) => {
297
409
  for (const [packageName, frameworkName] of Object.entries(FRAMEWORK_PACKAGES)) if (dependencies[packageName]) return frameworkName;
298
410
  return "unknown";
299
411
  };
412
+ const isCatalogReference = (version) => version.startsWith("catalog:");
413
+ const resolveVersionFromCatalog = (catalog, packageName) => {
414
+ const version = catalog[packageName];
415
+ if (typeof version === "string" && !isCatalogReference(version)) return version;
416
+ return null;
417
+ };
418
+ const resolveCatalogVersion = (packageJson, packageName) => {
419
+ const raw = packageJson;
420
+ if (isPlainObject(raw.catalog)) {
421
+ const version = resolveVersionFromCatalog(raw.catalog, packageName);
422
+ if (version) return version;
423
+ }
424
+ if (isPlainObject(raw.catalogs)) {
425
+ for (const catalogEntries of Object.values(raw.catalogs)) if (isPlainObject(catalogEntries)) {
426
+ const version = resolveVersionFromCatalog(catalogEntries, packageName);
427
+ if (version) return version;
428
+ }
429
+ }
430
+ return null;
431
+ };
300
432
  const extractDependencyInfo = (packageJson) => {
301
433
  const allDependencies = collectAllDependencies(packageJson);
434
+ const rawVersion = allDependencies.react ?? null;
302
435
  return {
303
- reactVersion: allDependencies.react ?? null,
436
+ reactVersion: rawVersion && !isCatalogReference(rawVersion) ? rawVersion : null,
304
437
  framework: detectFramework(allDependencies)
305
438
  };
306
439
  };
307
440
  const parsePnpmWorkspacePatterns = (rootDirectory) => {
308
441
  const workspacePath = path.join(rootDirectory, "pnpm-workspace.yaml");
309
- if (!fs.existsSync(workspacePath)) return [];
442
+ if (!isFile(workspacePath)) return [];
310
443
  const content = fs.readFileSync(workspacePath, "utf-8");
311
444
  const patterns = [];
312
445
  let isInsidePackagesBlock = false;
@@ -332,14 +465,14 @@ const resolveWorkspaceDirectories = (rootDirectory, pattern) => {
332
465
  const cleanPattern = pattern.replace(/["']/g, "").replace(/\/\*\*$/, "/*");
333
466
  if (!cleanPattern.includes("*")) {
334
467
  const directoryPath = path.join(rootDirectory, cleanPattern);
335
- if (fs.existsSync(directoryPath) && fs.existsSync(path.join(directoryPath, "package.json"))) return [directoryPath];
468
+ if (fs.existsSync(directoryPath) && isFile(path.join(directoryPath, "package.json"))) return [directoryPath];
336
469
  return [];
337
470
  }
338
471
  const wildcardIndex = cleanPattern.indexOf("*");
339
472
  const baseDirectory = path.join(rootDirectory, cleanPattern.slice(0, wildcardIndex));
340
473
  const suffixAfterWildcard = cleanPattern.slice(wildcardIndex + 1);
341
474
  if (!fs.existsSync(baseDirectory) || !fs.statSync(baseDirectory).isDirectory()) return [];
342
- return fs.readdirSync(baseDirectory).map((entry) => path.join(baseDirectory, entry, suffixAfterWildcard)).filter((entryPath) => fs.existsSync(entryPath) && fs.statSync(entryPath).isDirectory() && fs.existsSync(path.join(entryPath, "package.json")));
475
+ return fs.readdirSync(baseDirectory).map((entry) => path.join(baseDirectory, entry, suffixAfterWildcard)).filter((entryPath) => fs.existsSync(entryPath) && fs.statSync(entryPath).isDirectory() && isFile(path.join(entryPath, "package.json")));
343
476
  };
344
477
  const findDependencyInfoFromMonorepoRoot = (directory) => {
345
478
  const monorepoRoot = findMonorepoRoot(directory);
@@ -347,11 +480,17 @@ const findDependencyInfoFromMonorepoRoot = (directory) => {
347
480
  reactVersion: null,
348
481
  framework: "unknown"
349
482
  };
350
- const rootPackageJson = readPackageJson(path.join(monorepoRoot, "package.json"));
483
+ const monorepoPackageJsonPath = path.join(monorepoRoot, "package.json");
484
+ if (!isFile(monorepoPackageJsonPath)) return {
485
+ reactVersion: null,
486
+ framework: "unknown"
487
+ };
488
+ const rootPackageJson = readPackageJson(monorepoPackageJsonPath);
351
489
  const rootInfo = extractDependencyInfo(rootPackageJson);
490
+ const catalogVersion = resolveCatalogVersion(rootPackageJson, "react");
352
491
  const workspaceInfo = findReactInWorkspaces(monorepoRoot, rootPackageJson);
353
492
  return {
354
- reactVersion: rootInfo.reactVersion ?? workspaceInfo.reactVersion,
493
+ reactVersion: rootInfo.reactVersion ?? catalogVersion ?? workspaceInfo.reactVersion,
355
494
  framework: rootInfo.framework !== "unknown" ? rootInfo.framework : workspaceInfo.framework
356
495
  };
357
496
  };
@@ -377,7 +516,7 @@ const hasCompilerPackage = (packageJson) => {
377
516
  return Object.keys(allDependencies).some((packageName) => REACT_COMPILER_PACKAGES.has(packageName));
378
517
  };
379
518
  const fileContainsPattern = (filePath, pattern) => {
380
- if (!fs.existsSync(filePath)) return false;
519
+ if (!isFile(filePath)) return false;
381
520
  const content = fs.readFileSync(filePath, "utf-8");
382
521
  return pattern.test(content);
383
522
  };
@@ -391,7 +530,7 @@ const detectReactCompiler = (directory, packageJson) => {
391
530
  let ancestorDirectory = path.dirname(directory);
392
531
  while (ancestorDirectory !== path.dirname(ancestorDirectory)) {
393
532
  const ancestorPackagePath = path.join(ancestorDirectory, "package.json");
394
- if (fs.existsSync(ancestorPackagePath)) {
533
+ if (isFile(ancestorPackagePath)) {
395
534
  if (hasCompilerPackage(readPackageJson(ancestorPackagePath))) return true;
396
535
  }
397
536
  ancestorDirectory = path.dirname(ancestorDirectory);
@@ -400,9 +539,10 @@ const detectReactCompiler = (directory, packageJson) => {
400
539
  };
401
540
  const discoverProject = (directory) => {
402
541
  const packageJsonPath = path.join(directory, "package.json");
403
- if (!fs.existsSync(packageJsonPath)) throw new Error(`No package.json found in ${directory}`);
542
+ if (!isFile(packageJsonPath)) throw new Error(`No package.json found in ${directory}`);
404
543
  const packageJson = readPackageJson(packageJsonPath);
405
544
  let { reactVersion, framework } = extractDependencyInfo(packageJson);
545
+ if (!reactVersion) reactVersion = resolveCatalogVersion(packageJson, "react");
406
546
  if (!reactVersion || framework === "unknown") {
407
547
  const workspaceInfo = findReactInWorkspaces(directory, packageJson);
408
548
  if (!reactVersion && workspaceInfo.reactVersion) reactVersion = workspaceInfo.reactVersion;
@@ -432,10 +572,9 @@ const discoverProject = (directory) => {
432
572
  //#region src/utils/load-config.ts
433
573
  const CONFIG_FILENAME = "react-doctor.config.json";
434
574
  const PACKAGE_JSON_CONFIG_KEY = "reactDoctor";
435
- const isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
436
575
  const loadConfig = (rootDirectory) => {
437
576
  const configFilePath = path.join(rootDirectory, CONFIG_FILENAME);
438
- if (fs.existsSync(configFilePath)) try {
577
+ if (isFile(configFilePath)) try {
439
578
  const fileContent = fs.readFileSync(configFilePath, "utf-8");
440
579
  const parsed = JSON.parse(fileContent);
441
580
  if (isPlainObject(parsed)) return parsed;
@@ -444,7 +583,7 @@ const loadConfig = (rootDirectory) => {
444
583
  console.warn(`Warning: Failed to parse ${CONFIG_FILENAME}: ${error instanceof Error ? error.message : String(error)}`);
445
584
  }
446
585
  const packageJsonPath = path.join(rootDirectory, "package.json");
447
- if (fs.existsSync(packageJsonPath)) try {
586
+ if (isFile(packageJsonPath)) try {
448
587
  const fileContent = fs.readFileSync(packageJsonPath, "utf-8");
449
588
  const embeddedConfig = JSON.parse(fileContent)[PACKAGE_JSON_CONFIG_KEY];
450
589
  if (isPlainObject(embeddedConfig)) return embeddedConfig;
@@ -542,7 +681,7 @@ const runKnip = async (rootDirectory) => {
542
681
  let knipResult;
543
682
  if (monorepoRoot) {
544
683
  const packageJsonPath = path.join(rootDirectory, "package.json");
545
- const workspaceName = (fs.existsSync(packageJsonPath) ? JSON.parse(fs.readFileSync(packageJsonPath, "utf-8")) : {}).name ?? path.basename(rootDirectory);
684
+ const workspaceName = (isFile(packageJsonPath) ? JSON.parse(fs.readFileSync(packageJsonPath, "utf-8")) : {}).name ?? path.basename(rootDirectory);
546
685
  try {
547
686
  knipResult = await runKnipWithOptions(monorepoRoot, workspaceName);
548
687
  } catch {
@@ -825,7 +964,7 @@ const RULE_CATEGORY_MAP = {
825
964
  "react-doctor/rn-no-single-element-style-array": "React Native"
826
965
  };
827
966
  const RULE_HELP_MAP = {
828
- "no-derived-state-effect": "For derived state, compute inline: `const x = fn(dep)`. For state resets on prop change, use a key prop: `<Component key={prop} />`",
967
+ "no-derived-state-effect": "For derived state, compute inline: `const x = fn(dep)`. For state resets on prop change, use a key prop: `<Component key={prop} />`. See https://react.dev/learn/you-might-not-need-an-effect",
829
968
  "no-fetch-in-effect": "Use `useQuery()` from @tanstack/react-query, `useSWR()`, or fetch in a Server Component instead",
830
969
  "no-cascading-set-state": "Combine into useReducer: `const [state, dispatch] = useReducer(reducer, initialState)`",
831
970
  "no-effect-event-handler": "Move the conditional logic into onClick, onChange, or onSubmit handlers directly",
@@ -865,7 +1004,7 @@ const RULE_HELP_MAP = {
865
1004
  "nextjs-no-use-search-params-without-suspense": "Wrap the component using useSearchParams: `<Suspense fallback={<Skeleton />}><SearchComponent /></Suspense>`",
866
1005
  "nextjs-no-client-fetch-for-server-data": "Remove 'use client' and fetch directly in the Server Component — no API round-trip, secrets stay on server",
867
1006
  "nextjs-missing-metadata": "Add `export const metadata = { title: '...', description: '...' }` or `export async function generateMetadata()`",
868
- "nextjs-no-client-side-redirect": "Use `redirect('/path')` from 'next/navigation' in a Server Component, or handle in middleware",
1007
+ "nextjs-no-client-side-redirect": "Use `redirect('/path')` from 'next/navigation' directly (works in both server and client components), or handle in middleware",
869
1008
  "nextjs-no-redirect-in-try-catch": "Move the redirect/notFound call outside the try block, or add `unstable_rethrow(error)` in the catch",
870
1009
  "nextjs-image-missing-sizes": "Add sizes for responsive behavior: `sizes=\"(max-width: 768px) 100vw, 50vw\"` matching your layout breakpoints",
871
1010
  "nextjs-no-native-script": "`import Script from \"next/script\"` — use `strategy=\"afterInteractive\"` for analytics or `\"lazyOnload\"` for widgets",
@@ -953,7 +1092,14 @@ const spawnOxlint = (args, rootDirectory, nodeBinaryPath) => new Promise((resolv
953
1092
  child.stdout.on("data", (buffer) => stdoutBuffers.push(buffer));
954
1093
  child.stderr.on("data", (buffer) => stderrBuffers.push(buffer));
955
1094
  child.on("error", (error) => reject(/* @__PURE__ */ new Error(`Failed to run oxlint: ${error.message}`)));
956
- child.on("close", () => {
1095
+ child.on("close", (code, signal) => {
1096
+ if (signal) {
1097
+ const stderrOutput = Buffer.concat(stderrBuffers).toString("utf-8").trim();
1098
+ const hint = signal === "SIGABRT" ? " (out of memory — try scanning fewer files with --diff)" : "";
1099
+ const detail = stderrOutput ? `: ${stderrOutput}` : "";
1100
+ reject(/* @__PURE__ */ new Error(`oxlint was killed by ${signal}${hint}${detail}`));
1101
+ return;
1102
+ }
957
1103
  const output = Buffer.concat(stdoutBuffers).toString("utf-8").trim();
958
1104
  if (!output) {
959
1105
  const stderrOutput = Buffer.concat(stderrBuffers).toString("utf-8").trim();