react-doctor 0.5.8-dev.c2ce298 → 0.5.8-dev.ea00b1b

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]="b0741e5a-8791-58ae-be3e-0ea33c204326")}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]="7e6ea254-9fcd-5076-8bd6-01ad63633c24")}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";
@@ -36862,6 +36862,7 @@ const DOCS_URL = "https://react.doctor/docs";
36862
36862
  const DOCS_RULES_BASE_URL = `${DOCS_URL}/rules`;
36863
36863
  const FETCH_TIMEOUT_MS = 1e4;
36864
36864
  const GITHUB_VIEWER_PERMISSION_TIMEOUT_MS = 2e3;
36865
+ const SPAWN_ARGS_MAX_LENGTH_CHARS = 24e3;
36865
36866
  const PER_WORKER_MEM_BUDGET_BYTES = 1024 * 1024 * 1024;
36866
36867
  const DEFAULT_BRANCH_CANDIDATES = ["main", "master"];
36867
36868
  const ADOPTABLE_LINT_CONFIG_FILENAMES = [".oxlintrc.json", ".eslintrc.json"];
@@ -38768,6 +38769,12 @@ const BOOLEAN_FIELD_NAMES = [
38768
38769
  "adoptExistingLintConfig"
38769
38770
  ];
38770
38771
  const STRING_FIELD_NAMES = ["rootDir"];
38772
+ const STRING_ARRAY_FIELD_NAMES = [
38773
+ "projects",
38774
+ "textComponents",
38775
+ "rawTextWrapperComponents",
38776
+ "serverAuthFunctionNames"
38777
+ ];
38771
38778
  const SURFACE_CONTROL_FIELD_NAMES = [
38772
38779
  "includeTags",
38773
38780
  "excludeTags",
@@ -38869,6 +38876,7 @@ const validateConfigTypes = (config) => {
38869
38876
  const validated = { ...config };
38870
38877
  for (const fieldName of BOOLEAN_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => coerceMaybeBooleanString(fieldName, value));
38871
38878
  for (const fieldName of STRING_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => validateString(fieldName, value));
38879
+ for (const fieldName of STRING_ARRAY_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => validateStringArrayField(fieldName, value));
38872
38880
  applyFieldValidator(config, validated, "surfaces", validateSurfacesField);
38873
38881
  for (const fieldName of SEVERITY_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => validateSeverityMap(fieldName, value, fieldName === "categories"));
38874
38882
  applyFieldValidator(config, validated, "plugins", (value) => validateStringArrayField("plugins", value));
@@ -41097,43 +41105,46 @@ var Git = class Git extends Service()("react-doctor/Git") {
41097
41105
  * reason: GitInvocationFailed })` so the rest of the codebase
41098
41106
  * sees a single failure channel.
41099
41107
  */
41100
- const runCommand = (input) => scoped(gen(function* () {
41101
- const handle = yield* spawner.spawn(make$1(input.command, [...input.args], {
41102
- cwd: input.directory,
41103
- env: input.env,
41104
- extendEnv: true
41105
- }));
41106
- const maxStdoutBytes = input.maxStdoutBytes;
41107
- const stdoutByteCount = yield* make$13(0);
41108
- const [stdout, stderr, status] = yield* all([
41109
- mkString(decodeText(maxStdoutBytes === void 0 ? handle.stdout : handle.stdout.pipe(tap((chunk) => updateAndGet(stdoutByteCount, (total) => total + chunk.length).pipe(flatMap$2((total) => total > maxStdoutBytes ? fail$4(new ReactDoctorError({ reason: new GitInvocationFailed({
41110
- args: [...input.args],
41111
- directory: input.directory,
41112
- cause: /* @__PURE__ */ new Error(`git stdout exceeded ${maxStdoutBytes} bytes`)
41113
- }) })) : void_)))))),
41114
- mkString(decodeText(handle.stderr)),
41115
- handle.exitCode
41116
- ], { concurrency: 3 });
41117
- return {
41118
- status,
41119
- stdout,
41120
- stderr
41121
- };
41122
- })).pipe(catchTag$1("PlatformError", (cause) => {
41123
- if (input.command !== "git") return succeed$2({
41108
+ const runCommand = (input) => {
41109
+ const foldSpawnFailure = (cause) => input.command !== "git" ? succeed$2({
41124
41110
  status: 127,
41125
41111
  stdout: "",
41126
41112
  stderr: String(cause)
41127
- });
41128
- return new ReactDoctorError({ reason: new GitInvocationFailed({
41113
+ }) : fail$4(new ReactDoctorError({ reason: new GitInvocationFailed({
41129
41114
  args: [...input.args],
41130
41115
  directory: input.directory,
41131
41116
  cause
41132
- }) });
41133
- }), withSpan("git.exec", { attributes: {
41134
- "git.command": input.command,
41135
- "git.subcommand": input.args[0] ?? ""
41136
- } }));
41117
+ }) }));
41118
+ return scoped(gen(function* () {
41119
+ if (!isDirectory(input.directory)) return yield* foldSpawnFailure(`spawn ENOTDIR (cwd is not a directory: ${input.directory})`);
41120
+ const argvLengthChars = input.command.length + 1 + input.args.reduce((total, arg) => total + arg.length + 1, 0);
41121
+ if (argvLengthChars > 24e3) return yield* foldSpawnFailure(`spawn ENAMETOOLONG (${argvLengthChars} argv chars exceed ${SPAWN_ARGS_MAX_LENGTH_CHARS})`);
41122
+ const handle = yield* spawner.spawn(make$1(input.command, [...input.args], {
41123
+ cwd: input.directory,
41124
+ env: input.env,
41125
+ extendEnv: true
41126
+ }));
41127
+ const maxStdoutBytes = input.maxStdoutBytes;
41128
+ const stdoutByteCount = yield* make$13(0);
41129
+ const [stdout, stderr, status] = yield* all([
41130
+ mkString(decodeText(maxStdoutBytes === void 0 ? handle.stdout : handle.stdout.pipe(tap((chunk) => updateAndGet(stdoutByteCount, (total) => total + chunk.length).pipe(flatMap$2((total) => total > maxStdoutBytes ? fail$4(new ReactDoctorError({ reason: new GitInvocationFailed({
41131
+ args: [...input.args],
41132
+ directory: input.directory,
41133
+ cause: /* @__PURE__ */ new Error(`git stdout exceeded ${maxStdoutBytes} bytes`)
41134
+ }) })) : void_)))))),
41135
+ mkString(decodeText(handle.stderr)),
41136
+ handle.exitCode
41137
+ ], { concurrency: 3 });
41138
+ return {
41139
+ status,
41140
+ stdout,
41141
+ stderr
41142
+ };
41143
+ })).pipe(catchTag$1("PlatformError", foldSpawnFailure), withSpan("git.exec", { attributes: {
41144
+ "git.command": input.command,
41145
+ "git.subcommand": input.args[0] ?? ""
41146
+ } }));
41147
+ };
41137
41148
  const runGit = (directory, args) => runCommand({
41138
41149
  command: "git",
41139
41150
  args,
@@ -41166,7 +41177,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
41166
41177
  "rev-parse",
41167
41178
  "--verify",
41168
41179
  branch
41169
- ]).pipe(map$3((result) => result.status === 0));
41180
+ ]).pipe(map$3((result) => result.status === 0), catch_$1((error) => error.reason._tag === "GitInvocationFailed" ? succeed$2(false) : fail$4(error)));
41170
41181
  const headSha = (directory) => runGit(directory, ["rev-parse", "HEAD"]).pipe(map$3((result) => result.status === 0 ? trimOrNull(result.stdout) : null));
41171
41182
  const mergeBase = (input) => isSafeGitRevision(input.ref) ? runGit(input.directory, [
41172
41183
  "merge-base",
@@ -41380,7 +41391,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
41380
41391
  ]);
41381
41392
  if (result.status !== 0) return null;
41382
41393
  return parseChangedLineRanges(result.stdout);
41383
- }).pipe(withSpan("Git.changedLineRanges"))
41394
+ }).pipe(catch_$1((error) => error.reason._tag === "GitInvocationFailed" ? succeed$2(null) : fail$4(error)), withSpan("Git.changedLineRanges"))
41384
41395
  });
41385
41396
  })).pipe(provide$2(layer$3.pipe(provide$2(mergeAll$1(layer$2, layer$1)))));
41386
41397
  /**
@@ -44842,6 +44853,7 @@ const NANOSECONDS_PER_SECOND = 1000000000n;
44842
44853
  const METRIC = {
44843
44854
  cliInvoked: "cli.invoked",
44844
44855
  cliError: "cli.error",
44856
+ cliEnvironmentError: "cli.env_error",
44845
44857
  projectDetected: "project.detected",
44846
44858
  projectPathSelected: "project.path_selected",
44847
44859
  projectConfigSelected: "project.config_selected",
@@ -44914,7 +44926,7 @@ const makeNoopConsole = () => ({
44914
44926
  });
44915
44927
  //#endregion
44916
44928
  //#region src/cli/utils/version.ts
44917
- const VERSION = "0.5.8-dev.c2ce298";
44929
+ const VERSION = "0.5.8-dev.ea00b1b";
44918
44930
  //#endregion
44919
44931
  //#region src/cli/utils/json-mode.ts
44920
44932
  let context = null;
@@ -45278,13 +45290,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
45278
45290
  * uploads source-map artifacts under, so stack frames symbolicate. Honors the
45279
45291
  * standard `SENTRY_RELEASE` override.
45280
45292
  */
45281
- const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.5.8-dev.c2ce298`;
45293
+ const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.5.8-dev.ea00b1b`;
45282
45294
  /**
45283
45295
  * Deployment environment shown in Sentry's environment filter. Defaults to
45284
45296
  * `production` for tagged releases and `development` for dev/unbuilt versions,
45285
45297
  * overridable via the standard `SENTRY_ENVIRONMENT` env var.
45286
45298
  */
45287
- const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.5.8-dev.c2ce298") ? "development" : "production");
45299
+ const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.5.8-dev.ea00b1b") ? "development" : "production");
45288
45300
  /**
45289
45301
  * Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
45290
45302
  * (set to `0` to disable tracing) and falls back to
@@ -50357,7 +50369,7 @@ const readPackageJson = (projectRoot) => {
50357
50369
  return null;
50358
50370
  }
50359
50371
  };
50360
- const writeJsonFile$1 = (filePath, value) => {
50372
+ const writeJsonFile = (filePath, value) => {
50361
50373
  NFS.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`);
50362
50374
  };
50363
50375
  const packageHasDependency = (projectRoot, dependencyName) => {
@@ -51096,6 +51108,35 @@ const materializeStagedFiles = async (directory, stagedFiles, tempDirectory) =>
51096
51108
  };
51097
51109
  };
51098
51110
  //#endregion
51111
+ //#region src/cli/utils/is-environment-error.ts
51112
+ const isNodeSystemError = (error) => error instanceof Error && typeof error.code === "string";
51113
+ const ENVIRONMENT_ERROR_CODES = new Set([
51114
+ "ENOSPC",
51115
+ "EIO",
51116
+ "EROFS",
51117
+ "EACCES",
51118
+ "EPERM",
51119
+ "ENOTDIR"
51120
+ ]);
51121
+ const isEnvironmentError = (error) => {
51122
+ if (!isNodeSystemError(error)) return false;
51123
+ if (error.code === "ENOENT") return error.syscall?.startsWith("spawn") ?? false;
51124
+ return error.code !== void 0 && ENVIRONMENT_ERROR_CODES.has(error.code);
51125
+ };
51126
+ const formatEnvironmentError = (error) => {
51127
+ if (!isNodeSystemError(error)) return error instanceof Error ? error.message : String(error);
51128
+ switch (error.code) {
51129
+ case "ENOSPC": return "No space left on device. Free up disk space and try again.";
51130
+ case "EIO": return "I/O error: the filesystem or disk may be failing. Check your system logs.";
51131
+ case "EROFS": return "Read-only filesystem: cannot write to this location.";
51132
+ case "EACCES":
51133
+ case "EPERM": return error.path ? `Permission denied accessing ${error.path}. Check file permissions and try again.` : "Permission denied. Check file permissions and try again.";
51134
+ 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.";
51135
+ case "ENOENT": return "Required command not found. Ensure the tool (e.g. git) is installed and on your PATH.";
51136
+ default: return error.message;
51137
+ }
51138
+ };
51139
+ //#endregion
51099
51140
  //#region src/cli/utils/handle-error.ts
51100
51141
  const OTLP_ENDPOINT_ENVIRONMENT_VARIABLE = "REACT_DOCTOR_OTLP_ENDPOINT";
51101
51142
  const OTLP_AUTH_HEADER_ENVIRONMENT_VARIABLE = "REACT_DOCTOR_OTLP_AUTH_HEADER";
@@ -51178,15 +51219,19 @@ const handleError = (error, options = {}) => {
51178
51219
  process.exitCode = 1;
51179
51220
  };
51180
51221
  /**
51181
- * Renderer for expected, user-actionable failures — a bad `--diff` value or
51182
- * a base branch that isn't fetched. Prints just the (already human-readable)
51183
- * message no "Something went wrong", prefilled issue, Discord link, or
51184
- * Sentry reference because there is no bug to report.
51222
+ * Renderer for expected, user-actionable failures — a bad `--diff` value,
51223
+ * a base branch that isn't fetched, or environment errors like disk-full or
51224
+ * permission-denied. Prints just the (already human-readable) message no
51225
+ * "Something went wrong", prefilled issue, Discord link, or Sentry reference
51226
+ * — because there is no bug to report.
51185
51227
  */
51186
51228
  const handleUserError = (error, options = {}) => {
51229
+ const isEnvError = isEnvironmentError(error);
51230
+ if (isEnvError) recordCount(METRIC.cliEnvironmentError, 1, { code: error.code ?? "unknown" });
51231
+ const message = isEnvError ? formatEnvironmentError(error) : formatErrorForReport(error);
51187
51232
  runSync(gen(function* () {
51188
51233
  yield* error$1("");
51189
- yield* error$1(highlighter.error(formatErrorForReport(error)));
51234
+ yield* error$1(highlighter.error(message));
51190
51235
  yield* error$1("");
51191
51236
  }));
51192
51237
  if (options.shouldExit !== false) process.exit(1);
@@ -51201,7 +51246,7 @@ const handleUserError = (error, options = {}) => {
51201
51246
  * `handleUserError` (a plain message — no "Something went wrong", prefilled
51202
51247
  * issue, Discord link, or Sentry reference), since there is no bug to report.
51203
51248
  *
51204
- * Three distinct shapes reach the CLI's catch blocks:
51249
+ * Four distinct shapes reach the CLI's catch blocks:
51205
51250
  *
51206
51251
  * - **Project-discovery failures** (`NoReactDependencyError`,
51207
51252
  * `ProjectNotFoundError`, `PackageJsonNotFoundError`, `NotADirectoryError`,
@@ -51214,12 +51259,19 @@ const handleUserError = (error, options = {}) => {
51214
51259
  * `--project` name.
51215
51260
  * - **Bad `--diff` input** (`GitBaseBranchInvalid` / `GitBaseBranchMissing`)
51216
51261
  * stays the tagged `ReactDoctorError`, so dispatch on the reason `_tag`.
51262
+ * - **Environment failures** (`ENOSPC`, `EIO`, `EROFS`, `EACCES`, `EPERM`,
51263
+ * `ENOTDIR`, plus a `spawn`-scoped `ENOENT` for a missing binary) — disk
51264
+ * full / failing / read-only, permission denied, or a path blocked by a
51265
+ * file. React Doctor cannot fix the user's environment; exit cleanly with an
51266
+ * actionable message instead of crashing. See `is-environment-error.ts` for
51267
+ * why the set stays narrow (codes that usually mean our bug keep reaching
51268
+ * Sentry).
51217
51269
  *
51218
51270
  * This composes the existing core narrowers rather than introducing a new
51219
51271
  * error-shape helper (AGENTS.md): it encodes CLI-layer reporting policy, not
51220
51272
  * knowledge of the `ReactDoctorError` shape.
51221
51273
  */
51222
- const isExpectedUserError = (error) => error instanceof CliInputError || isProjectDiscoveryError(error) || isReactDoctorError(error) && (error.reason._tag === "GitBaseBranchInvalid" || error.reason._tag === "GitBaseBranchMissing");
51274
+ const isExpectedUserError = (error) => error instanceof CliInputError || isProjectDiscoveryError(error) || isEnvironmentError(error) || isReactDoctorError(error) && (error.reason._tag === "GitBaseBranchInvalid" || error.reason._tag === "GitBaseBranchMissing");
51223
51275
  //#endregion
51224
51276
  //#region src/cli/utils/build-handoff-payload.ts
51225
51277
  const buildHandoffPayload = (input) => {
@@ -51395,7 +51447,7 @@ const installDoctorScript = (options) => {
51395
51447
  };
51396
51448
  })();
51397
51449
  const scriptStatus = scriptTarget.status;
51398
- if (scriptStatus === "created") writeJsonFile$1(packageJsonPath, {
51450
+ if (scriptStatus === "created") writeJsonFile(packageJsonPath, {
51399
51451
  ...packageJson,
51400
51452
  scripts: {
51401
51453
  ...isRecord$1(scripts) ? scripts : {},
@@ -51989,20 +52041,34 @@ const CURSOR_HOOKS_RELATIVE_PATH = ".cursor/hooks.json";
51989
52041
  const CURSOR_HOOK_RELATIVE_PATH = ".cursor/hooks/react-doctor.sh";
51990
52042
  const CURSOR_HOOK_MATCHER = "Write|Edit|MultiEdit|ApplyPatch";
51991
52043
  const CURSOR_HOOKS_SCHEMA_VERSION = 1;
51992
- const JSON_INDENT_SPACES$1 = 2;
51993
52044
  const isSupportedAgent = (agent) => agent === CLAUDE_AGENT || agent === CURSOR_AGENT;
51994
52045
  const readJsonFile = (filePath, fallback) => {
51995
52046
  if (!NFS.existsSync(filePath)) return fallback;
51996
52047
  const content = NFS.readFileSync(filePath, "utf8").trim();
51997
52048
  if (content.length === 0) return fallback;
51998
- return JSON.parse(content);
52049
+ try {
52050
+ return JSON.parse(content);
52051
+ } catch (error) {
52052
+ if (error instanceof SyntaxError) throw new CliInputError(`Could not parse ${filePath}: the file contains invalid JSON. Fix the syntax errors in this file and re-run the install command.`);
52053
+ throw error;
52054
+ }
51999
52055
  };
52000
- const writeJsonFile = (filePath, value) => {
52001
- NFS.mkdirSync(Path.dirname(filePath), { recursive: true });
52002
- NFS.writeFileSync(filePath, `${JSON.stringify(value, null, JSON_INDENT_SPACES$1)}\n`);
52056
+ const ensureDirectoryExists = (directoryPath) => {
52057
+ try {
52058
+ NFS.mkdirSync(directoryPath, { recursive: true });
52059
+ } catch (error) {
52060
+ const code = error.code;
52061
+ if (code === "EACCES" || code === "EPERM") throw new CliInputError(`Could not create directory ${directoryPath}: permission denied. Ensure you have write permissions for this location and re-run the install command.`);
52062
+ if (code === "ENOTDIR" || code === "EEXIST") throw new CliInputError(`Could not create directory ${directoryPath}: a file exists at this path or one of its parent paths. Remove the conflicting file and re-run the install command.`);
52063
+ throw error;
52064
+ }
52065
+ };
52066
+ const writeJsonFileWithDirectoryCheck = (filePath, value) => {
52067
+ ensureDirectoryExists(Path.dirname(filePath));
52068
+ writeJsonFile(filePath, value);
52003
52069
  };
52004
52070
  const writeHookScript = (filePath) => {
52005
- NFS.mkdirSync(Path.dirname(filePath), { recursive: true });
52071
+ ensureDirectoryExists(Path.dirname(filePath));
52006
52072
  NFS.writeFileSync(filePath, buildAgentHookScript());
52007
52073
  NFS.chmodSync(filePath, 493);
52008
52074
  };
@@ -52018,7 +52084,7 @@ const installClaudeHook = (projectRoot) => {
52018
52084
  command: CLAUDE_HOOK_COMMAND
52019
52085
  }] });
52020
52086
  hooks.PostToolBatch = postToolBatchHooks;
52021
- writeJsonFile(settingsPath, {
52087
+ writeJsonFileWithDirectoryCheck(settingsPath, {
52022
52088
  ...settings,
52023
52089
  hooks
52024
52090
  });
@@ -52038,7 +52104,7 @@ const installCursorHook = (projectRoot) => {
52038
52104
  timeout: 120
52039
52105
  });
52040
52106
  hooks.postToolUse = postToolUseHooks;
52041
- writeJsonFile(configPath, {
52107
+ writeJsonFileWithDirectoryCheck(configPath, {
52042
52108
  ...config,
52043
52109
  version: config.version ?? CURSOR_HOOKS_SCHEMA_VERSION,
52044
52110
  hooks
@@ -52274,7 +52340,7 @@ const installPackageJsonHook = (options, strategy) => {
52274
52340
  parent = cloned;
52275
52341
  }
52276
52342
  parent[leafKey] = strategy.leafShape === "array" ? appendArrayCommand(parent[leafKey]) : appendStringCommand(parent[leafKey]);
52277
- writeJsonFile$1(packageJsonPath, nextPackageJson);
52343
+ writeJsonFile(packageJsonPath, nextPackageJson);
52278
52344
  removeLegacyManagedRunner(options.projectRoot);
52279
52345
  return {
52280
52346
  hookPath: packageJsonPath,
@@ -53713,7 +53779,7 @@ const warnDeprecatedDiff = (flags, userConfig) => {
53713
53779
  };
53714
53780
  const warnDiffUnavailable = (requested, isQuiet) => {
53715
53781
  if (isQuiet) return;
53716
- if (typeof requested.base === "string") cliLogger.warn(`Could not compute diff against "${requested.base}" (merge-base failed or HEAD has no history). Running full scan.`);
53782
+ if (typeof requested.base === "string") cliLogger.warn(`Could not compute diff against "${requested.base}" (git unavailable, ref not found, or merge-base failed). Running full scan.`);
53717
53783
  else cliLogger.warn("No feature branch or uncommitted changes detected. Running full scan.");
53718
53784
  cliLogger.break();
53719
53785
  };
@@ -54404,6 +54470,10 @@ const installAction = async (options, command) => {
54404
54470
  projectRoot: options.cwd ?? process.cwd()
54405
54471
  });
54406
54472
  } catch (error) {
54473
+ if (isExpectedUserError(error)) {
54474
+ handleUserError(error);
54475
+ return;
54476
+ }
54407
54477
  handleError(error, { sentryEventId: await reportErrorToSentry(error) });
54408
54478
  }
54409
54479
  };
@@ -55411,4 +55481,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
55411
55481
  export {};
55412
55482
 
55413
55483
  //# sourceMappingURL=cli.js.map
55414
- //# debugId=b0741e5a-8791-58ae-be3e-0ea33c204326
55484
+ //# debugId=7e6ea254-9fcd-5076-8bd6-01ad63633c24
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="fa4e432e-2004-53cc-816a-025aebf1c752")}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]="dccb6d63-63d4-5e91-af21-2756a08fe93a")}catch(e){}}();
3
3
  import { r as __toESM$1, t as __commonJSMin$1 } from "./chunk-N93fKeF6.js";
4
4
  import { createRequire } from "node:module";
5
5
  import * as NFS from "node:fs";
@@ -33694,6 +33694,7 @@ const MILLISECONDS_PER_SECOND = 1e3;
33694
33694
  const SCORE_API_URL = "https://www.react.doctor/api/score";
33695
33695
  const FETCH_TIMEOUT_MS = 1e4;
33696
33696
  const GITHUB_VIEWER_PERMISSION_TIMEOUT_MS = 2e3;
33697
+ const SPAWN_ARGS_MAX_LENGTH_CHARS = 24e3;
33697
33698
  const PER_WORKER_MEM_BUDGET_BYTES = 1024 * 1024 * 1024;
33698
33699
  const DEFAULT_BRANCH_CANDIDATES = ["main", "master"];
33699
33700
  const ADOPTABLE_LINT_CONFIG_FILENAMES = [".oxlintrc.json", ".eslintrc.json"];
@@ -35580,6 +35581,12 @@ const BOOLEAN_FIELD_NAMES = [
35580
35581
  "adoptExistingLintConfig"
35581
35582
  ];
35582
35583
  const STRING_FIELD_NAMES = ["rootDir"];
35584
+ const STRING_ARRAY_FIELD_NAMES = [
35585
+ "projects",
35586
+ "textComponents",
35587
+ "rawTextWrapperComponents",
35588
+ "serverAuthFunctionNames"
35589
+ ];
35583
35590
  const SURFACE_CONTROL_FIELD_NAMES = [
35584
35591
  "includeTags",
35585
35592
  "excludeTags",
@@ -35681,6 +35688,7 @@ const validateConfigTypes = (config) => {
35681
35688
  const validated = { ...config };
35682
35689
  for (const fieldName of BOOLEAN_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => coerceMaybeBooleanString(fieldName, value));
35683
35690
  for (const fieldName of STRING_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => validateString(fieldName, value));
35691
+ for (const fieldName of STRING_ARRAY_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => validateStringArrayField(fieldName, value));
35684
35692
  applyFieldValidator(config, validated, "surfaces", validateSurfacesField);
35685
35693
  for (const fieldName of SEVERITY_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => validateSeverityMap(fieldName, value, fieldName === "categories"));
35686
35694
  applyFieldValidator(config, validated, "plugins", (value) => validateStringArrayField("plugins", value));
@@ -37865,43 +37873,46 @@ var Git = class Git extends Service()("react-doctor/Git") {
37865
37873
  * reason: GitInvocationFailed })` so the rest of the codebase
37866
37874
  * sees a single failure channel.
37867
37875
  */
37868
- const runCommand = (input) => scoped(gen(function* () {
37869
- const handle = yield* spawner.spawn(make$1(input.command, [...input.args], {
37870
- cwd: input.directory,
37871
- env: input.env,
37872
- extendEnv: true
37873
- }));
37874
- const maxStdoutBytes = input.maxStdoutBytes;
37875
- const stdoutByteCount = yield* make$13(0);
37876
- const [stdout, stderr, status] = yield* all([
37877
- mkString(decodeText(maxStdoutBytes === void 0 ? handle.stdout : handle.stdout.pipe(tap((chunk) => updateAndGet(stdoutByteCount, (total) => total + chunk.length).pipe(flatMap$2((total) => total > maxStdoutBytes ? fail$4(new ReactDoctorError({ reason: new GitInvocationFailed({
37878
- args: [...input.args],
37879
- directory: input.directory,
37880
- cause: /* @__PURE__ */ new Error(`git stdout exceeded ${maxStdoutBytes} bytes`)
37881
- }) })) : void_)))))),
37882
- mkString(decodeText(handle.stderr)),
37883
- handle.exitCode
37884
- ], { concurrency: 3 });
37885
- return {
37886
- status,
37887
- stdout,
37888
- stderr
37889
- };
37890
- })).pipe(catchTag$1("PlatformError", (cause) => {
37891
- if (input.command !== "git") return succeed$2({
37876
+ const runCommand = (input) => {
37877
+ const foldSpawnFailure = (cause) => input.command !== "git" ? succeed$2({
37892
37878
  status: 127,
37893
37879
  stdout: "",
37894
37880
  stderr: String(cause)
37895
- });
37896
- return new ReactDoctorError({ reason: new GitInvocationFailed({
37881
+ }) : fail$4(new ReactDoctorError({ reason: new GitInvocationFailed({
37897
37882
  args: [...input.args],
37898
37883
  directory: input.directory,
37899
37884
  cause
37900
- }) });
37901
- }), withSpan("git.exec", { attributes: {
37902
- "git.command": input.command,
37903
- "git.subcommand": input.args[0] ?? ""
37904
- } }));
37885
+ }) }));
37886
+ return scoped(gen(function* () {
37887
+ if (!isDirectory(input.directory)) return yield* foldSpawnFailure(`spawn ENOTDIR (cwd is not a directory: ${input.directory})`);
37888
+ const argvLengthChars = input.command.length + 1 + input.args.reduce((total, arg) => total + arg.length + 1, 0);
37889
+ if (argvLengthChars > 24e3) return yield* foldSpawnFailure(`spawn ENAMETOOLONG (${argvLengthChars} argv chars exceed ${SPAWN_ARGS_MAX_LENGTH_CHARS})`);
37890
+ const handle = yield* spawner.spawn(make$1(input.command, [...input.args], {
37891
+ cwd: input.directory,
37892
+ env: input.env,
37893
+ extendEnv: true
37894
+ }));
37895
+ const maxStdoutBytes = input.maxStdoutBytes;
37896
+ const stdoutByteCount = yield* make$13(0);
37897
+ const [stdout, stderr, status] = yield* all([
37898
+ mkString(decodeText(maxStdoutBytes === void 0 ? handle.stdout : handle.stdout.pipe(tap((chunk) => updateAndGet(stdoutByteCount, (total) => total + chunk.length).pipe(flatMap$2((total) => total > maxStdoutBytes ? fail$4(new ReactDoctorError({ reason: new GitInvocationFailed({
37899
+ args: [...input.args],
37900
+ directory: input.directory,
37901
+ cause: /* @__PURE__ */ new Error(`git stdout exceeded ${maxStdoutBytes} bytes`)
37902
+ }) })) : void_)))))),
37903
+ mkString(decodeText(handle.stderr)),
37904
+ handle.exitCode
37905
+ ], { concurrency: 3 });
37906
+ return {
37907
+ status,
37908
+ stdout,
37909
+ stderr
37910
+ };
37911
+ })).pipe(catchTag$1("PlatformError", foldSpawnFailure), withSpan("git.exec", { attributes: {
37912
+ "git.command": input.command,
37913
+ "git.subcommand": input.args[0] ?? ""
37914
+ } }));
37915
+ };
37905
37916
  const runGit = (directory, args) => runCommand({
37906
37917
  command: "git",
37907
37918
  args,
@@ -37934,7 +37945,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
37934
37945
  "rev-parse",
37935
37946
  "--verify",
37936
37947
  branch
37937
- ]).pipe(map$3((result) => result.status === 0));
37948
+ ]).pipe(map$3((result) => result.status === 0), catch_$1((error) => error.reason._tag === "GitInvocationFailed" ? succeed$2(false) : fail$4(error)));
37938
37949
  const headSha = (directory) => runGit(directory, ["rev-parse", "HEAD"]).pipe(map$3((result) => result.status === 0 ? trimOrNull(result.stdout) : null));
37939
37950
  const mergeBase = (input) => isSafeGitRevision(input.ref) ? runGit(input.directory, [
37940
37951
  "merge-base",
@@ -38148,7 +38159,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
38148
38159
  ]);
38149
38160
  if (result.status !== 0) return null;
38150
38161
  return parseChangedLineRanges(result.stdout);
38151
- }).pipe(withSpan("Git.changedLineRanges"))
38162
+ }).pipe(catch_$1((error) => error.reason._tag === "GitInvocationFailed" ? succeed$2(null) : fail$4(error)), withSpan("Git.changedLineRanges"))
38152
38163
  });
38153
38164
  })).pipe(provide$2(layer$2.pipe(provide$2(mergeAll$1(layer$1, layer)))));
38154
38165
  /**
@@ -41472,4 +41483,4 @@ const toJsonReport = (result, options) => buildJsonReport({
41472
41483
  export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
41473
41484
 
41474
41485
  //# sourceMappingURL=index.js.map
41475
- //# debugId=fa4e432e-2004-53cc-816a-025aebf1c752
41486
+ //# debugId=dccb6d63-63d4-5e91-af21-2756a08fe93a
package/dist/lsp.js CHANGED
@@ -33730,6 +33730,7 @@ const MILLISECONDS_PER_SECOND = 1e3;
33730
33730
  const SCORE_API_URL = "https://www.react.doctor/api/score";
33731
33731
  const FETCH_TIMEOUT_MS = 1e4;
33732
33732
  const GITHUB_VIEWER_PERMISSION_TIMEOUT_MS = 2e3;
33733
+ const SPAWN_ARGS_MAX_LENGTH_CHARS = 24e3;
33733
33734
  const PER_WORKER_MEM_BUDGET_BYTES = 1024 * 1024 * 1024;
33734
33735
  const DEFAULT_BRANCH_CANDIDATES = ["main", "master"];
33735
33736
  const ADOPTABLE_LINT_CONFIG_FILENAMES = [".oxlintrc.json", ".eslintrc.json"];
@@ -35613,6 +35614,12 @@ const BOOLEAN_FIELD_NAMES = [
35613
35614
  "adoptExistingLintConfig"
35614
35615
  ];
35615
35616
  const STRING_FIELD_NAMES = ["rootDir"];
35617
+ const STRING_ARRAY_FIELD_NAMES = [
35618
+ "projects",
35619
+ "textComponents",
35620
+ "rawTextWrapperComponents",
35621
+ "serverAuthFunctionNames"
35622
+ ];
35616
35623
  const SURFACE_CONTROL_FIELD_NAMES = [
35617
35624
  "includeTags",
35618
35625
  "excludeTags",
@@ -35714,6 +35721,7 @@ const validateConfigTypes = (config) => {
35714
35721
  const validated = { ...config };
35715
35722
  for (const fieldName of BOOLEAN_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => coerceMaybeBooleanString(fieldName, value));
35716
35723
  for (const fieldName of STRING_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => validateString(fieldName, value));
35724
+ for (const fieldName of STRING_ARRAY_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => validateStringArrayField(fieldName, value));
35717
35725
  applyFieldValidator(config, validated, "surfaces", validateSurfacesField);
35718
35726
  for (const fieldName of SEVERITY_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => validateSeverityMap(fieldName, value, fieldName === "categories"));
35719
35727
  applyFieldValidator(config, validated, "plugins", (value) => validateStringArrayField("plugins", value));
@@ -37850,43 +37858,46 @@ var Git = class Git extends Service()("react-doctor/Git") {
37850
37858
  * reason: GitInvocationFailed })` so the rest of the codebase
37851
37859
  * sees a single failure channel.
37852
37860
  */
37853
- const runCommand = (input) => scoped(gen(function* () {
37854
- const handle = yield* spawner.spawn(make$1(input.command, [...input.args], {
37855
- cwd: input.directory,
37856
- env: input.env,
37857
- extendEnv: true
37858
- }));
37859
- const maxStdoutBytes = input.maxStdoutBytes;
37860
- const stdoutByteCount = yield* make$13(0);
37861
- const [stdout, stderr, status] = yield* all([
37862
- mkString(decodeText(maxStdoutBytes === void 0 ? handle.stdout : handle.stdout.pipe(tap((chunk) => updateAndGet(stdoutByteCount, (total) => total + chunk.length).pipe(flatMap$2((total) => total > maxStdoutBytes ? fail$4(new ReactDoctorError({ reason: new GitInvocationFailed({
37863
- args: [...input.args],
37864
- directory: input.directory,
37865
- cause: /* @__PURE__ */ new Error(`git stdout exceeded ${maxStdoutBytes} bytes`)
37866
- }) })) : void_)))))),
37867
- mkString(decodeText(handle.stderr)),
37868
- handle.exitCode
37869
- ], { concurrency: 3 });
37870
- return {
37871
- status,
37872
- stdout,
37873
- stderr
37874
- };
37875
- })).pipe(catchTag$1("PlatformError", (cause) => {
37876
- if (input.command !== "git") return succeed$2({
37861
+ const runCommand = (input) => {
37862
+ const foldSpawnFailure = (cause) => input.command !== "git" ? succeed$2({
37877
37863
  status: 127,
37878
37864
  stdout: "",
37879
37865
  stderr: String(cause)
37880
- });
37881
- return new ReactDoctorError({ reason: new GitInvocationFailed({
37866
+ }) : fail$4(new ReactDoctorError({ reason: new GitInvocationFailed({
37882
37867
  args: [...input.args],
37883
37868
  directory: input.directory,
37884
37869
  cause
37885
- }) });
37886
- }), withSpan("git.exec", { attributes: {
37887
- "git.command": input.command,
37888
- "git.subcommand": input.args[0] ?? ""
37889
- } }));
37870
+ }) }));
37871
+ return scoped(gen(function* () {
37872
+ if (!isDirectory(input.directory)) return yield* foldSpawnFailure(`spawn ENOTDIR (cwd is not a directory: ${input.directory})`);
37873
+ const argvLengthChars = input.command.length + 1 + input.args.reduce((total, arg) => total + arg.length + 1, 0);
37874
+ if (argvLengthChars > 24e3) return yield* foldSpawnFailure(`spawn ENAMETOOLONG (${argvLengthChars} argv chars exceed ${SPAWN_ARGS_MAX_LENGTH_CHARS})`);
37875
+ const handle = yield* spawner.spawn(make$1(input.command, [...input.args], {
37876
+ cwd: input.directory,
37877
+ env: input.env,
37878
+ extendEnv: true
37879
+ }));
37880
+ const maxStdoutBytes = input.maxStdoutBytes;
37881
+ const stdoutByteCount = yield* make$13(0);
37882
+ const [stdout, stderr, status] = yield* all([
37883
+ mkString(decodeText(maxStdoutBytes === void 0 ? handle.stdout : handle.stdout.pipe(tap((chunk) => updateAndGet(stdoutByteCount, (total) => total + chunk.length).pipe(flatMap$2((total) => total > maxStdoutBytes ? fail$4(new ReactDoctorError({ reason: new GitInvocationFailed({
37884
+ args: [...input.args],
37885
+ directory: input.directory,
37886
+ cause: /* @__PURE__ */ new Error(`git stdout exceeded ${maxStdoutBytes} bytes`)
37887
+ }) })) : void_)))))),
37888
+ mkString(decodeText(handle.stderr)),
37889
+ handle.exitCode
37890
+ ], { concurrency: 3 });
37891
+ return {
37892
+ status,
37893
+ stdout,
37894
+ stderr
37895
+ };
37896
+ })).pipe(catchTag$1("PlatformError", foldSpawnFailure), withSpan("git.exec", { attributes: {
37897
+ "git.command": input.command,
37898
+ "git.subcommand": input.args[0] ?? ""
37899
+ } }));
37900
+ };
37890
37901
  const runGit = (directory, args) => runCommand({
37891
37902
  command: "git",
37892
37903
  args,
@@ -37919,7 +37930,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
37919
37930
  "rev-parse",
37920
37931
  "--verify",
37921
37932
  branch
37922
- ]).pipe(map$3((result) => result.status === 0));
37933
+ ]).pipe(map$3((result) => result.status === 0), catch_$1((error) => error.reason._tag === "GitInvocationFailed" ? succeed$2(false) : fail$4(error)));
37923
37934
  const headSha = (directory) => runGit(directory, ["rev-parse", "HEAD"]).pipe(map$3((result) => result.status === 0 ? trimOrNull(result.stdout) : null));
37924
37935
  const mergeBase = (input) => isSafeGitRevision(input.ref) ? runGit(input.directory, [
37925
37936
  "merge-base",
@@ -38133,7 +38144,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
38133
38144
  ]);
38134
38145
  if (result.status !== 0) return null;
38135
38146
  return parseChangedLineRanges(result.stdout);
38136
- }).pipe(withSpan("Git.changedLineRanges"))
38147
+ }).pipe(catch_$1((error) => error.reason._tag === "GitInvocationFailed" ? succeed$2(null) : fail$4(error)), withSpan("Git.changedLineRanges"))
38137
38148
  });
38138
38149
  })).pipe(provide$2(layer$2.pipe(provide$2(mergeAll$1(layer$1, layer)))));
38139
38150
  /**
@@ -42906,6 +42917,7 @@ const SENTRY_FLUSH_TIMEOUT_MS = 2e3;
42906
42917
  const METRIC = {
42907
42918
  cliInvoked: "cli.invoked",
42908
42919
  cliError: "cli.error",
42920
+ cliEnvironmentError: "cli.env_error",
42909
42921
  projectDetected: "project.detected",
42910
42922
  projectPathSelected: "project.path_selected",
42911
42923
  projectConfigSelected: "project.config_selected",
@@ -43252,5 +43264,5 @@ const startLanguageServer = () => {
43252
43264
  };
43253
43265
  //#endregion
43254
43266
  export { startLanguageServer };
43255
- !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]="28a9132e-0eb3-5487-9d97-46eb9456a794")}catch(e){}}();
43256
- //# debugId=28a9132e-0eb3-5487-9d97-46eb9456a794
43267
+ !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]="d12f3c5d-14fd-59ba-9d8a-7e3f589c88ad")}catch(e){}}();
43268
+ //# debugId=d12f3c5d-14fd-59ba-9d8a-7e3f589c88ad
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-doctor",
3
- "version": "0.5.8-dev.c2ce298",
3
+ "version": "0.5.8-dev.ea00b1b",
4
4
  "description": "Your agent writes bad React. This catches it",
5
5
  "keywords": [
6
6
  "accessibility",
@@ -64,7 +64,7 @@
64
64
  "vscode-languageserver-textdocument": "^1.0.12",
65
65
  "vscode-uri": "^3.1.0",
66
66
  "deslop-js": "0.5.8",
67
- "oxlint-plugin-react-doctor": "0.5.8-dev.c2ce298"
67
+ "oxlint-plugin-react-doctor": "0.5.8-dev.ea00b1b"
68
68
  },
69
69
  "devDependencies": {
70
70
  "@types/babel__code-frame": "^7.27.0",