react-doctor 0.5.8-dev.9f28454 → 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/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- MIT License (with Additional Restrictions)
1
+ MIT License
2
2
 
3
3
  Copyright (c) 2026 Million Software, Inc.
4
4
 
@@ -12,13 +12,6 @@ furnished to do so, subject to the following conditions:
12
12
  The above copyright notice and this permission notice shall be included in all
13
13
  copies or substantial portions of the Software.
14
14
 
15
- The Software, its source code, and any derivative works thereof may not be
16
- used, in whole or in part, as training data, fine-tuning data, evaluation
17
- data, or as input to any automated pipeline for the purpose of training or
18
- improving any machine learning model or AI system, without prior written
19
- permission from the copyright holder. To request permission, contact
20
- founders@million.dev.
21
-
22
15
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
16
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
17
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
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]="34e4ad65-2650-5e3e-afd8-191931eb2a47")}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";
@@ -26,7 +26,7 @@ import tty from "node:tty";
26
26
  import { codeFrameColumns } from "@babel/code-frame";
27
27
  import Conf from "conf";
28
28
  import basePrompts from "prompts";
29
- import { SKILL_MANIFEST_FILE, detectInstalledSkillAgents, getSkillAgentConfig, getSkillAgentTypes, installSkillsFromSource } from "agent-install";
29
+ import { SKILL_MANIFEST_FILE, detectInstalledSkillAgents, getSkillAgentConfig, getSkillAgentTypes, installSkillsFromSource, isSkillAgentType } from "agent-install";
30
30
  import { generateCode, loadFile, writeFile } from "magicast";
31
31
  import { getConfigFromVariableDeclaration, getDefaultExportOptions } from "magicast/helpers";
32
32
  //#region \0rolldown/runtime.js
@@ -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"];
@@ -37679,7 +37680,10 @@ for (const [legacyRuleKey, nativeRuleKey] of Object.entries(LEGACY_RULE_KEY_TO_N
37679
37680
  NATIVE_RULE_KEY_TO_LEGACY_RULE_KEYS.set(nativeRuleKey, aliases);
37680
37681
  }
37681
37682
  const getLegacyRuleKeysForNative = (ruleKey) => NATIVE_RULE_KEY_TO_LEGACY_RULE_KEYS.get(ruleKey) ?? [];
37682
- const canonicalizeRuleKey = (ruleKey) => LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY[ruleKey] ?? ruleKey;
37683
+ const canonicalizeRuleKey = (ruleKey) => {
37684
+ const nativeRuleKey = LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY[ruleKey];
37685
+ return typeof nativeRuleKey === "string" ? nativeRuleKey : ruleKey;
37686
+ };
37683
37687
  const isReactDoctorShortIdOf = (bareRuleKey, qualifiedRuleKey) => !bareRuleKey.includes("/") && qualifiedRuleKey === `react-doctor/${bareRuleKey}`;
37684
37688
  const isSameRuleKey = (candidateRuleKey, targetRuleKey) => {
37685
37689
  const canonicalCandidate = canonicalizeRuleKey(candidateRuleKey);
@@ -38765,6 +38769,12 @@ const BOOLEAN_FIELD_NAMES = [
38765
38769
  "adoptExistingLintConfig"
38766
38770
  ];
38767
38771
  const STRING_FIELD_NAMES = ["rootDir"];
38772
+ const STRING_ARRAY_FIELD_NAMES = [
38773
+ "projects",
38774
+ "textComponents",
38775
+ "rawTextWrapperComponents",
38776
+ "serverAuthFunctionNames"
38777
+ ];
38768
38778
  const SURFACE_CONTROL_FIELD_NAMES = [
38769
38779
  "includeTags",
38770
38780
  "excludeTags",
@@ -38866,6 +38876,7 @@ const validateConfigTypes = (config) => {
38866
38876
  const validated = { ...config };
38867
38877
  for (const fieldName of BOOLEAN_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => coerceMaybeBooleanString(fieldName, value));
38868
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));
38869
38880
  applyFieldValidator(config, validated, "surfaces", validateSurfacesField);
38870
38881
  for (const fieldName of SEVERITY_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => validateSeverityMap(fieldName, value, fieldName === "categories"));
38871
38882
  applyFieldValidator(config, validated, "plugins", (value) => validateStringArrayField("plugins", value));
@@ -41094,43 +41105,46 @@ var Git = class Git extends Service()("react-doctor/Git") {
41094
41105
  * reason: GitInvocationFailed })` so the rest of the codebase
41095
41106
  * sees a single failure channel.
41096
41107
  */
41097
- const runCommand = (input) => scoped(gen(function* () {
41098
- const handle = yield* spawner.spawn(make$1(input.command, [...input.args], {
41099
- cwd: input.directory,
41100
- env: input.env,
41101
- extendEnv: true
41102
- }));
41103
- const maxStdoutBytes = input.maxStdoutBytes;
41104
- const stdoutByteCount = yield* make$13(0);
41105
- const [stdout, stderr, status] = yield* all([
41106
- 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({
41107
- args: [...input.args],
41108
- directory: input.directory,
41109
- cause: /* @__PURE__ */ new Error(`git stdout exceeded ${maxStdoutBytes} bytes`)
41110
- }) })) : void_)))))),
41111
- mkString(decodeText(handle.stderr)),
41112
- handle.exitCode
41113
- ], { concurrency: 3 });
41114
- return {
41115
- status,
41116
- stdout,
41117
- stderr
41118
- };
41119
- })).pipe(catchTag$1("PlatformError", (cause) => {
41120
- if (input.command !== "git") return succeed$2({
41108
+ const runCommand = (input) => {
41109
+ const foldSpawnFailure = (cause) => input.command !== "git" ? succeed$2({
41121
41110
  status: 127,
41122
41111
  stdout: "",
41123
41112
  stderr: String(cause)
41124
- });
41125
- return new ReactDoctorError({ reason: new GitInvocationFailed({
41113
+ }) : fail$4(new ReactDoctorError({ reason: new GitInvocationFailed({
41126
41114
  args: [...input.args],
41127
41115
  directory: input.directory,
41128
41116
  cause
41129
- }) });
41130
- }), withSpan("git.exec", { attributes: {
41131
- "git.command": input.command,
41132
- "git.subcommand": input.args[0] ?? ""
41133
- } }));
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
+ };
41134
41148
  const runGit = (directory, args) => runCommand({
41135
41149
  command: "git",
41136
41150
  args,
@@ -41163,7 +41177,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
41163
41177
  "rev-parse",
41164
41178
  "--verify",
41165
41179
  branch
41166
- ]).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)));
41167
41181
  const headSha = (directory) => runGit(directory, ["rev-parse", "HEAD"]).pipe(map$3((result) => result.status === 0 ? trimOrNull(result.stdout) : null));
41168
41182
  const mergeBase = (input) => isSafeGitRevision(input.ref) ? runGit(input.directory, [
41169
41183
  "merge-base",
@@ -41377,7 +41391,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
41377
41391
  ]);
41378
41392
  if (result.status !== 0) return null;
41379
41393
  return parseChangedLineRanges(result.stdout);
41380
- }).pipe(withSpan("Git.changedLineRanges"))
41394
+ }).pipe(catch_$1((error) => error.reason._tag === "GitInvocationFailed" ? succeed$2(null) : fail$4(error)), withSpan("Git.changedLineRanges"))
41381
41395
  });
41382
41396
  })).pipe(provide$2(layer$3.pipe(provide$2(mergeAll$1(layer$2, layer$1)))));
41383
41397
  /**
@@ -44702,25 +44716,7 @@ const CI_PROVIDER_BY_ENVIRONMENT_VARIABLE = [
44702
44716
  ["TEAMCITY_VERSION", "teamcity"],
44703
44717
  ["BITBUCKET_BUILD_NUMBER", "bitbucket"],
44704
44718
  ["TRAVIS", "travis"],
44705
- ["DRONE", "drone"],
44706
- ["SEMAPHORE", "semaphore"],
44707
- ["APPVEYOR", "appveyor"],
44708
- ["HARNESS_BUILD_ID", "harness"],
44709
- ["BUDDY", "buddy"],
44710
- ["CF_BUILD_ID", "codefresh"],
44711
- ["NETLIFY", "netlify"],
44712
- ["RAILWAY_SERVICE_ID", "railway"],
44713
- ["VERCEL", "vercel"],
44714
- ["CM_BUILD_ID", "codemagic"],
44715
- ["PROW_JOB_ID", "prow"],
44716
- ["AGOLA_GIT_REF", "agola"],
44717
- ["CIRRUS_CI", "cirrus-ci"],
44718
- ["BLACKSMITH_STICKYDISK_TOKEN", "blacksmith"],
44719
- ["WARPBUILD_RUNNER_VERIFICATION_TOKEN", "warpbuild"],
44720
- ["NSC_CACHE_PATH", "namespace"],
44721
- ["UBICLOUD_CACHE_URL", "ubicloud"],
44722
- ["RENDER", "render"],
44723
- ["FLY_APP_NAME", "fly-io"]
44719
+ ["DRONE", "drone"]
44724
44720
  ];
44725
44721
  const GITHUB_ACTION_MARKER_ENVIRONMENT_VARIABLE = "REACT_DOCTOR_GITHUB_ACTION";
44726
44722
  const ACTION_INPUT_ENVIRONMENT_VARIABLES = {
@@ -44738,10 +44734,7 @@ const CODING_AGENT_BY_ENVIRONMENT_VARIABLE = [
44738
44734
  ["CODEX_SANDBOX_NETWORK_DISABLED", "codex"],
44739
44735
  ["OPENCODE", "opencode"],
44740
44736
  ["GOOSE_TERMINAL", "goose"],
44741
- ["AMP_THREAD_ID", "amp"],
44742
- ["CLINE_ACTIVE", "cline"],
44743
- ["AUGMENT_AGENT", "augment"],
44744
- ["TRAE_AI_SHELL_ID", "trae-ai"]
44737
+ ["AMP_THREAD_ID", "amp"]
44745
44738
  ];
44746
44739
  const GENERIC_CODING_AGENT_ENVIRONMENT_VARIABLES = ["AGENT_SESSION_ID", "AGENT_THREAD_ID"];
44747
44740
  const CODING_AGENT_ENVIRONMENT_VALUE_VARIABLES = ["AGENT"];
@@ -44860,6 +44853,7 @@ const NANOSECONDS_PER_SECOND = 1000000000n;
44860
44853
  const METRIC = {
44861
44854
  cliInvoked: "cli.invoked",
44862
44855
  cliError: "cli.error",
44856
+ cliEnvironmentError: "cli.env_error",
44863
44857
  projectDetected: "project.detected",
44864
44858
  projectPathSelected: "project.path_selected",
44865
44859
  projectConfigSelected: "project.config_selected",
@@ -44932,7 +44926,7 @@ const makeNoopConsole = () => ({
44932
44926
  });
44933
44927
  //#endregion
44934
44928
  //#region src/cli/utils/version.ts
44935
- const VERSION = "0.5.8-dev.9f28454";
44929
+ const VERSION = "0.5.8-dev.ea00b1b";
44936
44930
  //#endregion
44937
44931
  //#region src/cli/utils/json-mode.ts
44938
44932
  let context = null;
@@ -45296,13 +45290,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
45296
45290
  * uploads source-map artifacts under, so stack frames symbolicate. Honors the
45297
45291
  * standard `SENTRY_RELEASE` override.
45298
45292
  */
45299
- const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.5.8-dev.9f28454`;
45293
+ const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.5.8-dev.ea00b1b`;
45300
45294
  /**
45301
45295
  * Deployment environment shown in Sentry's environment filter. Defaults to
45302
45296
  * `production` for tagged releases and `development` for dev/unbuilt versions,
45303
45297
  * overridable via the standard `SENTRY_ENVIRONMENT` env var.
45304
45298
  */
45305
- const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.5.8-dev.9f28454") ? "development" : "production");
45299
+ const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.5.8-dev.ea00b1b") ? "development" : "production");
45306
45300
  /**
45307
45301
  * Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
45308
45302
  * (set to `0` to disable tracing) and falls back to
@@ -49711,6 +49705,7 @@ const CI_PITCH_EVENT = "ci-pitch";
49711
49705
  const ACTION_UPGRADE_EVENT = "action-upgrade-v2";
49712
49706
  const SETUP_HINT_EVENT = "setup-hint";
49713
49707
  const HANDOFF_TARGET_PREFERENCE_ID = "handoff-target";
49708
+ const INSTALL_AGENTS_PREFERENCE_ID = "install-agents";
49714
49709
  const foldLegacyDecisions = (projects, legacy, eventId) => {
49715
49710
  for (const [hash, record] of Object.entries(legacy ?? {})) {
49716
49711
  const existing = projects[hash] ?? { rootDirectory: record.rootDirectory ?? "" };
@@ -50374,7 +50369,7 @@ const readPackageJson = (projectRoot) => {
50374
50369
  return null;
50375
50370
  }
50376
50371
  };
50377
- const writeJsonFile$1 = (filePath, value) => {
50372
+ const writeJsonFile = (filePath, value) => {
50378
50373
  NFS.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`);
50379
50374
  };
50380
50375
  const packageHasDependency = (projectRoot, dependencyName) => {
@@ -51113,6 +51108,35 @@ const materializeStagedFiles = async (directory, stagedFiles, tempDirectory) =>
51113
51108
  };
51114
51109
  };
51115
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
51116
51140
  //#region src/cli/utils/handle-error.ts
51117
51141
  const OTLP_ENDPOINT_ENVIRONMENT_VARIABLE = "REACT_DOCTOR_OTLP_ENDPOINT";
51118
51142
  const OTLP_AUTH_HEADER_ENVIRONMENT_VARIABLE = "REACT_DOCTOR_OTLP_AUTH_HEADER";
@@ -51195,15 +51219,19 @@ const handleError = (error, options = {}) => {
51195
51219
  process.exitCode = 1;
51196
51220
  };
51197
51221
  /**
51198
- * Renderer for expected, user-actionable failures — a bad `--diff` value or
51199
- * a base branch that isn't fetched. Prints just the (already human-readable)
51200
- * message no "Something went wrong", prefilled issue, Discord link, or
51201
- * 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.
51202
51227
  */
51203
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);
51204
51232
  runSync(gen(function* () {
51205
51233
  yield* error$1("");
51206
- yield* error$1(highlighter.error(formatErrorForReport(error)));
51234
+ yield* error$1(highlighter.error(message));
51207
51235
  yield* error$1("");
51208
51236
  }));
51209
51237
  if (options.shouldExit !== false) process.exit(1);
@@ -51218,7 +51246,7 @@ const handleUserError = (error, options = {}) => {
51218
51246
  * `handleUserError` (a plain message — no "Something went wrong", prefilled
51219
51247
  * issue, Discord link, or Sentry reference), since there is no bug to report.
51220
51248
  *
51221
- * Three distinct shapes reach the CLI's catch blocks:
51249
+ * Four distinct shapes reach the CLI's catch blocks:
51222
51250
  *
51223
51251
  * - **Project-discovery failures** (`NoReactDependencyError`,
51224
51252
  * `ProjectNotFoundError`, `PackageJsonNotFoundError`, `NotADirectoryError`,
@@ -51231,12 +51259,19 @@ const handleUserError = (error, options = {}) => {
51231
51259
  * `--project` name.
51232
51260
  * - **Bad `--diff` input** (`GitBaseBranchInvalid` / `GitBaseBranchMissing`)
51233
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).
51234
51269
  *
51235
51270
  * This composes the existing core narrowers rather than introducing a new
51236
51271
  * error-shape helper (AGENTS.md): it encodes CLI-layer reporting policy, not
51237
51272
  * knowledge of the `ReactDoctorError` shape.
51238
51273
  */
51239
- 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");
51240
51275
  //#endregion
51241
51276
  //#region src/cli/utils/build-handoff-payload.ts
51242
51277
  const buildHandoffPayload = (input) => {
@@ -51317,6 +51352,20 @@ const detectAvailableAgents = async () => {
51317
51352
  const detected = new Set([...detectPathAvailableAgents(), ...await detectInstalledSkillAgents()]);
51318
51353
  return getSkillAgentTypes().filter((agent) => agent !== "universal" && detected.has(agent));
51319
51354
  };
51355
+ const DEFAULT_INSTALL_AGENTS = [
51356
+ "claude-code",
51357
+ "cursor",
51358
+ "codex",
51359
+ "opencode"
51360
+ ];
51361
+ const computeDefaultSelectedAgents = (detectedAgents, rememberedAgents) => {
51362
+ const detected = new Set(detectedAgents);
51363
+ const remembered = rememberedAgents.filter((agent) => detected.has(agent));
51364
+ if (remembered.length > 0) return remembered;
51365
+ const defaults = DEFAULT_INSTALL_AGENTS.filter((agent) => detected.has(agent));
51366
+ if (defaults.length > 0) return defaults;
51367
+ return detectedAgents.length === 1 ? [...detectedAgents] : [];
51368
+ };
51320
51369
  //#endregion
51321
51370
  //#region src/cli/utils/install-doctor-script.ts
51322
51371
  const DOCTOR_SCRIPT_NAME = "doctor";
@@ -51398,7 +51447,7 @@ const installDoctorScript = (options) => {
51398
51447
  };
51399
51448
  })();
51400
51449
  const scriptStatus = scriptTarget.status;
51401
- if (scriptStatus === "created") writeJsonFile$1(packageJsonPath, {
51450
+ if (scriptStatus === "created") writeJsonFile(packageJsonPath, {
51402
51451
  ...packageJson,
51403
51452
  scripts: {
51404
51453
  ...isRecord$1(scripts) ? scripts : {},
@@ -51969,6 +52018,19 @@ const setUpGitHubActions = async (options) => {
51969
52018
  return didCreateWorkflow;
51970
52019
  };
51971
52020
  //#endregion
52021
+ //#region src/cli/utils/install-agents-preference.ts
52022
+ const INSTALL_AGENTS_PREFERENCE = {
52023
+ id: INSTALL_AGENTS_PREFERENCE_ID,
52024
+ scope: "global"
52025
+ };
52026
+ const PREFERENCE_SEPARATOR = ",";
52027
+ const readInstallAgents = (options = {}) => {
52028
+ const stored = readPreference(INSTALL_AGENTS_PREFERENCE, {}, options);
52029
+ if (stored === null) return [];
52030
+ return stored.split(PREFERENCE_SEPARATOR).map((entry) => entry.trim()).filter((entry) => isSkillAgentType(entry));
52031
+ };
52032
+ const rememberInstallAgents = (agents, options = {}) => writePreference(INSTALL_AGENTS_PREFERENCE, agents.join(PREFERENCE_SEPARATOR), {}, options);
52033
+ //#endregion
51972
52034
  //#region src/cli/utils/install-agent-hooks.ts
51973
52035
  const CLAUDE_AGENT = "claude-code";
51974
52036
  const CURSOR_AGENT = "cursor";
@@ -51979,20 +52041,34 @@ const CURSOR_HOOKS_RELATIVE_PATH = ".cursor/hooks.json";
51979
52041
  const CURSOR_HOOK_RELATIVE_PATH = ".cursor/hooks/react-doctor.sh";
51980
52042
  const CURSOR_HOOK_MATCHER = "Write|Edit|MultiEdit|ApplyPatch";
51981
52043
  const CURSOR_HOOKS_SCHEMA_VERSION = 1;
51982
- const JSON_INDENT_SPACES$1 = 2;
51983
52044
  const isSupportedAgent = (agent) => agent === CLAUDE_AGENT || agent === CURSOR_AGENT;
51984
52045
  const readJsonFile = (filePath, fallback) => {
51985
52046
  if (!NFS.existsSync(filePath)) return fallback;
51986
52047
  const content = NFS.readFileSync(filePath, "utf8").trim();
51987
52048
  if (content.length === 0) return fallback;
51988
- 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
+ }
51989
52055
  };
51990
- const writeJsonFile = (filePath, value) => {
51991
- NFS.mkdirSync(Path.dirname(filePath), { recursive: true });
51992
- 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);
51993
52069
  };
51994
52070
  const writeHookScript = (filePath) => {
51995
- NFS.mkdirSync(Path.dirname(filePath), { recursive: true });
52071
+ ensureDirectoryExists(Path.dirname(filePath));
51996
52072
  NFS.writeFileSync(filePath, buildAgentHookScript());
51997
52073
  NFS.chmodSync(filePath, 493);
51998
52074
  };
@@ -52008,7 +52084,7 @@ const installClaudeHook = (projectRoot) => {
52008
52084
  command: CLAUDE_HOOK_COMMAND
52009
52085
  }] });
52010
52086
  hooks.PostToolBatch = postToolBatchHooks;
52011
- writeJsonFile(settingsPath, {
52087
+ writeJsonFileWithDirectoryCheck(settingsPath, {
52012
52088
  ...settings,
52013
52089
  hooks
52014
52090
  });
@@ -52028,7 +52104,7 @@ const installCursorHook = (projectRoot) => {
52028
52104
  timeout: 120
52029
52105
  });
52030
52106
  hooks.postToolUse = postToolUseHooks;
52031
- writeJsonFile(configPath, {
52107
+ writeJsonFileWithDirectoryCheck(configPath, {
52032
52108
  ...config,
52033
52109
  version: config.version ?? CURSOR_HOOKS_SCHEMA_VERSION,
52034
52110
  hooks
@@ -52264,7 +52340,7 @@ const installPackageJsonHook = (options, strategy) => {
52264
52340
  parent = cloned;
52265
52341
  }
52266
52342
  parent[leafKey] = strategy.leafShape === "array" ? appendArrayCommand(parent[leafKey]) : appendStringCommand(parent[leafKey]);
52267
- writeJsonFile$1(packageJsonPath, nextPackageJson);
52343
+ writeJsonFile(packageJsonPath, nextPackageJson);
52268
52344
  removeLegacyManagedRunner(options.projectRoot);
52269
52345
  return {
52270
52346
  hookPath: packageJsonPath,
@@ -52847,6 +52923,9 @@ const runInstallReactDoctor = async (options = {}) => {
52847
52923
  const shouldUpgradeWorkflow = canUpgradeWorkflow && (Boolean(options.yes) || upgradePromptOutcome === "yes");
52848
52924
  if (upgradePromptOutcome === "no" && !options.dryRun) recordActionUpgradeDecision(projectRoot, "declined");
52849
52925
  if ((ciPromptOutcome === "yes" || ciPromptOutcome === "no") && !options.dryRun) recordCiPromptDecision(projectRoot, ciPromptOutcome === "yes" ? "accepted" : "declined");
52926
+ const rememberedAgents = options.lastSelectedAgents ?? readInstallAgents();
52927
+ const defaultSelectedAgents = computeDefaultSelectedAgents(detectedAgents, rememberedAgents);
52928
+ const usedRememberedAgents = rememberedAgents.some((agent) => detectedAgents.includes(agent));
52850
52929
  const selectedAgents = skipPrompts ? detectedAgents : (await prompt({
52851
52930
  type: "multiselect",
52852
52931
  name: "agents",
@@ -52854,12 +52933,13 @@ const runInstallReactDoctor = async (options = {}) => {
52854
52933
  choices: detectedAgents.map((agent) => ({
52855
52934
  title: getSkillAgentConfig(agent).displayName,
52856
52935
  value: agent,
52857
- selected: true
52936
+ selected: defaultSelectedAgents.includes(agent)
52858
52937
  })),
52859
52938
  instructions: false,
52860
52939
  min: 1
52861
52940
  }, promptOptions)).agents ?? [];
52862
52941
  if (selectedAgents.length === 0) return;
52942
+ if (!skipPrompts && !options.dryRun) rememberInstallAgents(selectedAgents);
52863
52943
  let dependencyResult;
52864
52944
  if (!options.dryRun) {
52865
52945
  await installReactDoctorSkillStep(sourceDir, selectedAgents, projectRoot);
@@ -52918,6 +52998,8 @@ const runInstallReactDoctor = async (options = {}) => {
52918
52998
  }
52919
52999
  recordCount(METRIC.installCompleted, 1, {
52920
53000
  agentsCount: selectedAgents.length,
53001
+ agentsDetected: detectedAgents.length,
53002
+ usedRememberedAgents,
52921
53003
  gitHook: shouldInstallGitHook,
52922
53004
  agentHooks: shouldInstallAgentHooks,
52923
53005
  workflow: didInstallWorkflow,
@@ -53697,7 +53779,7 @@ const warnDeprecatedDiff = (flags, userConfig) => {
53697
53779
  };
53698
53780
  const warnDiffUnavailable = (requested, isQuiet) => {
53699
53781
  if (isQuiet) return;
53700
- 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.`);
53701
53783
  else cliLogger.warn("No feature branch or uncommitted changes detected. Running full scan.");
53702
53784
  cliLogger.break();
53703
53785
  };
@@ -54001,69 +54083,6 @@ const warnDeprecatedFailOn = (flags, userConfig) => {
54001
54083
  cliLogger.warn(`${source} is deprecated; rename it to ${replacement}.`);
54002
54084
  };
54003
54085
  //#endregion
54004
- //#region src/cli/utils/detect-ai-training-environment.ts
54005
- const AI_TRAINING_ENV_VARS = [
54006
- ["HF_DATASETS_CACHE", "huggingface"],
54007
- ["HF_HOME", "huggingface"],
54008
- ["HUGGINGFACE_HUB_CACHE", "huggingface"],
54009
- ["CUDA_VISIBLE_DEVICES", "cuda"],
54010
- ["NVIDIA_VISIBLE_DEVICES", "nvidia"],
54011
- ["WANDB_RUN_ID", "wandb"],
54012
- ["MLFLOW_RUN_ID", "mlflow"],
54013
- ["MLFLOW_TRACKING_URI", "mlflow"],
54014
- ["COMET_EXPERIMENT_KEY", "comet"],
54015
- ["NEPTUNE_RUN_ID", "neptune"],
54016
- ["RAY_WORKER_PROCESS", "ray"],
54017
- ["RAY_ADDRESS", "ray"],
54018
- ["MUJOCO_GL", "mujoco"],
54019
- ["MUJOCO_PATH", "mujoco"],
54020
- ["GYM_DISABLE_ENV_CHECKER", "gymnasium"],
54021
- ["SAGEMAKER_BASE_DIR", "sagemaker"],
54022
- ["AZURE_ML_MODEL_DIR", "azure-ml"],
54023
- ["VERTEX_AI_LOG_LEVEL", "vertex-ai"],
54024
- ["DAYTONA_WS_ID", "daytona"],
54025
- ["DAYTONA_WS_NAME", "daytona"],
54026
- ["E2B_SANDBOX_ID", "e2b"],
54027
- ["MODAL_FUNCTION_ID", "modal"],
54028
- ["MODAL_TASK_ID", "modal"],
54029
- ["RUNPOD_POD_ID", "runpod"],
54030
- ["HARBOR_URL", "harbor"],
54031
- ["HARBOR_HOSTNAME", "harbor"],
54032
- ["SWE_BENCH_TASK", "swe-bench"],
54033
- ["SWEBENCH_TASK", "swe-bench"],
54034
- ["SWE_AGENT_MODEL", "swe-agent"],
54035
- ["KAGGLE_KERNEL_RUN_TYPE", "kaggle"],
54036
- ["COLAB_BACKEND_VERSION", "google-colab"],
54037
- ["DATABRICKS_RUNTIME_VERSION", "databricks"],
54038
- ["SM_TRAINING_ENV", "sagemaker"],
54039
- ["TRAINING_JOB_ARN", "sagemaker"],
54040
- ["AZUREML_RUN_ID", "azure-ml"],
54041
- ["CLOUD_ML_PROJECT_ID", "vertex-ai"],
54042
- ["WANDB_SWEEP_ID", "wandb"],
54043
- ["DVC_STAGE", "dvc"],
54044
- ["CLEARML_TASK_ID", "clearml"],
54045
- ["FLYTE_INTERNAL_EXECUTION_ID", "flyte"],
54046
- ["DET_MASTER", "determined-ai"],
54047
- ["LIGHTNING_USER_ID", "lightning-ai"],
54048
- ["ARGO_WORKFLOW_NAME", "argo-workflows"],
54049
- ["KFP_POD_NAME", "kubeflow-pipelines"],
54050
- ["SPACE_ID", "huggingface-spaces"],
54051
- ["REPLICATE_USERNAME", "replicate"],
54052
- ["VAST_CONTAINERLABEL", "vast-ai"],
54053
- ["TPU_NAME", "google-tpu"],
54054
- ["ROCR_VISIBLE_DEVICES", "rocm"]
54055
- ];
54056
- const detectAiTrainingEnvironment = () => {
54057
- for (const [envVar, label] of AI_TRAINING_ENV_VARS) if (process.env[envVar] !== void 0) return label;
54058
- return null;
54059
- };
54060
- //#endregion
54061
- //#region src/cli/utils/warn-ai-training-environment.ts
54062
- const warnIfAiTrainingEnvironment = () => {
54063
- if (detectAiTrainingEnvironment() === null) return;
54064
- cliLogger.warn("react-doctor detected use in an AI or ML pipeline. This use requires written permission under the react-doctor license — contact founders@million.dev to request access.");
54065
- };
54066
- //#endregion
54067
54086
  //#region src/cli/utils/validate-mode-flags.ts
54068
54087
  const usedDiffAlias = (flags) => flags.diff !== void 0 && flags.diff !== false && flags.diff !== "false" && flags.diff !== "";
54069
54088
  const usedScope = (flags) => typeof flags.scope === "string" && flags.scope.length > 0;
@@ -54164,7 +54183,6 @@ const inspectAction = async (directory, flags) => {
54164
54183
  setJsonReportDirectory(resolvedDirectory);
54165
54184
  warnDeprecatedFailOn(flags, userConfig);
54166
54185
  warnDeprecatedDiff(flags, userConfig);
54167
- warnIfAiTrainingEnvironment();
54168
54186
  if (scanTarget.didRedirectViaRootDir && !isQuiet) {
54169
54187
  cliLogger.dim(`Redirected to ${highlighter.info(toRelativePath(resolvedDirectory, requestedDirectory))} via react-doctor config "rootDir".`);
54170
54188
  cliLogger.break();
@@ -54452,6 +54470,10 @@ const installAction = async (options, command) => {
54452
54470
  projectRoot: options.cwd ?? process.cwd()
54453
54471
  });
54454
54472
  } catch (error) {
54473
+ if (isExpectedUserError(error)) {
54474
+ handleUserError(error);
54475
+ return;
54476
+ }
54455
54477
  handleError(error, { sentryEventId: await reportErrorToSentry(error) });
54456
54478
  }
54457
54479
  };
@@ -55459,4 +55481,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
55459
55481
  export {};
55460
55482
 
55461
55483
  //# sourceMappingURL=cli.js.map
55462
- //# debugId=34e4ad65-2650-5e3e-afd8-191931eb2a47
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]="1ef7f287-5a83-55e9-a20b-41145ffdda43")}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"];
@@ -34487,7 +34488,10 @@ for (const [legacyRuleKey, nativeRuleKey] of Object.entries(LEGACY_RULE_KEY_TO_N
34487
34488
  NATIVE_RULE_KEY_TO_LEGACY_RULE_KEYS.set(nativeRuleKey, aliases);
34488
34489
  }
34489
34490
  const getLegacyRuleKeysForNative = (ruleKey) => NATIVE_RULE_KEY_TO_LEGACY_RULE_KEYS.get(ruleKey) ?? [];
34490
- const canonicalizeRuleKey = (ruleKey) => LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY[ruleKey] ?? ruleKey;
34491
+ const canonicalizeRuleKey = (ruleKey) => {
34492
+ const nativeRuleKey = LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY[ruleKey];
34493
+ return typeof nativeRuleKey === "string" ? nativeRuleKey : ruleKey;
34494
+ };
34491
34495
  const isReactDoctorShortIdOf = (bareRuleKey, qualifiedRuleKey) => !bareRuleKey.includes("/") && qualifiedRuleKey === `react-doctor/${bareRuleKey}`;
34492
34496
  const isSameRuleKey = (candidateRuleKey, targetRuleKey) => {
34493
34497
  const canonicalCandidate = canonicalizeRuleKey(candidateRuleKey);
@@ -35577,6 +35581,12 @@ const BOOLEAN_FIELD_NAMES = [
35577
35581
  "adoptExistingLintConfig"
35578
35582
  ];
35579
35583
  const STRING_FIELD_NAMES = ["rootDir"];
35584
+ const STRING_ARRAY_FIELD_NAMES = [
35585
+ "projects",
35586
+ "textComponents",
35587
+ "rawTextWrapperComponents",
35588
+ "serverAuthFunctionNames"
35589
+ ];
35580
35590
  const SURFACE_CONTROL_FIELD_NAMES = [
35581
35591
  "includeTags",
35582
35592
  "excludeTags",
@@ -35678,6 +35688,7 @@ const validateConfigTypes = (config) => {
35678
35688
  const validated = { ...config };
35679
35689
  for (const fieldName of BOOLEAN_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => coerceMaybeBooleanString(fieldName, value));
35680
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));
35681
35692
  applyFieldValidator(config, validated, "surfaces", validateSurfacesField);
35682
35693
  for (const fieldName of SEVERITY_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => validateSeverityMap(fieldName, value, fieldName === "categories"));
35683
35694
  applyFieldValidator(config, validated, "plugins", (value) => validateStringArrayField("plugins", value));
@@ -37862,43 +37873,46 @@ var Git = class Git extends Service()("react-doctor/Git") {
37862
37873
  * reason: GitInvocationFailed })` so the rest of the codebase
37863
37874
  * sees a single failure channel.
37864
37875
  */
37865
- const runCommand = (input) => scoped(gen(function* () {
37866
- const handle = yield* spawner.spawn(make$1(input.command, [...input.args], {
37867
- cwd: input.directory,
37868
- env: input.env,
37869
- extendEnv: true
37870
- }));
37871
- const maxStdoutBytes = input.maxStdoutBytes;
37872
- const stdoutByteCount = yield* make$13(0);
37873
- const [stdout, stderr, status] = yield* all([
37874
- 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({
37875
- args: [...input.args],
37876
- directory: input.directory,
37877
- cause: /* @__PURE__ */ new Error(`git stdout exceeded ${maxStdoutBytes} bytes`)
37878
- }) })) : void_)))))),
37879
- mkString(decodeText(handle.stderr)),
37880
- handle.exitCode
37881
- ], { concurrency: 3 });
37882
- return {
37883
- status,
37884
- stdout,
37885
- stderr
37886
- };
37887
- })).pipe(catchTag$1("PlatformError", (cause) => {
37888
- if (input.command !== "git") return succeed$2({
37876
+ const runCommand = (input) => {
37877
+ const foldSpawnFailure = (cause) => input.command !== "git" ? succeed$2({
37889
37878
  status: 127,
37890
37879
  stdout: "",
37891
37880
  stderr: String(cause)
37892
- });
37893
- return new ReactDoctorError({ reason: new GitInvocationFailed({
37881
+ }) : fail$4(new ReactDoctorError({ reason: new GitInvocationFailed({
37894
37882
  args: [...input.args],
37895
37883
  directory: input.directory,
37896
37884
  cause
37897
- }) });
37898
- }), withSpan("git.exec", { attributes: {
37899
- "git.command": input.command,
37900
- "git.subcommand": input.args[0] ?? ""
37901
- } }));
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
+ };
37902
37916
  const runGit = (directory, args) => runCommand({
37903
37917
  command: "git",
37904
37918
  args,
@@ -37931,7 +37945,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
37931
37945
  "rev-parse",
37932
37946
  "--verify",
37933
37947
  branch
37934
- ]).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)));
37935
37949
  const headSha = (directory) => runGit(directory, ["rev-parse", "HEAD"]).pipe(map$3((result) => result.status === 0 ? trimOrNull(result.stdout) : null));
37936
37950
  const mergeBase = (input) => isSafeGitRevision(input.ref) ? runGit(input.directory, [
37937
37951
  "merge-base",
@@ -38145,7 +38159,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
38145
38159
  ]);
38146
38160
  if (result.status !== 0) return null;
38147
38161
  return parseChangedLineRanges(result.stdout);
38148
- }).pipe(withSpan("Git.changedLineRanges"))
38162
+ }).pipe(catch_$1((error) => error.reason._tag === "GitInvocationFailed" ? succeed$2(null) : fail$4(error)), withSpan("Git.changedLineRanges"))
38149
38163
  });
38150
38164
  })).pipe(provide$2(layer$2.pipe(provide$2(mergeAll$1(layer$1, layer)))));
38151
38165
  /**
@@ -41342,64 +41356,6 @@ const mergeReactDoctorConfigs = (baseConfig, overrideConfig) => {
41342
41356
  };
41343
41357
  //#endregion
41344
41358
  //#region ../api/dist/index.js
41345
- const AI_TRAINING_ENV_VARS = [
41346
- "HF_DATASETS_CACHE",
41347
- "HF_HOME",
41348
- "HUGGINGFACE_HUB_CACHE",
41349
- "CUDA_VISIBLE_DEVICES",
41350
- "NVIDIA_VISIBLE_DEVICES",
41351
- "WANDB_RUN_ID",
41352
- "WANDB_SWEEP_ID",
41353
- "MLFLOW_RUN_ID",
41354
- "MLFLOW_TRACKING_URI",
41355
- "COMET_EXPERIMENT_KEY",
41356
- "NEPTUNE_RUN_ID",
41357
- "CLEARML_TASK_ID",
41358
- "RAY_WORKER_PROCESS",
41359
- "RAY_ADDRESS",
41360
- "MUJOCO_GL",
41361
- "MUJOCO_PATH",
41362
- "GYM_DISABLE_ENV_CHECKER",
41363
- "SM_TRAINING_ENV",
41364
- "TRAINING_JOB_ARN",
41365
- "SAGEMAKER_BASE_DIR",
41366
- "AZUREML_RUN_ID",
41367
- "AZURE_ML_MODEL_DIR",
41368
- "CLOUD_ML_PROJECT_ID",
41369
- "VERTEX_AI_LOG_LEVEL",
41370
- "KAGGLE_KERNEL_RUN_TYPE",
41371
- "COLAB_BACKEND_VERSION",
41372
- "DATABRICKS_RUNTIME_VERSION",
41373
- "FLYTE_INTERNAL_EXECUTION_ID",
41374
- "DET_MASTER",
41375
- "LIGHTNING_USER_ID",
41376
- "ARGO_WORKFLOW_NAME",
41377
- "KFP_POD_NAME",
41378
- "DVC_STAGE",
41379
- "DAYTONA_WS_ID",
41380
- "DAYTONA_WS_NAME",
41381
- "E2B_SANDBOX_ID",
41382
- "MODAL_FUNCTION_ID",
41383
- "MODAL_TASK_ID",
41384
- "RUNPOD_POD_ID",
41385
- "SPACE_ID",
41386
- "REPLICATE_USERNAME",
41387
- "VAST_CONTAINERLABEL",
41388
- "TPU_NAME",
41389
- "ROCR_VISIBLE_DEVICES",
41390
- "HARBOR_URL",
41391
- "HARBOR_HOSTNAME",
41392
- "SWE_BENCH_TASK",
41393
- "SWEBENCH_TASK",
41394
- "SWE_AGENT_MODEL"
41395
- ];
41396
- let didWarnAiTraining = false;
41397
- const warnIfAiTrainingEnvironment = () => {
41398
- if (didWarnAiTraining) return;
41399
- if (AI_TRAINING_ENV_VARS.find((envVar) => process.env[envVar] !== void 0) === void 0) return;
41400
- didWarnAiTraining = true;
41401
- console.warn("[react-doctor] Use in an AI or ML pipeline requires written permission under the react-doctor license. Contact founders@million.dev to request access.");
41402
- };
41403
41359
  const buildDiagnoseLayer = (config, configOverrideTarget) => {
41404
41360
  const configLayer = configOverrideTarget === void 0 ? Config.layerNode : Config.layerOf({
41405
41361
  config,
@@ -41437,7 +41393,6 @@ const outputToDiagnoseResult = (output, elapsedMilliseconds) => {
41437
41393
  };
41438
41394
  };
41439
41395
  const diagnoseDirectory = async (directory, options) => {
41440
- warnIfAiTrainingEnvironment();
41441
41396
  const startTime = globalThis.performance.now();
41442
41397
  const scanTarget = await resolveScanTarget(directory);
41443
41398
  return outputToDiagnoseResult(await runPromise(restoreLegacyThrow(buildInspectProgram(scanTarget, options).pipe(provide(buildDiagnoseLayer(scanTarget.userConfig)), provide(layerOtlp)))), globalThis.performance.now() - startTime);
@@ -41484,7 +41439,6 @@ const diagnoseProject = async (projectDefinition, baseOptions, batchConfig) => {
41484
41439
  }
41485
41440
  };
41486
41441
  const diagnoseProjectBatch = async (input) => {
41487
- warnIfAiTrainingEnvironment();
41488
41442
  const startTime = globalThis.performance.now();
41489
41443
  const { projects, concurrency, config: batchConfig, ...baseOptions } = input;
41490
41444
  const projectResults = await mapWithConcurrency(projects, concurrency ?? 4, (projectDefinition) => diagnoseProject(projectDefinition, baseOptions, batchConfig));
@@ -41529,4 +41483,4 @@ const toJsonReport = (result, options) => buildJsonReport({
41529
41483
  export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
41530
41484
 
41531
41485
  //# sourceMappingURL=index.js.map
41532
- //# debugId=1ef7f287-5a83-55e9-a20b-41145ffdda43
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"];
@@ -34546,7 +34547,10 @@ for (const [legacyRuleKey, nativeRuleKey] of Object.entries(LEGACY_RULE_KEY_TO_N
34546
34547
  NATIVE_RULE_KEY_TO_LEGACY_RULE_KEYS.set(nativeRuleKey, aliases);
34547
34548
  }
34548
34549
  const getLegacyRuleKeysForNative = (ruleKey) => NATIVE_RULE_KEY_TO_LEGACY_RULE_KEYS.get(ruleKey) ?? [];
34549
- const canonicalizeRuleKey = (ruleKey) => LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY[ruleKey] ?? ruleKey;
34550
+ const canonicalizeRuleKey = (ruleKey) => {
34551
+ const nativeRuleKey = LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY[ruleKey];
34552
+ return typeof nativeRuleKey === "string" ? nativeRuleKey : ruleKey;
34553
+ };
34550
34554
  const isReactDoctorShortIdOf = (bareRuleKey, qualifiedRuleKey) => !bareRuleKey.includes("/") && qualifiedRuleKey === `react-doctor/${bareRuleKey}`;
34551
34555
  const isSameRuleKey = (candidateRuleKey, targetRuleKey) => {
34552
34556
  const canonicalCandidate = canonicalizeRuleKey(candidateRuleKey);
@@ -35610,6 +35614,12 @@ const BOOLEAN_FIELD_NAMES = [
35610
35614
  "adoptExistingLintConfig"
35611
35615
  ];
35612
35616
  const STRING_FIELD_NAMES = ["rootDir"];
35617
+ const STRING_ARRAY_FIELD_NAMES = [
35618
+ "projects",
35619
+ "textComponents",
35620
+ "rawTextWrapperComponents",
35621
+ "serverAuthFunctionNames"
35622
+ ];
35613
35623
  const SURFACE_CONTROL_FIELD_NAMES = [
35614
35624
  "includeTags",
35615
35625
  "excludeTags",
@@ -35711,6 +35721,7 @@ const validateConfigTypes = (config) => {
35711
35721
  const validated = { ...config };
35712
35722
  for (const fieldName of BOOLEAN_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => coerceMaybeBooleanString(fieldName, value));
35713
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));
35714
35725
  applyFieldValidator(config, validated, "surfaces", validateSurfacesField);
35715
35726
  for (const fieldName of SEVERITY_FIELD_NAMES) applyFieldValidator(config, validated, fieldName, (value) => validateSeverityMap(fieldName, value, fieldName === "categories"));
35716
35727
  applyFieldValidator(config, validated, "plugins", (value) => validateStringArrayField("plugins", value));
@@ -37847,43 +37858,46 @@ var Git = class Git extends Service()("react-doctor/Git") {
37847
37858
  * reason: GitInvocationFailed })` so the rest of the codebase
37848
37859
  * sees a single failure channel.
37849
37860
  */
37850
- const runCommand = (input) => scoped(gen(function* () {
37851
- const handle = yield* spawner.spawn(make$1(input.command, [...input.args], {
37852
- cwd: input.directory,
37853
- env: input.env,
37854
- extendEnv: true
37855
- }));
37856
- const maxStdoutBytes = input.maxStdoutBytes;
37857
- const stdoutByteCount = yield* make$13(0);
37858
- const [stdout, stderr, status] = yield* all([
37859
- 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({
37860
- args: [...input.args],
37861
- directory: input.directory,
37862
- cause: /* @__PURE__ */ new Error(`git stdout exceeded ${maxStdoutBytes} bytes`)
37863
- }) })) : void_)))))),
37864
- mkString(decodeText(handle.stderr)),
37865
- handle.exitCode
37866
- ], { concurrency: 3 });
37867
- return {
37868
- status,
37869
- stdout,
37870
- stderr
37871
- };
37872
- })).pipe(catchTag$1("PlatformError", (cause) => {
37873
- if (input.command !== "git") return succeed$2({
37861
+ const runCommand = (input) => {
37862
+ const foldSpawnFailure = (cause) => input.command !== "git" ? succeed$2({
37874
37863
  status: 127,
37875
37864
  stdout: "",
37876
37865
  stderr: String(cause)
37877
- });
37878
- return new ReactDoctorError({ reason: new GitInvocationFailed({
37866
+ }) : fail$4(new ReactDoctorError({ reason: new GitInvocationFailed({
37879
37867
  args: [...input.args],
37880
37868
  directory: input.directory,
37881
37869
  cause
37882
- }) });
37883
- }), withSpan("git.exec", { attributes: {
37884
- "git.command": input.command,
37885
- "git.subcommand": input.args[0] ?? ""
37886
- } }));
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
+ };
37887
37901
  const runGit = (directory, args) => runCommand({
37888
37902
  command: "git",
37889
37903
  args,
@@ -37916,7 +37930,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
37916
37930
  "rev-parse",
37917
37931
  "--verify",
37918
37932
  branch
37919
- ]).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)));
37920
37934
  const headSha = (directory) => runGit(directory, ["rev-parse", "HEAD"]).pipe(map$3((result) => result.status === 0 ? trimOrNull(result.stdout) : null));
37921
37935
  const mergeBase = (input) => isSafeGitRevision(input.ref) ? runGit(input.directory, [
37922
37936
  "merge-base",
@@ -38130,7 +38144,7 @@ var Git = class Git extends Service()("react-doctor/Git") {
38130
38144
  ]);
38131
38145
  if (result.status !== 0) return null;
38132
38146
  return parseChangedLineRanges(result.stdout);
38133
- }).pipe(withSpan("Git.changedLineRanges"))
38147
+ }).pipe(catch_$1((error) => error.reason._tag === "GitInvocationFailed" ? succeed$2(null) : fail$4(error)), withSpan("Git.changedLineRanges"))
38134
38148
  });
38135
38149
  })).pipe(provide$2(layer$2.pipe(provide$2(mergeAll$1(layer$1, layer)))));
38136
38150
  /**
@@ -42903,6 +42917,7 @@ const SENTRY_FLUSH_TIMEOUT_MS = 2e3;
42903
42917
  const METRIC = {
42904
42918
  cliInvoked: "cli.invoked",
42905
42919
  cliError: "cli.error",
42920
+ cliEnvironmentError: "cli.env_error",
42906
42921
  projectDetected: "project.detected",
42907
42922
  projectPathSelected: "project.path_selected",
42908
42923
  projectConfigSelected: "project.config_selected",
@@ -43249,5 +43264,5 @@ const startLanguageServer = () => {
43249
43264
  };
43250
43265
  //#endregion
43251
43266
  export { startLanguageServer };
43252
- !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]="9ee72ccc-703c-588f-b7e4-c92657144721")}catch(e){}}();
43253
- //# debugId=9ee72ccc-703c-588f-b7e4-c92657144721
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.9f28454",
3
+ "version": "0.5.8-dev.ea00b1b",
4
4
  "description": "Your agent writes bad React. This catches it",
5
5
  "keywords": [
6
6
  "accessibility",
@@ -20,7 +20,7 @@
20
20
  "bugs": {
21
21
  "url": "https://github.com/millionco/react-doctor/issues"
22
22
  },
23
- "license": "SEE LICENSE IN LICENSE",
23
+ "license": "MIT",
24
24
  "author": "Million Software, Inc",
25
25
  "repository": {
26
26
  "type": "git",
@@ -63,8 +63,8 @@
63
63
  "vscode-languageserver": "^9.0.1",
64
64
  "vscode-languageserver-textdocument": "^1.0.12",
65
65
  "vscode-uri": "^3.1.0",
66
- "oxlint-plugin-react-doctor": "0.5.8-dev.9f28454",
67
- "deslop-js": "0.5.8"
66
+ "deslop-js": "0.5.8",
67
+ "oxlint-plugin-react-doctor": "0.5.8-dev.ea00b1b"
68
68
  },
69
69
  "devDependencies": {
70
70
  "@types/babel__code-frame": "^7.27.0",
@@ -73,8 +73,8 @@
73
73
  "commander": "^14.0.3",
74
74
  "ora": "^9.4.0",
75
75
  "@react-doctor/api": "0.5.8",
76
- "@react-doctor/core": "0.5.8",
77
- "@react-doctor/language-server": "0.5.8"
76
+ "@react-doctor/language-server": "0.5.8",
77
+ "@react-doctor/core": "0.5.8"
78
78
  },
79
79
  "engines": {
80
80
  "node": "^20.19.0 || >=22.13.0"