react-doctor 0.9.3-dev.f958625 → 0.9.4-dev.212b8b4

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 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]="2b3a9fe9-8109-5067-854f-a10b2e6454e7")}catch(e){}}();
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]="7d7e0bde-eb19-58b5-b161-625bcb1b2588")}catch(e){}}();
3
3
  import { createRequire } from "node:module";
4
4
  import * as NodeChildProcess from "node:child_process";
5
5
  import { execFile, execFileSync, spawn, spawnSync } from "node:child_process";
@@ -65488,7 +65488,7 @@ const SOCKET_FREE_USER_AGENT = "react-doctor-supply-chain";
65488
65488
  const FILE_LINT_CACHE_FILENAME = "file-lint-cache.json";
65489
65489
  const FILE_LINT_CACHE_MAX_FILE_COUNT = 5e4;
65490
65490
  const SIDECAR_LINT_CACHE_FILENAME = "sidecar-lint-cache.json";
65491
- const CORE_PACKAGE_VERSION = "0.9.3";
65491
+ const CORE_PACKAGE_VERSION = "0.9.4";
65492
65492
  const DEAD_CODE_CACHE_FILENAME = "dead-code-cache.json";
65493
65493
  const DEAD_CODE_SUMMARY_CACHE_FILENAME = "dead-code-summaries.json";
65494
65494
  const SUPPLY_CHAIN_PLUGIN = "socket";
@@ -65501,17 +65501,20 @@ const SUPPLY_CHAIN_IGNORED_PACKAGES = new Set(["next"]);
65501
65501
  const isErrnoException = (error) => error instanceof Error && "code" in error;
65502
65502
  const IGNORABLE_READDIR_ERROR_CODES = new Set([
65503
65503
  "EACCES",
65504
+ "EBUSY",
65504
65505
  "EPERM",
65505
65506
  "ENOENT",
65506
65507
  "ENOTDIR",
65507
65508
  "EINVAL",
65508
65509
  "ELOOP",
65509
- "ENAMETOOLONG"
65510
+ "ENAMETOOLONG",
65511
+ "ETIMEDOUT",
65512
+ "UNKNOWN"
65510
65513
  ]);
65511
65514
  const isIgnorableReaddirError = (error) => isErrnoException(error) && typeof error.code === "string" && IGNORABLE_READDIR_ERROR_CODES.has(error.code);
65512
65515
  const readDirectoryEntries = (directoryPath) => {
65513
65516
  try {
65514
- return NFS.readdirSync(directoryPath, { withFileTypes: true });
65517
+ return fs.readdirSync(directoryPath, { withFileTypes: true });
65515
65518
  } catch (error) {
65516
65519
  if (isIgnorableReaddirError(error)) return [];
65517
65520
  throw error;
@@ -65519,14 +65522,14 @@ const readDirectoryEntries = (directoryPath) => {
65519
65522
  };
65520
65523
  const isFile = (filePath) => {
65521
65524
  try {
65522
- return NFS.statSync(filePath).isFile();
65525
+ return fs.statSync(filePath).isFile();
65523
65526
  } catch {
65524
65527
  return false;
65525
65528
  }
65526
65529
  };
65527
65530
  const isDirectory = (directoryPath) => {
65528
65531
  try {
65529
- return NFS.statSync(directoryPath).isDirectory();
65532
+ return fs.statSync(directoryPath).isDirectory();
65530
65533
  } catch {
65531
65534
  return false;
65532
65535
  }
@@ -65950,7 +65953,12 @@ const mergeCompilerOptions = (inherited, current) => {
65950
65953
  };
65951
65954
  };
65952
65955
  const readResolvedCompilerOptions = (tsConfigPath, extendsDepth, visitedPaths) => {
65953
- const realPath = NFS.realpathSync.native(tsConfigPath);
65956
+ let realPath;
65957
+ try {
65958
+ realPath = NFS.realpathSync.native(tsConfigPath);
65959
+ } catch {
65960
+ return null;
65961
+ }
65954
65962
  if (visitedPaths.has(realPath)) return null;
65955
65963
  const tsConfig = readTsConfig(realPath);
65956
65964
  if (!tsConfig) return null;
@@ -66478,27 +66486,30 @@ const getSelectedObjectProperty = (expression, propertyName, analysis, visitedEx
66478
66486
  }
66479
66487
  return null;
66480
66488
  };
66481
- const configExpressionMayDefineProperty = (expression, propertyName, analysis) => {
66489
+ const configExpressionMayDefineProperty = (expression, propertyName, analysis, visitedExpressions = /* @__PURE__ */ new Set()) => {
66482
66490
  let resolvedExpression = expression;
66483
66491
  while (ts.isParenthesizedExpression(resolvedExpression) || ts.isAsExpression(resolvedExpression) || ts.isTypeAssertionExpression(resolvedExpression) || ts.isSatisfiesExpression(resolvedExpression) || ts.isNonNullExpression(resolvedExpression)) resolvedExpression = resolvedExpression.expression;
66492
+ if (visitedExpressions.has(resolvedExpression)) return true;
66493
+ const nextVisitedExpressions = new Set(visitedExpressions);
66494
+ nextVisitedExpressions.add(resolvedExpression);
66484
66495
  if (ts.isIdentifier(resolvedExpression)) {
66485
66496
  if (analysis.localBindings.has(resolvedExpression.text)) {
66486
66497
  const localReference = analysis.localBindings.get(resolvedExpression.text);
66487
- return localReference?.expression ? configExpressionMayDefineProperty(localReference.expression, propertyName, localReference.analysis) : true;
66498
+ return localReference?.expression ? configExpressionMayDefineProperty(localReference.expression, propertyName, localReference.analysis, nextVisitedExpressions) : true;
66488
66499
  }
66489
66500
  const scopedBinding = getScopedConfigBinding(resolvedExpression);
66490
- if (scopedBinding.wasFound) return scopedBinding.initializer ? configExpressionMayDefineProperty(scopedBinding.initializer, propertyName, analysis) : true;
66501
+ if (scopedBinding.wasFound) return scopedBinding.initializer ? configExpressionMayDefineProperty(scopedBinding.initializer, propertyName, analysis, nextVisitedExpressions) : true;
66491
66502
  const topLevelBinding = getTopLevelBinding(analysis.sourceFile, resolvedExpression.text);
66492
- return topLevelBinding && ts.isExpression(topLevelBinding) ? configExpressionMayDefineProperty(topLevelBinding, propertyName, analysis) : true;
66503
+ return topLevelBinding && ts.isExpression(topLevelBinding) ? configExpressionMayDefineProperty(topLevelBinding, propertyName, analysis, nextVisitedExpressions) : true;
66493
66504
  }
66494
66505
  if (ts.isConditionalExpression(resolvedExpression)) {
66495
- if (isStaticallyTruthyConfigExpression(resolvedExpression.condition, analysis)) return configExpressionMayDefineProperty(resolvedExpression.whenTrue, propertyName, analysis);
66496
- if (isStaticallyDisabledConfigExpression(resolvedExpression.condition, analysis)) return configExpressionMayDefineProperty(resolvedExpression.whenFalse, propertyName, analysis);
66497
- return configExpressionMayDefineProperty(resolvedExpression.whenTrue, propertyName, analysis) || configExpressionMayDefineProperty(resolvedExpression.whenFalse, propertyName, analysis);
66506
+ if (isStaticallyTruthyConfigExpression(resolvedExpression.condition, analysis)) return configExpressionMayDefineProperty(resolvedExpression.whenTrue, propertyName, analysis, nextVisitedExpressions);
66507
+ if (isStaticallyDisabledConfigExpression(resolvedExpression.condition, analysis)) return configExpressionMayDefineProperty(resolvedExpression.whenFalse, propertyName, analysis, nextVisitedExpressions);
66508
+ return configExpressionMayDefineProperty(resolvedExpression.whenTrue, propertyName, analysis, nextVisitedExpressions) || configExpressionMayDefineProperty(resolvedExpression.whenFalse, propertyName, analysis, nextVisitedExpressions);
66498
66509
  }
66499
66510
  if (!ts.isObjectLiteralExpression(resolvedExpression)) return true;
66500
66511
  return resolvedExpression.properties.some((property) => {
66501
- if (ts.isSpreadAssignment(property)) return configExpressionMayDefineProperty(property.expression, propertyName, analysis);
66512
+ if (ts.isSpreadAssignment(property)) return configExpressionMayDefineProperty(property.expression, propertyName, analysis, nextVisitedExpressions);
66502
66513
  if (ts.isPropertyAssignment(property) || ts.isShorthandPropertyAssignment(property) || ts.isMethodDeclaration(property) || ts.isGetAccessorDeclaration(property) || ts.isSetAccessorDeclaration(property)) {
66503
66514
  const staticPropertyName = getStaticPropertyName$2(property.name);
66504
66515
  return staticPropertyName === null || staticPropertyName === propertyName;
@@ -66574,7 +66585,13 @@ const analyzeConfigModuleExport = (filePath, exportName, allowCompilerTransform,
66574
66585
  if (visitedModules.has(moduleVisitKey)) return false;
66575
66586
  const nextVisitedModules = new Set(visitedModules);
66576
66587
  nextVisitedModules.add(moduleVisitKey);
66577
- return analyzeConfigSourceFileExport(parseConfigSourceFile(filePath, NFS.readFileSync(filePath, "utf-8")), filePath, exportName, allowCompilerTransform, importDepth, nextVisitedModules, argumentsList, argumentAnalysis);
66588
+ let sourceText;
66589
+ try {
66590
+ sourceText = NFS.readFileSync(filePath, "utf-8");
66591
+ } catch {
66592
+ return false;
66593
+ }
66594
+ return analyzeConfigSourceFileExport(parseConfigSourceFile(filePath, sourceText), filePath, exportName, allowCompilerTransform, importDepth, nextVisitedModules, argumentsList, argumentAnalysis);
66578
66595
  };
66579
66596
  const analyzeConfigSourceFileExport = (sourceFile, filePath, exportName, allowCompilerTransform, importDepth, visitedModules, argumentsList = [], argumentAnalysis) => {
66580
66597
  const analysis = {
@@ -70077,6 +70094,43 @@ const validateStringArrayField = (fieldName, value) => {
70077
70094
  return false;
70078
70095
  });
70079
70096
  };
70097
+ const validateIgnoreOverride = (rawOverride, index) => {
70098
+ const fieldName = `ignore.overrides[${index}]`;
70099
+ if (!isRecord$1(rawOverride)) {
70100
+ warnConfigIssue(`config field "${fieldName}" must be an object (got ${typeof rawOverride}); ignoring this override.`);
70101
+ return;
70102
+ }
70103
+ const files = validateStringArrayField(`${fieldName}.files`, rawOverride.files);
70104
+ if (files === void 0) return void 0;
70105
+ const validatedOverride = { files };
70106
+ if (rawOverride.rules !== void 0) {
70107
+ const rules = validateStringArrayField(`${fieldName}.rules`, rawOverride.rules);
70108
+ if (rules !== void 0) validatedOverride.rules = rules;
70109
+ }
70110
+ return validatedOverride;
70111
+ };
70112
+ const validateIgnoreField = (rawIgnore) => {
70113
+ if (!isRecord$1(rawIgnore)) {
70114
+ warnConfigIssue(`config field "ignore" must be an object (got ${typeof rawIgnore}); ignoring this field.`);
70115
+ return;
70116
+ }
70117
+ const validatedIgnore = {};
70118
+ for (const fieldName of [
70119
+ "rules",
70120
+ "files",
70121
+ "tags"
70122
+ ]) {
70123
+ if (rawIgnore[fieldName] === void 0) continue;
70124
+ const validatedValues = validateStringArrayField(`ignore.${fieldName}`, rawIgnore[fieldName]);
70125
+ if (validatedValues !== void 0) validatedIgnore[fieldName] = validatedValues;
70126
+ }
70127
+ if (rawIgnore.overrides !== void 0) if (!Array.isArray(rawIgnore.overrides)) warnConfigIssue(`config field "ignore.overrides" must be an array (got ${typeof rawIgnore.overrides}); ignoring this field.`);
70128
+ else validatedIgnore.overrides = rawIgnore.overrides.flatMap((rawOverride, index) => {
70129
+ const validatedOverride = validateIgnoreOverride(rawOverride, index);
70130
+ return validatedOverride ? [validatedOverride] : [];
70131
+ });
70132
+ return validatedIgnore;
70133
+ };
70080
70134
  const validateSurfaceControls = (surface, rawControls) => {
70081
70135
  if (!isRecord$1(rawControls)) {
70082
70136
  warnConfigIssue(`config field "surfaces.${surface}" must be an object (got ${typeof rawControls}); ignoring this surface.`);
@@ -70152,6 +70206,7 @@ const validateConfigTypes = (config) => {
70152
70206
  for (const fieldName of BOOLEAN_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => coerceMaybeBooleanString(fieldName, value));
70153
70207
  for (const fieldName of STRING_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => validateString(fieldName, value));
70154
70208
  for (const fieldName of STRING_ARRAY_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => validateStringArrayField(fieldName, value));
70209
+ applyFieldValidator(config, validated, "ignore", validateIgnoreField);
70155
70210
  applyFieldValidator(config, validated, "surfaces", validateSurfacesField);
70156
70211
  for (const fieldName of SEVERITY_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => validateSeverityMap(fieldName, value, fieldName === "categories"));
70157
70212
  applyFieldValidator(config, validated, "plugins", (value) => validateStringArrayField("plugins", value));
@@ -71844,7 +71899,7 @@ const collectScriptMotionEvidence = (sourceFile, typeChecker, program) => {
71844
71899
  if (calleeEvidence.isReducedMotionHook && isExpressionValueConsumed(node)) evidence.hasReducedMotionHandling = true;
71845
71900
  }
71846
71901
  if (ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) {
71847
- const tagEvidence = ts.isJsxNamespacedName(node.tagName) ? EMPTY_MOTION_EXPRESSION_EVIDENCE : resolveMotionExpressionEvidence(node.tagName, typeChecker, program);
71902
+ const tagEvidence = ts.isIdentifier(node.tagName) || ts.isPropertyAccessExpression(node.tagName) ? resolveMotionExpressionEvidence(node.tagName, typeChecker, program) : EMPTY_MOTION_EXPRESSION_EVIDENCE;
71848
71903
  if (tagEvidence.isMotionComponent || tagEvidence.isMotionComponentFactory) evidence.hasMotionUse = true;
71849
71904
  if (tagEvidence.isMotionConfig && jsxElementHasReducedMotionConfiguration(node.attributes, typeChecker)) evidence.hasReducedMotionHandling = true;
71850
71905
  }
@@ -73844,7 +73899,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
73844
73899
  changedFiles: yield* mergeUntracked(directory, splitNullSeparated(diff.stdout), includeUntracked),
73845
73900
  isCurrentChanges: false
73846
73901
  };
73847
- }).pipe(withSpan("Git.diffSelection")),
73902
+ }).pipe(catchReasons("ReactDoctorError", { GitInvocationFailed: () => succeed$2(null) }, (_reason, error) => fail$4(error)), withSpan("Git.diffSelection")),
73848
73903
  stagedFilePaths: (directory) => runGit(directory, [
73849
73904
  "diff",
73850
73905
  "--no-ext-diff",
@@ -78475,7 +78530,7 @@ const materializeSourceTree = (input) => gen(function* () {
78475
78530
  const sourcePath = path.join(input.directory, subdirectory, configFilename);
78476
78531
  const targetPath = path.resolve(resolvedTempDirectory, subdirectory, configFilename);
78477
78532
  if (!isPathInsideDirectory(targetPath, resolvedTempDirectory)) continue;
78478
- if (fs.existsSync(sourcePath) && !fs.existsSync(targetPath)) {
78533
+ if (isFile(sourcePath) && !fs.existsSync(targetPath)) {
78479
78534
  fs.mkdirSync(path.dirname(targetPath), { recursive: true });
78480
78535
  fs.cpSync(sourcePath, targetPath);
78481
78536
  }
@@ -78938,59 +78993,34 @@ const createOxlintSpawnSlots = (concurrency) => createWorkerSlots({
78938
78993
  slotCount: resolveScanConcurrency(concurrency),
78939
78994
  createAbortError: createLintPhaseAbortError
78940
78995
  });
78941
- const AI_TRAINING_ENV_VARS = [
78942
- ["HF_DATASETS_CACHE", "huggingface"],
78943
- ["HF_HOME", "huggingface"],
78944
- ["HUGGINGFACE_HUB_CACHE", "huggingface"],
78945
- ["SPACE_ID", "huggingface-spaces"],
78946
- ["CUDA_VISIBLE_DEVICES", "cuda"],
78947
- ["NVIDIA_VISIBLE_DEVICES", "nvidia"],
78948
- ["ROCR_VISIBLE_DEVICES", "rocm"],
78949
- ["TPU_NAME", "google-tpu"],
78996
+ const AI_TRAINING_BY_ENVIRONMENT_VARIABLE = [
78950
78997
  ["WANDB_RUN_ID", "wandb"],
78951
78998
  ["WANDB_SWEEP_ID", "wandb"],
78952
78999
  ["MLFLOW_RUN_ID", "mlflow"],
78953
- ["MLFLOW_TRACKING_URI", "mlflow"],
78954
79000
  ["COMET_EXPERIMENT_KEY", "comet"],
78955
79001
  ["NEPTUNE_RUN_ID", "neptune"],
78956
79002
  ["CLEARML_TASK_ID", "clearml"],
78957
79003
  ["DVC_STAGE", "dvc"],
78958
79004
  ["RAY_WORKER_PROCESS", "ray"],
78959
- ["RAY_ADDRESS", "ray"],
78960
- ["MUJOCO_GL", "mujoco"],
78961
- ["MUJOCO_PATH", "mujoco"],
78962
- ["GYM_DISABLE_ENV_CHECKER", "gymnasium"],
78963
79005
  ["SM_TRAINING_ENV", "sagemaker"],
78964
79006
  ["TRAINING_JOB_ARN", "sagemaker"],
78965
- ["SAGEMAKER_BASE_DIR", "sagemaker"],
78966
79007
  ["AZUREML_RUN_ID", "azure-ml"],
78967
- ["AZURE_ML_MODEL_DIR", "azure-ml"],
78968
- ["CLOUD_ML_PROJECT_ID", "vertex-ai"],
78969
- ["VERTEX_AI_LOG_LEVEL", "vertex-ai"],
78970
- ["DET_MASTER", "determined-ai"],
78971
- ["LIGHTNING_USER_ID", "lightning-ai"],
78972
79008
  ["FLYTE_INTERNAL_EXECUTION_ID", "flyte"],
78973
- ["ARGO_WORKFLOW_NAME", "argo-workflows"],
78974
79009
  ["KFP_POD_NAME", "kubeflow-pipelines"],
78975
79010
  ["KAGGLE_KERNEL_RUN_TYPE", "kaggle"],
78976
79011
  ["COLAB_BACKEND_VERSION", "google-colab"],
78977
- ["DATABRICKS_RUNTIME_VERSION", "databricks"],
78978
79012
  ["DAYTONA_WS_ID", "daytona"],
78979
- ["DAYTONA_WS_NAME", "daytona"],
78980
79013
  ["E2B_SANDBOX_ID", "e2b"],
78981
79014
  ["MODAL_FUNCTION_ID", "modal"],
78982
79015
  ["MODAL_TASK_ID", "modal"],
78983
79016
  ["RUNPOD_POD_ID", "runpod"],
78984
- ["REPLICATE_USERNAME", "replicate"],
78985
79017
  ["VAST_CONTAINERLABEL", "vast-ai"],
78986
- ["HARBOR_URL", "harbor"],
78987
- ["HARBOR_HOSTNAME", "harbor"],
78988
79018
  ["SWE_BENCH_TASK", "swe-bench"],
78989
79019
  ["SWEBENCH_TASK", "swe-bench"],
78990
79020
  ["SWE_AGENT_MODEL", "swe-agent"]
78991
79021
  ];
78992
- const detectAiTrainingEnvironment = () => {
78993
- for (const [envVar, label] of AI_TRAINING_ENV_VARS) if (process.env[envVar]) return label;
79022
+ const detectAiTrainingEnvironment = (environment = process.env) => {
79023
+ for (const [environmentVariable, label] of AI_TRAINING_BY_ENVIRONMENT_VARIABLE) if (environment[environmentVariable]?.trim()) return label;
78994
79024
  return null;
78995
79025
  };
78996
79026
  const groupBy = (items, keyFn) => {
@@ -79018,16 +79048,17 @@ const groupBy = (items, keyFn) => {
79018
79048
  * a 404. Gate the directive on this predicate.
79019
79049
  */
79020
79050
  const hasPublishedFixRecipe = (diagnostic) => diagnostic.plugin === "react-doctor" && Object.hasOwn(REACT_DOCTOR_RULE_REGISTRY, diagnostic.rule);
79021
- const unionValues = (baseValues, overrideValues) => [...new Set([...baseValues, ...overrideValues])];
79051
+ const stringValues = (values) => Array.isArray(values) ? values.filter((value) => typeof value === "string") : [];
79052
+ const unionValues = (baseValues, overrideValues) => [...new Set([...stringValues(baseValues), ...stringValues(overrideValues)])];
79022
79053
  const mergeIgnores = (baseIgnore, overrideIgnore) => {
79023
79054
  const mergedIgnore = {
79024
79055
  ...baseIgnore,
79025
79056
  ...overrideIgnore
79026
79057
  };
79027
- if (baseIgnore.rules && overrideIgnore.rules) mergedIgnore.rules = unionValues(baseIgnore.rules, overrideIgnore.rules);
79028
- if (baseIgnore.files && overrideIgnore.files) mergedIgnore.files = unionValues(baseIgnore.files, overrideIgnore.files);
79029
- if (baseIgnore.tags && overrideIgnore.tags) mergedIgnore.tags = unionValues(baseIgnore.tags, overrideIgnore.tags);
79030
- if (baseIgnore.overrides && overrideIgnore.overrides) mergedIgnore.overrides = [...baseIgnore.overrides, ...overrideIgnore.overrides];
79058
+ if (baseIgnore.rules !== void 0 || overrideIgnore.rules !== void 0) mergedIgnore.rules = unionValues(baseIgnore.rules, overrideIgnore.rules);
79059
+ if (baseIgnore.files !== void 0 || overrideIgnore.files !== void 0) mergedIgnore.files = unionValues(baseIgnore.files, overrideIgnore.files);
79060
+ if (baseIgnore.tags !== void 0 || overrideIgnore.tags !== void 0) mergedIgnore.tags = unionValues(baseIgnore.tags, overrideIgnore.tags);
79061
+ if (baseIgnore.overrides !== void 0 || overrideIgnore.overrides !== void 0) mergedIgnore.overrides = [...Array.isArray(baseIgnore.overrides) ? baseIgnore.overrides : [], ...Array.isArray(overrideIgnore.overrides) ? overrideIgnore.overrides : []];
79031
79062
  return mergedIgnore;
79032
79063
  };
79033
79064
  /**
@@ -79222,6 +79253,21 @@ const NON_INTERACTIVE_ENVIRONMENT_VARIABLES = [
79222
79253
  ];
79223
79254
  const isNonInteractiveEnvironment = () => NON_INTERACTIVE_ENVIRONMENT_VARIABLES.some((envVariable) => Boolean(process.env[envVariable])) || isCodingAgentEnvironment();
79224
79255
  //#endregion
79256
+ //#region src/cli/utils/cli-input-error.ts
79257
+ /**
79258
+ * A mistake in *how the CLI was invoked* — a malformed `<file>:<line>`
79259
+ * argument, mutually exclusive flags, or an unknown `--project` name. This is
79260
+ * user input, not a react-doctor bug, so `isExpectedUserError` routes it
79261
+ * through `handleUserError`: a clean, single-line message with no Sentry
79262
+ * report and no "Something went wrong, open a prefilled issue" block.
79263
+ *
79264
+ * The `message` is rendered verbatim, so it must read as a complete,
79265
+ * human-readable sentence that tells the user how to fix their invocation.
79266
+ */
79267
+ var CliInputError = class extends Error {
79268
+ name = "CliInputError";
79269
+ };
79270
+ //#endregion
79225
79271
  //#region src/cli/utils/constants.ts
79226
79272
  const REACT_DOCTOR_CONFIG_PROJECT_NAME = "react-doctor";
79227
79273
  const STAGED_FILES_TEMP_DIR_PREFIX = "react-doctor-staged-";
@@ -79373,7 +79419,7 @@ const makeNoopConsole = () => ({
79373
79419
  });
79374
79420
  //#endregion
79375
79421
  //#region src/cli/utils/version.ts
79376
- const VERSION = "0.9.3-dev.f958625";
79422
+ const VERSION = "0.9.4-dev.212b8b4";
79377
79423
  //#endregion
79378
79424
  //#region src/cli/utils/json-mode.ts
79379
79425
  let context = null;
@@ -79417,9 +79463,14 @@ const enableJsonMode = ({ compact, directory, outputFile }) => {
79417
79463
  directory,
79418
79464
  startTime: performance$1.now(),
79419
79465
  mode: "full",
79420
- outputFile: outputFile ?? null
79466
+ outputFile: null
79421
79467
  };
79422
79468
  installSilentConsole();
79469
+ if (outputFile) {
79470
+ const resolvedOutputFile = Path.resolve(outputFile);
79471
+ if (NFS.existsSync(resolvedOutputFile) && !NFS.statSync(resolvedOutputFile).isFile()) throw new CliInputError(`--json-out must point to a file, not ${resolvedOutputFile}.`);
79472
+ context.outputFile = outputFile;
79473
+ }
79423
79474
  };
79424
79475
  const isJsonModeActive = () => context !== null;
79425
79476
  const setJsonReportDirectory = (directory) => {
@@ -79613,6 +79664,101 @@ const buildSentryScope = (runContext = buildRunContext()) => {
79613
79664
  //#region src/cli/utils/is-env-flag-enabled.ts
79614
79665
  const isEnvFlagEnabled = (value) => value === "1" || value?.toLowerCase() === "true";
79615
79666
  //#endregion
79667
+ //#region src/cli/utils/is-environment-error.ts
79668
+ const ENVIRONMENT_ERROR_CODES = new Set([
79669
+ "EACCES",
79670
+ "EIO",
79671
+ "ENOSPC",
79672
+ "ENOTDIR",
79673
+ "EPERM",
79674
+ "EROFS"
79675
+ ]);
79676
+ const ENVIRONMENT_FILESYSTEM_SYSCALLS = new Set([
79677
+ "lstat",
79678
+ "open",
79679
+ "read",
79680
+ "readdir",
79681
+ "readlink",
79682
+ "realpath",
79683
+ "scandir",
79684
+ "stat"
79685
+ ]);
79686
+ const TRANSIENT_FILESYSTEM_ERROR_CODES = new Set([
79687
+ "EBUSY",
79688
+ "ETIMEDOUT",
79689
+ "UNKNOWN"
79690
+ ]);
79691
+ const findEnvironmentError = (error) => {
79692
+ const pendingErrors = [error];
79693
+ const visitedErrors = /* @__PURE__ */ new Set();
79694
+ while (pendingErrors.length > 0) {
79695
+ const currentError = pendingErrors.pop();
79696
+ if (typeof currentError !== "object" || currentError === null) continue;
79697
+ if (visitedErrors.has(currentError)) continue;
79698
+ visitedErrors.add(currentError);
79699
+ if (isErrnoException(currentError) && typeof currentError.code === "string") {
79700
+ const syscall = currentError.syscall ?? "";
79701
+ if (ENVIRONMENT_ERROR_CODES.has(currentError.code) || currentError.code === "ENOENT" && syscall.startsWith("spawn") || TRANSIENT_FILESYSTEM_ERROR_CODES.has(currentError.code) && ENVIRONMENT_FILESYSTEM_SYSCALLS.has(syscall)) return currentError;
79702
+ }
79703
+ if (currentError instanceof Error && currentError.cause !== void 0) pendingErrors.push(currentError.cause);
79704
+ if (isReactDoctorError(currentError) && "cause" in currentError.reason) pendingErrors.push(currentError.reason.cause);
79705
+ }
79706
+ return null;
79707
+ };
79708
+ const isEnvironmentError = (error) => findEnvironmentError(error) !== null;
79709
+ const formatEnvironmentError = (error) => {
79710
+ const environmentError = findEnvironmentError(error);
79711
+ if (!environmentError) return messageFromUnknown(error);
79712
+ switch (environmentError.code) {
79713
+ case "EBUSY": return environmentError.path ? `Resource busy or locked at ${environmentError.path}. Close the process using it and try again.` : "Resource busy or locked. Close the process using it and try again.";
79714
+ case "ENOSPC": return "No space left on device. Free up disk space and try again.";
79715
+ case "EIO": return "I/O error: the filesystem or disk may be failing. Check your system logs.";
79716
+ case "EROFS": return "Read-only filesystem: cannot write to this location.";
79717
+ case "EACCES":
79718
+ case "EPERM": return environmentError.path ? `Permission denied accessing ${environmentError.path}. Check file permissions and try again.` : "Permission denied. Check file permissions and try again.";
79719
+ case "ENOTDIR": return environmentError.path ? `A file exists at ${environmentError.path} or one of its parent paths where a directory was expected.` : "A file exists where a directory was expected.";
79720
+ case "ENOENT": return "Required command not found. Ensure the tool (e.g. git) is installed and on your PATH.";
79721
+ case "ETIMEDOUT": return environmentError.path ? `Timed out accessing ${environmentError.path}. Check the network or cloud drive and try again.` : "Filesystem access timed out. Check the network or cloud drive and try again.";
79722
+ case "UNKNOWN": return environmentError.path ? `The operating system could not access ${environmentError.path}. Check the filesystem and try again.` : "The operating system could not access the filesystem. Check it and try again.";
79723
+ default: return environmentError.message;
79724
+ }
79725
+ };
79726
+ //#endregion
79727
+ //#region src/cli/utils/is-expected-user-error.ts
79728
+ /**
79729
+ * Whether `error` is an expected, user-actionable failure — the user's project
79730
+ * or input, not a react-doctor bug. Such failures must be kept out of crash
79731
+ * reporting (Sentry + the alertable error-rate metric) and rendered via
79732
+ * `handleUserError` (a plain message — no "Something went wrong", prefilled
79733
+ * issue, Discord link, or Sentry reference), since there is no bug to report.
79734
+ *
79735
+ * Four distinct shapes reach the CLI's catch blocks:
79736
+ *
79737
+ * - **Project-discovery failures** (`NoReactDependencyError`,
79738
+ * `ProjectNotFoundError`, `PackageJsonNotFoundError`, `NotADirectoryError`,
79739
+ * `AmbiguousProjectError`) arrive as their plain legacy classes (so
79740
+ * `isReactDoctorError` is `false` for them) — narrow with
79741
+ * `isProjectDiscoveryError`. Running react-doctor against a directory that
79742
+ * has no React, or a path that doesn't exist, is the canonical example.
79743
+ * - **CLI invocation mistakes** (`CliInputError`): a malformed
79744
+ * `<file>:<line>` argument, mutually exclusive flags, or an unknown
79745
+ * `--project` name.
79746
+ * - **Bad `--diff` input** (`GitBaseBranchInvalid` / `GitBaseBranchMissing`)
79747
+ * stays the tagged `ReactDoctorError`, so dispatch on the reason `_tag`.
79748
+ * - **Environment failures** (`ENOSPC`, `EIO`, `EROFS`, `EACCES`, `EPERM`,
79749
+ * `ENOTDIR`, plus a `spawn`-scoped `ENOENT` for a missing binary) — disk
79750
+ * full / failing / read-only, permission denied, or a path blocked by a
79751
+ * file. React Doctor cannot fix the user's environment; exit cleanly with an
79752
+ * actionable message instead of crashing. See `is-environment-error.ts` for
79753
+ * why the set stays narrow (codes that usually mean our bug keep reaching
79754
+ * Sentry).
79755
+ *
79756
+ * This composes the existing core narrowers rather than introducing a new
79757
+ * error-shape helper (AGENTS.md): it encodes CLI-layer reporting policy, not
79758
+ * knowledge of the `ReactDoctorError` shape.
79759
+ */
79760
+ const isExpectedUserError = (error) => error instanceof CliInputError || isProjectDiscoveryError(error) || isEnvironmentError(error) || isReactDoctorError(error) && (error.reason._tag === "GitBaseBranchInvalid" || error.reason._tag === "GitBaseBranchMissing");
79761
+ //#endregion
79616
79762
  //#region src/cli/utils/anonymize-text.ts
79617
79763
  /**
79618
79764
  * Free-text fields can carry both a home-directory path (the OS username) and a
@@ -79734,13 +79880,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
79734
79880
  * uploads source-map artifacts under, so stack frames symbolicate. Honors the
79735
79881
  * standard `SENTRY_RELEASE` override.
79736
79882
  */
79737
- const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.9.3-dev.f958625`;
79883
+ const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.9.4-dev.212b8b4`;
79738
79884
  /**
79739
79885
  * Deployment environment shown in Sentry's environment filter. Defaults to
79740
79886
  * `production` for tagged releases and `development` for dev/unbuilt versions,
79741
79887
  * overridable via the standard `SENTRY_ENVIRONMENT` env var.
79742
79888
  */
79743
- const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.9.3-dev.f958625") ? "development" : "production");
79889
+ const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.9.4-dev.212b8b4") ? "development" : "production");
79744
79890
  /**
79745
79891
  * Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
79746
79892
  * (set to `0` to disable tracing) and falls back to
@@ -79805,21 +79951,25 @@ const initializeSentry = () => {
79805
79951
  isInitialized = true;
79806
79952
  resolvedTracesSampleRate = isDebugFlagEnabled() ? 1 : resolveTracesSampleRate();
79807
79953
  const { tags, contexts } = buildSentryScope();
79808
- Sentry.init({
79809
- dsn: process.env.SENTRY_DSN || "https://f253d570240a59b8dbd77b7a548ef133@o4510226365743104.ingest.us.sentry.io/4511487817809920",
79810
- release: resolveSentryRelease(),
79811
- environment: resolveSentryEnvironment(),
79812
- sendDefaultPii: false,
79813
- tracesSampleRate: resolvedTracesSampleRate,
79814
- debug: isEnvFlagEnabled(process.env.SENTRY_DEBUG),
79815
- initialScope: {
79816
- tags,
79817
- contexts
79818
- },
79819
- beforeSend: (event) => scrubSentryEvent(event),
79820
- beforeSendTransaction: (event) => scrubSentryEvent(event),
79821
- beforeSendMetric: (metric) => scrubSentryMetric(metric)
79822
- });
79954
+ try {
79955
+ Sentry.init({
79956
+ dsn: process.env.SENTRY_DSN || "https://f253d570240a59b8dbd77b7a548ef133@o4510226365743104.ingest.us.sentry.io/4511487817809920",
79957
+ release: resolveSentryRelease(),
79958
+ environment: resolveSentryEnvironment(),
79959
+ sendDefaultPii: false,
79960
+ tracesSampleRate: resolvedTracesSampleRate,
79961
+ debug: isEnvFlagEnabled(process.env.SENTRY_DEBUG),
79962
+ initialScope: {
79963
+ tags,
79964
+ contexts
79965
+ },
79966
+ beforeSend: (event, hint) => isExpectedUserError(hint.originalException) ? null : scrubSentryEvent(event),
79967
+ beforeSendTransaction: (event) => scrubSentryEvent(event),
79968
+ beforeSendMetric: (metric) => scrubSentryMetric(metric)
79969
+ });
79970
+ } catch {
79971
+ resolvedTracesSampleRate = 0;
79972
+ }
79823
79973
  };
79824
79974
  //#endregion
79825
79975
  //#region src/cli/utils/cli-logger.ts
@@ -91134,34 +91284,6 @@ const runCiConfig = async (options = {}) => {
91134
91284
  });
91135
91285
  };
91136
91286
  //#endregion
91137
- //#region src/cli/utils/is-environment-error.ts
91138
- const ENVIRONMENT_ERROR_CODES = new Set([
91139
- "ENOSPC",
91140
- "EIO",
91141
- "EROFS",
91142
- "EACCES",
91143
- "EPERM",
91144
- "ENOTDIR"
91145
- ]);
91146
- const isEnvironmentError = (error) => {
91147
- if (!isErrnoException(error) || typeof error.code !== "string") return false;
91148
- if (error.code === "ENOENT") return error.syscall?.startsWith("spawn") ?? false;
91149
- return ENVIRONMENT_ERROR_CODES.has(error.code);
91150
- };
91151
- const formatEnvironmentError = (error) => {
91152
- if (!isErrnoException(error)) return messageFromUnknown(error);
91153
- switch (error.code) {
91154
- case "ENOSPC": return "No space left on device. Free up disk space and try again.";
91155
- case "EIO": return "I/O error: the filesystem or disk may be failing. Check your system logs.";
91156
- case "EROFS": return "Read-only filesystem: cannot write to this location.";
91157
- case "EACCES":
91158
- case "EPERM": return error.path ? `Permission denied accessing ${error.path}. Check file permissions and try again.` : "Permission denied. Check file permissions and try again.";
91159
- case "ENOTDIR": return error.path ? `A file exists at ${error.path} or one of its parent paths where a directory was expected.` : "A file exists where a directory was expected.";
91160
- case "ENOENT": return "Required command not found. Ensure the tool (e.g. git) is installed and on your PATH.";
91161
- default: return error.message;
91162
- }
91163
- };
91164
- //#endregion
91165
91287
  //#region src/cli/utils/handle-error.ts
91166
91288
  const OTLP_ENDPOINT_ENVIRONMENT_VARIABLE = "REACT_DOCTOR_OTLP_ENDPOINT";
91167
91289
  const OTLP_AUTH_HEADER_ENVIRONMENT_VARIABLE = "REACT_DOCTOR_OTLP_AUTH_HEADER";
@@ -91263,56 +91385,6 @@ const handleUserError = (error, options = {}) => {
91263
91385
  process.exitCode = 1;
91264
91386
  };
91265
91387
  //#endregion
91266
- //#region src/cli/utils/cli-input-error.ts
91267
- /**
91268
- * A mistake in *how the CLI was invoked* — a malformed `<file>:<line>`
91269
- * argument, mutually exclusive flags, or an unknown `--project` name. This is
91270
- * user input, not a react-doctor bug, so `isExpectedUserError` routes it
91271
- * through `handleUserError`: a clean, single-line message with no Sentry
91272
- * report and no "Something went wrong, open a prefilled issue" block.
91273
- *
91274
- * The `message` is rendered verbatim, so it must read as a complete,
91275
- * human-readable sentence that tells the user how to fix their invocation.
91276
- */
91277
- var CliInputError = class extends Error {
91278
- name = "CliInputError";
91279
- };
91280
- //#endregion
91281
- //#region src/cli/utils/is-expected-user-error.ts
91282
- /**
91283
- * Whether `error` is an expected, user-actionable failure — the user's project
91284
- * or input, not a react-doctor bug. Such failures must be kept out of crash
91285
- * reporting (Sentry + the alertable error-rate metric) and rendered via
91286
- * `handleUserError` (a plain message — no "Something went wrong", prefilled
91287
- * issue, Discord link, or Sentry reference), since there is no bug to report.
91288
- *
91289
- * Four distinct shapes reach the CLI's catch blocks:
91290
- *
91291
- * - **Project-discovery failures** (`NoReactDependencyError`,
91292
- * `ProjectNotFoundError`, `PackageJsonNotFoundError`, `NotADirectoryError`,
91293
- * `AmbiguousProjectError`) arrive as their plain legacy classes (so
91294
- * `isReactDoctorError` is `false` for them) — narrow with
91295
- * `isProjectDiscoveryError`. Running react-doctor against a directory that
91296
- * has no React, or a path that doesn't exist, is the canonical example.
91297
- * - **CLI invocation mistakes** (`CliInputError`): a malformed
91298
- * `<file>:<line>` argument, mutually exclusive flags, or an unknown
91299
- * `--project` name.
91300
- * - **Bad `--diff` input** (`GitBaseBranchInvalid` / `GitBaseBranchMissing`)
91301
- * stays the tagged `ReactDoctorError`, so dispatch on the reason `_tag`.
91302
- * - **Environment failures** (`ENOSPC`, `EIO`, `EROFS`, `EACCES`, `EPERM`,
91303
- * `ENOTDIR`, plus a `spawn`-scoped `ENOENT` for a missing binary) — disk
91304
- * full / failing / read-only, permission denied, or a path blocked by a
91305
- * file. React Doctor cannot fix the user's environment; exit cleanly with an
91306
- * actionable message instead of crashing. See `is-environment-error.ts` for
91307
- * why the set stays narrow (codes that usually mean our bug keep reaching
91308
- * Sentry).
91309
- *
91310
- * This composes the existing core narrowers rather than introducing a new
91311
- * error-shape helper (AGENTS.md): it encodes CLI-layer reporting policy, not
91312
- * knowledge of the `ReactDoctorError` shape.
91313
- */
91314
- const isExpectedUserError = (error) => error instanceof CliInputError || isProjectDiscoveryError(error) || isEnvironmentError(error) || isReactDoctorError(error) && (error.reason._tag === "GitBaseBranchInvalid" || error.reason._tag === "GitBaseBranchMissing");
91315
- //#endregion
91316
91388
  //#region src/cli/utils/print-branded-header.ts
91317
91389
  /**
91318
91390
  * Single branded line every command prints first when not in JSON
@@ -97285,16 +97357,16 @@ const inspectAction = async (directory, flags, invocationCommand = "inspect") =>
97285
97357
  const requestedDirectory = Path.resolve(directory);
97286
97358
  const startTime = performance$1.now();
97287
97359
  let scanStartupSpinner = null;
97288
- if (isJsonMode) {
97289
- enableJsonMode({
97290
- compact: Boolean(flags.jsonCompact),
97291
- directory: requestedDirectory,
97292
- outputFile: flags.jsonOut
97293
- });
97294
- if (flags.jsonOut) recordCount(METRIC.jsonOutUsed, 1);
97295
- }
97296
- recordCount(METRIC.cliInvoked, 1, { command: invocationCommand });
97297
97360
  try {
97361
+ if (isJsonMode) {
97362
+ enableJsonMode({
97363
+ compact: Boolean(flags.jsonCompact),
97364
+ directory: requestedDirectory,
97365
+ outputFile: flags.jsonOut
97366
+ });
97367
+ if (flags.jsonOut) recordCount(METRIC.jsonOutUsed, 1);
97368
+ }
97369
+ recordCount(METRIC.cliInvoked, 1, { command: invocationCommand });
97298
97370
  validateModeFlags(flags);
97299
97371
  if (flags.staged) setJsonReportMode("staged");
97300
97372
  await maybeMigrateLegacyConfig(requestedDirectory, {
@@ -97848,7 +97920,7 @@ const runScanCommand = async (input) => {
97848
97920
  return;
97849
97921
  }
97850
97922
  recordCount(METRIC.cliInvoked, 1, { command: input.invocationCommand });
97851
- const { runScanApp } = await import("./run-scan-app-QbtG43NL.js");
97923
+ const { runScanApp } = await import("./run-scan-app-C8iWVgvR.js");
97852
97924
  const { shouldFail } = await runScanApp({
97853
97925
  directory: input.directory,
97854
97926
  scanTarget,
@@ -98981,6 +99053,9 @@ program.command("experimental-tui [directory]", { hidden: true }).description("[
98981
99053
  process.stdout.on("error", (error) => {
98982
99054
  if (error.code === "EPIPE") process.exit(0);
98983
99055
  });
99056
+ process.stderr.on("error", (error) => {
99057
+ if (error.code === "EPIPE") process.exit(0);
99058
+ });
98984
99059
  const knownCommands = program.commands.flatMap((command) => [command.name(), ...command.aliases()]);
98985
99060
  const strippedArgv = stripUnknownCliFlags(process.argv);
98986
99061
  if (process.argv.includes("-V") && !strippedArgv.includes("-V")) {
@@ -99007,4 +99082,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
99007
99082
  export { TUI_REPORT_DETAIL_WIDTH_FRACTION as $, shouldRecordOnboarding as A, buildSortedRuleGroups as B, createInvocationInspect as C, markOnboardingComplete as D, doctorFace as E, buildEmptyReportMessage as F, recordCount as G, formatLearnMoreLine as H, hasIncompleteScoreAnalysis as I, findNearestPackageDirectory as J, recordDistribution as K, buildNoScoreMessage as L, computeProjectedScore as M, formatElapsedTime as N, canAnimateOnboarding as O, buildCodeFrame as P, TUI_REPORT_COMPACT_MAX_ROWS as Q, hasLintHardFailure as R, deduplicateProjectScans as S, easeOutCubic as T, resolveBlockingLevel as U, formatFixRecipeLine as V, isReactDoctorWorkflowInstalled as W, CI_TRUST_COMPANIES as X, isUnicodeSupported as Y, METRIC as Z, launchCliAgent as _, succeed$3 as _t, selectProjects as a, buildRuleDocsUrl as at, formatSkippedProjectsMessage as b, resolveWorkspaceDeadCodeOwner as c, highlighter as ct, countUniqueScannedFiles as d, remainingDeadlineBudgetMs as dt, isCiEnvironment as et, setUpGitHubActions as f, resolveScanTarget as ft, copyToClipboard as g, void_ as gt, CLI_AGENT_BINARIES as h, sync as ht, discoverWorkspacePackages as i, Reporter as it, filterDiagnosticsByCategories as j, isOnboardingForced as k, resolveCliInspectOptions as l, isPathInsideDirectory as lt, detectLaunchableAgents as m, runPromise as mt, registerActiveTuiRenderer as n, GITHUB_ACTIONS_SETUP_URL as nt, resolveProjectScan as o, getCategoryImpact as ot, openUrl as p, yieldToEventLoop as pt, toForwardSlashes as q, shouldFailScanGate as r, Progress as rt, retryMissingProjectScores as s, hasPublishedFixRecipe as st, exitGracefully as t, DIAGNOSTIC_CATEGORY_BUCKETS as tt, filterScansForSurface as u, mapWithConcurrency as ut, buildHandoffPayload as v, printFooter as w, collectProjectSourceFileCounts as x, isShareOptedOut as y, buildRulePriorityMap as z };
99008
99083
 
99009
99084
  //# sourceMappingURL=cli.js.map
99010
- //# debugId=2b3a9fe9-8109-5067-854f-a10b2e6454e7
99085
+ //# debugId=7d7e0bde-eb19-58b5-b161-625bcb1b2588
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { FrameworkToken } from "oxlint-plugin-react-doctor/core";
2
+ import fs from "node:fs";
2
3
 
3
4
  //#region ../../node_modules/.pnpm/effect@4.0.0-beta.70/node_modules/effect/dist/Types.d.ts
4
5
  /**