react-doctor 0.5.8-dev.b69f4a7 → 0.5.8-dev.b6d1a87
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 +158 -98
- package/dist/index.js +8 -2
- package/dist/lsp.js +10 -3
- package/package.json +3 -3
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]="
|
|
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]="1e81c0a2-fd08-5f5b-ba28-bd5f7587e0f4")}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";
|
|
@@ -40522,6 +40522,11 @@ const toCanonicalPath = (filePath) => {
|
|
|
40522
40522
|
};
|
|
40523
40523
|
const DEAD_CODE_PLUGIN = "deslop";
|
|
40524
40524
|
const DEAD_CODE_CATEGORY = "Maintainability";
|
|
40525
|
+
const REACT_DOCTOR_TOOLCHAIN_PACKAGES = new Set([
|
|
40526
|
+
"react-doctor",
|
|
40527
|
+
"eslint-plugin-react-doctor",
|
|
40528
|
+
"oxlint-plugin-react-doctor"
|
|
40529
|
+
]);
|
|
40525
40530
|
const DEAD_CODE_WORKER_SCRIPT = `
|
|
40526
40531
|
const inputChunks = [];
|
|
40527
40532
|
process.stdin.on("data", (chunk) => inputChunks.push(chunk));
|
|
@@ -40827,6 +40832,7 @@ const checkDeadCode = async (options) => {
|
|
|
40827
40832
|
});
|
|
40828
40833
|
}
|
|
40829
40834
|
for (const unusedDependency of result.unusedDependencies) {
|
|
40835
|
+
if (REACT_DOCTOR_TOOLCHAIN_PACKAGES.has(unusedDependency.name)) continue;
|
|
40830
40836
|
const label = unusedDependency.isDevDependency ? "devDependency" : "dependency";
|
|
40831
40837
|
diagnostics.push({
|
|
40832
40838
|
filePath: "package.json",
|
|
@@ -45059,7 +45065,8 @@ const METRIC = {
|
|
|
45059
45065
|
lspScanCompleted: "lsp.scan.completed",
|
|
45060
45066
|
lspScanDuration: "lsp.scan.duration",
|
|
45061
45067
|
lspScanDiagnostics: "lsp.scan.diagnostics",
|
|
45062
|
-
aiTrainingWarningShown: "ai.training.warning_shown"
|
|
45068
|
+
aiTrainingWarningShown: "ai.training.warning_shown",
|
|
45069
|
+
jsonOutUsed: "json.out_used"
|
|
45063
45070
|
};
|
|
45064
45071
|
//#endregion
|
|
45065
45072
|
//#region src/cli/utils/noop-console.ts
|
|
@@ -45102,7 +45109,7 @@ const makeNoopConsole = () => ({
|
|
|
45102
45109
|
});
|
|
45103
45110
|
//#endregion
|
|
45104
45111
|
//#region src/cli/utils/version.ts
|
|
45105
|
-
const VERSION = "0.5.8-dev.
|
|
45112
|
+
const VERSION = "0.5.8-dev.b6d1a87";
|
|
45106
45113
|
//#endregion
|
|
45107
45114
|
//#region src/cli/utils/json-mode.ts
|
|
45108
45115
|
let context = null;
|
|
@@ -45140,12 +45147,13 @@ const installSilentConsole = () => {
|
|
|
45140
45147
|
"trace"
|
|
45141
45148
|
]) target[key] = source[key];
|
|
45142
45149
|
};
|
|
45143
|
-
const enableJsonMode = ({ compact, directory }) => {
|
|
45150
|
+
const enableJsonMode = ({ compact, directory, outputFile }) => {
|
|
45144
45151
|
context = {
|
|
45145
45152
|
compact,
|
|
45146
45153
|
directory,
|
|
45147
45154
|
startTime: performance$1.now(),
|
|
45148
|
-
mode: "full"
|
|
45155
|
+
mode: "full",
|
|
45156
|
+
outputFile: outputFile ?? null
|
|
45149
45157
|
};
|
|
45150
45158
|
installSilentConsole();
|
|
45151
45159
|
};
|
|
@@ -45158,7 +45166,11 @@ const setJsonReportMode = (mode) => {
|
|
|
45158
45166
|
};
|
|
45159
45167
|
const writeJsonReport = (report) => {
|
|
45160
45168
|
const serialized = context?.compact ? JSON.stringify(report) : JSON.stringify(report, null, 2);
|
|
45161
|
-
|
|
45169
|
+
if (context?.outputFile) {
|
|
45170
|
+
const resolvedPath = Path.resolve(context.outputFile);
|
|
45171
|
+
NFS.mkdirSync(Path.dirname(resolvedPath), { recursive: true });
|
|
45172
|
+
NFS.writeFileSync(resolvedPath, `${serialized}\n`);
|
|
45173
|
+
} else process.stdout.write(`${serialized}\n`);
|
|
45162
45174
|
};
|
|
45163
45175
|
const writeJsonErrorReport = (error, sentryEventId) => {
|
|
45164
45176
|
if (!context) return;
|
|
@@ -45466,13 +45478,13 @@ const isDevVersion = (version) => version === "0.0.0" || version.includes("-");
|
|
|
45466
45478
|
* uploads source-map artifacts under, so stack frames symbolicate. Honors the
|
|
45467
45479
|
* standard `SENTRY_RELEASE` override.
|
|
45468
45480
|
*/
|
|
45469
|
-
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.5.8-dev.
|
|
45481
|
+
const resolveSentryRelease = () => process.env.SENTRY_RELEASE || `react-doctor@0.5.8-dev.b6d1a87`;
|
|
45470
45482
|
/**
|
|
45471
45483
|
* Deployment environment shown in Sentry's environment filter. Defaults to
|
|
45472
45484
|
* `production` for tagged releases and `development` for dev/unbuilt versions,
|
|
45473
45485
|
* overridable via the standard `SENTRY_ENVIRONMENT` env var.
|
|
45474
45486
|
*/
|
|
45475
|
-
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.5.8-dev.
|
|
45487
|
+
const resolveSentryEnvironment = () => process.env.SENTRY_ENVIRONMENT || (isDevVersion("0.5.8-dev.b6d1a87") ? "development" : "production");
|
|
45476
45488
|
/**
|
|
45477
45489
|
* Performance-tracing sample rate in `[0, 1]`. Reads `SENTRY_TRACES_SAMPLE_RATE`
|
|
45478
45490
|
* (set to `0` to disable tracing) and falls back to
|
|
@@ -50556,6 +50568,7 @@ const NON_BLOCKING_REACT_DOCTOR_COMMAND = [
|
|
|
50556
50568
|
`if ${REACT_DOCTOR_COMMAND} > "$react_doctor_output" 2>&1; then`,
|
|
50557
50569
|
"rm -f \"$react_doctor_output\";",
|
|
50558
50570
|
"else",
|
|
50571
|
+
"cat \"$react_doctor_output\" >&2;",
|
|
50559
50572
|
"rm -f \"$react_doctor_output\";",
|
|
50560
50573
|
`printf "%s\\n" "React Doctor found staged regressions." "Run ${REACT_DOCTOR_COMMAND} to inspect." "Want them fixed? Ask your agent to run that command and resolve the findings." >&2;`,
|
|
50561
50574
|
"fi"
|
|
@@ -52253,10 +52266,11 @@ const rememberInstallAgents = (agents, options = {}) => writePreference(INSTALL_
|
|
|
52253
52266
|
const CLAUDE_AGENT = "claude-code";
|
|
52254
52267
|
const CURSOR_AGENT = "cursor";
|
|
52255
52268
|
const CLAUDE_SETTINGS_RELATIVE_PATH = ".claude/settings.json";
|
|
52256
|
-
const CLAUDE_HOOK_RELATIVE_PATH = ".claude/hooks/react-doctor.
|
|
52257
|
-
const CLAUDE_HOOK_COMMAND = "
|
|
52269
|
+
const CLAUDE_HOOK_RELATIVE_PATH = ".claude/hooks/react-doctor.mjs";
|
|
52270
|
+
const CLAUDE_HOOK_COMMAND = "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/react-doctor.mjs\"";
|
|
52258
52271
|
const CURSOR_HOOKS_RELATIVE_PATH = ".cursor/hooks.json";
|
|
52259
|
-
const CURSOR_HOOK_RELATIVE_PATH = ".cursor/hooks/react-doctor.
|
|
52272
|
+
const CURSOR_HOOK_RELATIVE_PATH = ".cursor/hooks/react-doctor.mjs";
|
|
52273
|
+
const CURSOR_HOOK_COMMAND = "node .cursor/hooks/react-doctor.mjs";
|
|
52260
52274
|
const CURSOR_HOOK_MATCHER = "Write|Edit|MultiEdit|ApplyPatch";
|
|
52261
52275
|
const CURSOR_HOOKS_SCHEMA_VERSION = 1;
|
|
52262
52276
|
const isSupportedAgent = (agent) => agent === CLAUDE_AGENT || agent === CURSOR_AGENT;
|
|
@@ -52288,7 +52302,6 @@ const writeJsonFileWithDirectoryCheck = (filePath, value) => {
|
|
|
52288
52302
|
const writeHookScript = (filePath) => {
|
|
52289
52303
|
ensureDirectoryExists(Path.dirname(filePath));
|
|
52290
52304
|
NFS.writeFileSync(filePath, buildAgentHookScript());
|
|
52291
|
-
NFS.chmodSync(filePath, 493);
|
|
52292
52305
|
};
|
|
52293
52306
|
const hasClaudeHookCommand = (groups) => groups.some((group) => (group.hooks ?? []).some((hook) => hook.command === CLAUDE_HOOK_COMMAND));
|
|
52294
52307
|
const installClaudeHook = (projectRoot) => {
|
|
@@ -52309,7 +52322,7 @@ const installClaudeHook = (projectRoot) => {
|
|
|
52309
52322
|
writeHookScript(hookPath);
|
|
52310
52323
|
return [settingsPath, hookPath];
|
|
52311
52324
|
};
|
|
52312
|
-
const hasCursorHookCommand = (handlers) => handlers.some((handler) => handler.command ===
|
|
52325
|
+
const hasCursorHookCommand = (handlers) => handlers.some((handler) => handler.command === CURSOR_HOOK_COMMAND);
|
|
52313
52326
|
const installCursorHook = (projectRoot) => {
|
|
52314
52327
|
const configPath = Path.join(projectRoot, CURSOR_HOOKS_RELATIVE_PATH);
|
|
52315
52328
|
const hookPath = Path.join(projectRoot, CURSOR_HOOK_RELATIVE_PATH);
|
|
@@ -52317,7 +52330,7 @@ const installCursorHook = (projectRoot) => {
|
|
|
52317
52330
|
const hooks = { ...config.hooks ?? {} };
|
|
52318
52331
|
const postToolUseHooks = [...hooks.postToolUse ?? []];
|
|
52319
52332
|
if (!hasCursorHookCommand(postToolUseHooks)) postToolUseHooks.push({
|
|
52320
|
-
command:
|
|
52333
|
+
command: CURSOR_HOOK_COMMAND,
|
|
52321
52334
|
matcher: CURSOR_HOOK_MATCHER,
|
|
52322
52335
|
timeout: 120
|
|
52323
52336
|
});
|
|
@@ -52331,102 +52344,111 @@ const installCursorHook = (projectRoot) => {
|
|
|
52331
52344
|
return [configPath, hookPath];
|
|
52332
52345
|
};
|
|
52333
52346
|
const buildAgentHookScript = () => [
|
|
52334
|
-
"
|
|
52335
|
-
"
|
|
52347
|
+
"import { readFileSync, writeFileSync, unlinkSync } from 'node:fs';",
|
|
52348
|
+
"import { tmpdir } from 'node:os';",
|
|
52349
|
+
"import { join, dirname } from 'node:path';",
|
|
52350
|
+
"import { fileURLToPath } from 'node:url';",
|
|
52351
|
+
"import { spawnSync } from 'node:child_process';",
|
|
52336
52352
|
"",
|
|
52337
|
-
"
|
|
52338
|
-
"
|
|
52339
|
-
"trap 'rm -f \"$input_file\" \"$output_file\"' EXIT",
|
|
52340
|
-
"cat > \"$input_file\"",
|
|
52353
|
+
"const __filename = fileURLToPath(import.meta.url);",
|
|
52354
|
+
"const __dirname = dirname(__filename);",
|
|
52341
52355
|
"",
|
|
52342
|
-
"
|
|
52343
|
-
"project_root=${CLAUDE_PROJECT_DIR:-}",
|
|
52344
|
-
"if [ -z \"$project_root\" ]; then",
|
|
52345
|
-
" project_root=$(CDPATH= cd \"$script_dir/../..\" && pwd)",
|
|
52346
|
-
"fi",
|
|
52347
|
-
"if ! cd \"$project_root\"; then",
|
|
52348
|
-
" exit 0",
|
|
52349
|
-
"fi",
|
|
52356
|
+
"const EDIT_TOOL_NAMES = new Set(['Edit', 'Write', 'MultiEdit', 'NotebookEdit', 'ApplyPatch']);",
|
|
52350
52357
|
"",
|
|
52351
|
-
"
|
|
52352
|
-
"
|
|
52353
|
-
" return 0",
|
|
52354
|
-
"
|
|
52358
|
+
"const readStdin = () => {",
|
|
52359
|
+
" try {",
|
|
52360
|
+
" return readFileSync(0, 'utf8');",
|
|
52361
|
+
" } catch {",
|
|
52362
|
+
" return '';",
|
|
52363
|
+
" }",
|
|
52364
|
+
"};",
|
|
52355
52365
|
"",
|
|
52356
|
-
"
|
|
52357
|
-
"const fs = require('node:fs');",
|
|
52358
|
-
"const inputPath = process.argv[2];",
|
|
52359
|
-
"const editToolNames = new Set(['Edit', 'Write', 'MultiEdit', 'NotebookEdit', 'ApplyPatch']);",
|
|
52360
|
-
"try {",
|
|
52361
|
-
" const input = JSON.parse(fs.readFileSync(inputPath, 'utf8') || '{}');",
|
|
52366
|
+
"const shouldScan = (input) => {",
|
|
52362
52367
|
" const eventName = input.hook_event_name || input.eventName || input.event_name;",
|
|
52363
52368
|
" if (eventName === 'PostToolBatch') {",
|
|
52364
52369
|
" const toolCalls = Array.isArray(input.tool_calls) ? input.tool_calls : [];",
|
|
52365
|
-
"
|
|
52370
|
+
" return toolCalls.some((toolCall) => EDIT_TOOL_NAMES.has(toolCall.tool_name));",
|
|
52366
52371
|
" }",
|
|
52367
52372
|
" const toolName = input.tool_name || input.toolName || input.tool;",
|
|
52368
|
-
"
|
|
52369
|
-
"}
|
|
52370
|
-
" process.exit(0);",
|
|
52371
|
-
"}",
|
|
52372
|
-
"NODE",
|
|
52373
|
-
"}",
|
|
52373
|
+
" return !toolName || EDIT_TOOL_NAMES.has(toolName);",
|
|
52374
|
+
"};",
|
|
52374
52375
|
"",
|
|
52375
|
-
"
|
|
52376
|
-
"
|
|
52377
|
-
"
|
|
52378
|
-
"
|
|
52379
|
-
"
|
|
52376
|
+
"const runReactDoctor = (outputPath) => {",
|
|
52377
|
+
" // Each candidate is a single shell command string (not an args array):",
|
|
52378
|
+
" // `shell: true` is required to run the Windows `.cmd` shims, and an args",
|
|
52379
|
+
" // array with `shell: true` trips Node's DEP0190. A missing command exits",
|
|
52380
|
+
" // 127 via the shell (no ENOENT error), so fall through on that. With no",
|
|
52381
|
+
" // runner found, exit 0 silently — stdout is parsed as the hook's JSON.",
|
|
52382
|
+
" const commands = [",
|
|
52383
|
+
" './node_modules/.bin/react-doctor --verbose --scope changed --blocking warning --no-score',",
|
|
52384
|
+
" 'react-doctor --verbose --scope changed --blocking warning --no-score',",
|
|
52385
|
+
" 'pnpm dlx react-doctor@latest --verbose --scope changed --blocking warning --no-score',",
|
|
52386
|
+
" 'npx --yes react-doctor@latest --verbose --scope changed --blocking warning --no-score',",
|
|
52387
|
+
" ];",
|
|
52380
52388
|
"",
|
|
52381
|
-
"
|
|
52382
|
-
"
|
|
52383
|
-
"
|
|
52384
|
-
"
|
|
52389
|
+
" for (const command of commands) {",
|
|
52390
|
+
" const result = spawnSync(command, { encoding: 'utf8', shell: true });",
|
|
52391
|
+
" if (result.error?.code === 'ENOENT' || result.status === 127) continue;",
|
|
52392
|
+
" try {",
|
|
52393
|
+
" writeFileSync(outputPath, (result.stdout || '') + (result.stderr || ''));",
|
|
52394
|
+
" } catch {}",
|
|
52395
|
+
" return result.status;",
|
|
52396
|
+
" }",
|
|
52385
52397
|
"",
|
|
52386
|
-
"
|
|
52387
|
-
"
|
|
52388
|
-
" return",
|
|
52389
|
-
" fi",
|
|
52398
|
+
" return 0;",
|
|
52399
|
+
"};",
|
|
52390
52400
|
"",
|
|
52391
|
-
"
|
|
52392
|
-
"
|
|
52393
|
-
"
|
|
52394
|
-
"
|
|
52401
|
+
"const cleanup = (...paths) => {",
|
|
52402
|
+
" for (const path of paths) {",
|
|
52403
|
+
" try { unlinkSync(path); } catch {}",
|
|
52404
|
+
" }",
|
|
52405
|
+
"};",
|
|
52395
52406
|
"",
|
|
52396
|
-
"
|
|
52397
|
-
"
|
|
52398
|
-
"
|
|
52407
|
+
"const main = () => {",
|
|
52408
|
+
" let input;",
|
|
52409
|
+
" try {",
|
|
52410
|
+
" const stdinContent = readStdin();",
|
|
52411
|
+
" input = JSON.parse(stdinContent || '{}');",
|
|
52412
|
+
" } catch {",
|
|
52413
|
+
" input = {};",
|
|
52414
|
+
" }",
|
|
52399
52415
|
"",
|
|
52400
|
-
"if !
|
|
52401
|
-
"
|
|
52402
|
-
"
|
|
52416
|
+
" if (!shouldScan(input)) {",
|
|
52417
|
+
" process.exit(0);",
|
|
52418
|
+
" }",
|
|
52403
52419
|
"",
|
|
52404
|
-
"
|
|
52405
|
-
"
|
|
52406
|
-
"fi",
|
|
52420
|
+
" const projectRoot = process.env.CLAUDE_PROJECT_DIR || join(__dirname, '../..');",
|
|
52421
|
+
" const outputPath = join(tmpdir(), `react-doctor-agent-hook-output-${process.pid}.txt`);",
|
|
52407
52422
|
"",
|
|
52408
|
-
"node - \"$input_file\" \"$output_file\" <<'NODE'",
|
|
52409
|
-
"const fs = require('node:fs');",
|
|
52410
|
-
"const inputPath = process.argv[2];",
|
|
52411
|
-
"const outputPath = process.argv[3];",
|
|
52412
|
-
"const readInput = () => {",
|
|
52413
52423
|
" try {",
|
|
52414
|
-
"
|
|
52424
|
+
" process.chdir(projectRoot);",
|
|
52415
52425
|
" } catch {",
|
|
52416
|
-
"
|
|
52426
|
+
" process.exit(0);",
|
|
52427
|
+
" }",
|
|
52428
|
+
"",
|
|
52429
|
+
" const scanResult = runReactDoctor(outputPath);",
|
|
52430
|
+
" if (scanResult === 0) {",
|
|
52431
|
+
" cleanup(outputPath);",
|
|
52432
|
+
" process.exit(0);",
|
|
52433
|
+
" }",
|
|
52434
|
+
"",
|
|
52435
|
+
" const scanOutput = readFileSync(outputPath, 'utf8').trim();",
|
|
52436
|
+
" cleanup(outputPath);",
|
|
52437
|
+
"",
|
|
52438
|
+
" if (!scanOutput) {",
|
|
52439
|
+
" process.exit(0);",
|
|
52440
|
+
" }",
|
|
52441
|
+
"",
|
|
52442
|
+
" const message = `React Doctor found issues in the changed files. Review this output and fix the regressions before finishing. For confirmed issues that cannot be fixed now, create GitHub issues with the rule, file/line, confidence, impact, and proposed fix.\\n\\n${scanOutput}`;",
|
|
52443
|
+
"",
|
|
52444
|
+
" if (input.hook_event_name === 'PostToolBatch') {",
|
|
52445
|
+
" console.log(JSON.stringify({ hookSpecificOutput: { hookEventName: 'PostToolBatch', additionalContext: message } }));",
|
|
52446
|
+
" } else {",
|
|
52447
|
+
" console.log(JSON.stringify({ additional_context: message }));",
|
|
52417
52448
|
" }",
|
|
52418
52449
|
"};",
|
|
52419
|
-
"
|
|
52420
|
-
"
|
|
52421
|
-
"if (!scanOutput) process.exit(0);",
|
|
52422
|
-
"const message = `React Doctor found issues in the changed files. Review this output and fix the regressions before finishing. For confirmed issues that cannot be fixed now, create GitHub issues with the rule, file/line, confidence, impact, and proposed fix.\\n\\n${scanOutput}`;",
|
|
52423
|
-
"if (input.hook_event_name === 'PostToolBatch') {",
|
|
52424
|
-
" console.log(JSON.stringify({ hookSpecificOutput: { hookEventName: 'PostToolBatch', additionalContext: message } }));",
|
|
52425
|
-
"} else {",
|
|
52426
|
-
" console.log(JSON.stringify({ additional_context: message }));",
|
|
52427
|
-
"}",
|
|
52428
|
-
"NODE",
|
|
52429
|
-
""
|
|
52450
|
+
"",
|
|
52451
|
+
"main();"
|
|
52430
52452
|
].join("\n");
|
|
52431
52453
|
const installReactDoctorAgentHooks = (options) => {
|
|
52432
52454
|
const installedAgents = [];
|
|
@@ -52487,6 +52509,7 @@ const buildReactDoctorHookBlock = () => [
|
|
|
52487
52509
|
"if react_doctor_scan_staged_files > \"$react_doctor_output\" 2>&1; then",
|
|
52488
52510
|
" rm -f \"$react_doctor_output\"",
|
|
52489
52511
|
"else",
|
|
52512
|
+
" cat \"$react_doctor_output\" >&2",
|
|
52490
52513
|
" rm -f \"$react_doctor_output\"",
|
|
52491
52514
|
` printf '%s\\n' "React Doctor found staged regressions." "Run ${REACT_DOCTOR_COMMAND} to inspect." "Want them fixed? Ask your agent to run that command and resolve the findings." >&2`,
|
|
52492
52515
|
"fi",
|
|
@@ -53252,6 +53275,26 @@ const CLI_AGENT_AUTO_FLAGS = {
|
|
|
53252
53275
|
codex: ["--yolo"],
|
|
53253
53276
|
cursor: ["--force"]
|
|
53254
53277
|
};
|
|
53278
|
+
const resolveCursorBundledNode = () => {
|
|
53279
|
+
if (!isWindows) return null;
|
|
53280
|
+
const localAppData = process.env.LOCALAPPDATA;
|
|
53281
|
+
if (!localAppData) return null;
|
|
53282
|
+
const cursorAgentRoot = Path.join(localAppData, "cursor-agent", "versions");
|
|
53283
|
+
try {
|
|
53284
|
+
const versions = NFS.readdirSync(cursorAgentRoot);
|
|
53285
|
+
if (versions.length === 0) return null;
|
|
53286
|
+
const sortedVersions = versions.sort((a, b) => b.localeCompare(a, void 0, { numeric: true }));
|
|
53287
|
+
for (const version of sortedVersions) try {
|
|
53288
|
+
const bundledNodePath = Path.join(cursorAgentRoot, version, "node.exe");
|
|
53289
|
+
const entryScriptPath = Path.join(cursorAgentRoot, version, "index.js");
|
|
53290
|
+
if (NFS.statSync(bundledNodePath).isFile() && NFS.statSync(entryScriptPath).isFile()) return {
|
|
53291
|
+
command: bundledNodePath,
|
|
53292
|
+
args: [entryScriptPath]
|
|
53293
|
+
};
|
|
53294
|
+
} catch {}
|
|
53295
|
+
} catch {}
|
|
53296
|
+
return null;
|
|
53297
|
+
};
|
|
53255
53298
|
const resolveWindowsCmdEntryScript = (command) => {
|
|
53256
53299
|
if (!isWindows) return null;
|
|
53257
53300
|
const pathDirectories = (process.env.PATH ?? "").split(Path.delimiter).filter(Boolean);
|
|
@@ -53271,11 +53314,10 @@ const resolveWindowsCmdEntryScript = (command) => {
|
|
|
53271
53314
|
}
|
|
53272
53315
|
return null;
|
|
53273
53316
|
};
|
|
53274
|
-
const spawnAgent = (command, args, cwd
|
|
53317
|
+
const spawnAgent = (command, args, cwd) => new Promise((resolve, reject) => {
|
|
53275
53318
|
const child = spawn(command, [...args], {
|
|
53276
53319
|
cwd,
|
|
53277
|
-
stdio: "inherit"
|
|
53278
|
-
shell
|
|
53320
|
+
stdio: "inherit"
|
|
53279
53321
|
});
|
|
53280
53322
|
child.on("error", reject);
|
|
53281
53323
|
child.on("close", (code) => resolve(code ?? 0));
|
|
@@ -53284,6 +53326,10 @@ const launchCliAgent = async (agentId, prompt, cwd) => {
|
|
|
53284
53326
|
const binary = CLI_AGENT_BINARIES[agentId];
|
|
53285
53327
|
const agentArgs = [...CLI_AGENT_AUTO_FLAGS[agentId], prompt];
|
|
53286
53328
|
if (isWindows) {
|
|
53329
|
+
if (agentId === "cursor") {
|
|
53330
|
+
const cursorResolution = resolveCursorBundledNode();
|
|
53331
|
+
if (cursorResolution) return spawnAgent(cursorResolution.command, [...cursorResolution.args, ...agentArgs], cwd);
|
|
53332
|
+
}
|
|
53287
53333
|
const entryScript = resolveWindowsCmdEntryScript(binary);
|
|
53288
53334
|
if (entryScript) return spawnAgent(process.execPath, [entryScript, ...agentArgs], cwd);
|
|
53289
53335
|
}
|
|
@@ -54396,10 +54442,14 @@ const inspectAction = async (directory, flags) => {
|
|
|
54396
54442
|
const isQuiet = isScoreOnly || isJsonMode;
|
|
54397
54443
|
const requestedDirectory = Path.resolve(directory);
|
|
54398
54444
|
const startTime = performance$1.now();
|
|
54399
|
-
if (isJsonMode)
|
|
54400
|
-
|
|
54401
|
-
|
|
54402
|
-
|
|
54445
|
+
if (isJsonMode) {
|
|
54446
|
+
enableJsonMode({
|
|
54447
|
+
compact: Boolean(flags.jsonCompact),
|
|
54448
|
+
directory: requestedDirectory,
|
|
54449
|
+
outputFile: flags.jsonOut
|
|
54450
|
+
});
|
|
54451
|
+
if (flags.jsonOut) recordCount(METRIC.jsonOutUsed, 1);
|
|
54452
|
+
}
|
|
54403
54453
|
recordCount(METRIC.cliInvoked, 1, { command: "inspect" });
|
|
54404
54454
|
try {
|
|
54405
54455
|
validateModeFlags(flags);
|
|
@@ -55337,6 +55387,14 @@ const applyColorPreference = (argv, env = process.env) => {
|
|
|
55337
55387
|
setColorEnabled(enabled);
|
|
55338
55388
|
};
|
|
55339
55389
|
//#endregion
|
|
55390
|
+
//#region src/cli/utils/ensure-windows-utf8-console.ts
|
|
55391
|
+
const ensureWindowsUtf8Console = () => {
|
|
55392
|
+
if (process.platform !== "win32" || !process.stdout.isTTY) return;
|
|
55393
|
+
try {
|
|
55394
|
+
execFileSync("chcp.com", ["65001"], { stdio: "ignore" });
|
|
55395
|
+
} catch {}
|
|
55396
|
+
};
|
|
55397
|
+
//#endregion
|
|
55340
55398
|
//#region src/cli/utils/exit-gracefully.ts
|
|
55341
55399
|
const exitGracefully = () => {
|
|
55342
55400
|
try {
|
|
@@ -55479,6 +55537,7 @@ const ROOT_FLAG_SPEC = {
|
|
|
55479
55537
|
"--category",
|
|
55480
55538
|
"--changed-files-from",
|
|
55481
55539
|
"--blocking",
|
|
55540
|
+
"--json-out",
|
|
55482
55541
|
"--output-dir",
|
|
55483
55542
|
"--fail-on",
|
|
55484
55543
|
"--project",
|
|
@@ -55669,7 +55728,7 @@ ${highlighter.dim("Learn more:")}
|
|
|
55669
55728
|
${highlighter.info(CANONICAL_GITHUB_URL)}
|
|
55670
55729
|
`;
|
|
55671
55730
|
const collectCategoryOption = (value, previousValues) => [...previousValues ?? [], value];
|
|
55672
|
-
const program = new Command().name("react-doctor").description("Diagnose React codebase health").version(VERSION, "-v, --version", "display the version number").argument("[directory]", "project directory to scan", ".").option("--lint", "enable linting").option("--no-lint", "skip linting").option("--dead-code", "enable dead-code analysis (default)").option("--no-dead-code", "skip dead-code analysis (unused files / exports / dependencies, circular imports)").option("--verbose", "show every rule and per-file details (default shows top 3 rules)").option("--debug", "force a Sentry trace and print its id at the end (paste it into a bug report)").option("--output-dir <dir>", "directory for the full diagnostics dump (default: a temp folder)").option("--score", "output only the score").option("--json", "output a single structured JSON report (suppresses other output)").option("--json-compact", "with --json, emit compact JSON (no indentation)").option("-y, --yes", "skip prompts, scan all workspace projects").option("--no-parallel", "lint serially with one worker (default: parallel across CPU cores; set the worker count with REACT_DOCTOR_PARALLEL)").option("--project <name>", "select projects: workspace names or directory paths (comma-separated for multiple); overrides the `projects` config field").option("--scope <value>", "how much to scan/report: full (default), files, changed (only new issues vs base), or lines (only changed lines)").option("--base <ref>", "base git ref for files/changed/lines scope (auto-detected when omitted)").addOption(new Option("--diff [base]", "[deprecated] alias for --scope changed (pass `false` to force a full scan)").hideHelp()).addOption(new Option("--changed-files-from <file>", "scan source files listed in a newline-delimited changed-files file").hideHelp()).option("--no-score", "skip the score API, the share URL, and crash reporting").addOption(new Option("--category <category>", "only show diagnostics in a category (repeatable; e.g. Security)").argParser(collectCategoryOption)).option("--no-telemetry", "alias for --no-score (skip the score API, share URL, and crash reporting)").option("--staged", "scan only staged (git index) files for pre-commit hooks").option("--blocking <level>", "severity that fails CI: error (default), warning, or none (advisory)").addOption(new Option("--fail-on <level>", "[deprecated] alias for --blocking <level>").hideHelp()).option("--no-respect-inline-disables", "audit mode: neutralize inline lint suppressions before scanning").option("--warnings", "show warning-severity diagnostics (default)").option("--no-warnings", "hide warning-severity diagnostics (errors only)").option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").addHelpText("after", renderRootHelpEpilog);
|
|
55731
|
+
const program = new Command().name("react-doctor").description("Diagnose React codebase health").version(VERSION, "-v, --version", "display the version number").argument("[directory]", "project directory to scan", ".").option("--lint", "enable linting").option("--no-lint", "skip linting").option("--dead-code", "enable dead-code analysis (default)").option("--no-dead-code", "skip dead-code analysis (unused files / exports / dependencies, circular imports)").option("--verbose", "show every rule and per-file details (default shows top 3 rules)").option("--debug", "force a Sentry trace and print its id at the end (paste it into a bug report)").option("--output-dir <dir>", "directory for the full diagnostics dump (default: a temp folder)").option("--score", "output only the score").option("--json", "output a single structured JSON report (suppresses other output)").option("--json-compact", "with --json, emit compact JSON (no indentation)").option("--json-out <path>", "with --json, write the report to a file instead of stdout").option("-y, --yes", "skip prompts, scan all workspace projects").option("--no-parallel", "lint serially with one worker (default: parallel across CPU cores; set the worker count with REACT_DOCTOR_PARALLEL)").option("--project <name>", "select projects: workspace names or directory paths (comma-separated for multiple); overrides the `projects` config field").option("--scope <value>", "how much to scan/report: full (default), files, changed (only new issues vs base), or lines (only changed lines)").option("--base <ref>", "base git ref for files/changed/lines scope (auto-detected when omitted)").addOption(new Option("--diff [base]", "[deprecated] alias for --scope changed (pass `false` to force a full scan)").hideHelp()).addOption(new Option("--changed-files-from <file>", "scan source files listed in a newline-delimited changed-files file").hideHelp()).option("--no-score", "skip the score API, the share URL, and crash reporting").addOption(new Option("--category <category>", "only show diagnostics in a category (repeatable; e.g. Security)").argParser(collectCategoryOption)).option("--no-telemetry", "alias for --no-score (skip the score API, share URL, and crash reporting)").option("--staged", "scan only staged (git index) files for pre-commit hooks").option("--blocking <level>", "severity that fails CI: error (default), warning, or none (advisory)").addOption(new Option("--fail-on <level>", "[deprecated] alias for --blocking <level>").hideHelp()).option("--no-respect-inline-disables", "audit mode: neutralize inline lint suppressions before scanning").option("--warnings", "show warning-severity diagnostics (default)").option("--no-warnings", "hide warning-severity diagnostics (errors only)").option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").addHelpText("after", renderRootHelpEpilog);
|
|
55673
55732
|
program.action(inspectAction);
|
|
55674
55733
|
program.command("why <location>").description("Explain why a rule fired (or why a suppression didn't apply) at a file:line").option("--project <name>", "select projects: workspace names or directory paths (comma-separated for multiple)").option("-c, --cwd <cwd>", "working directory", process.cwd()).option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").action((location, options) => whyAction(location, options));
|
|
55675
55734
|
program.command("install").alias("setup").description("Install the react-doctor skill into your coding agents and optional git hook").option("-y, --yes", "skip prompts, install for all detected agents").option("--dry-run", "show what would be installed without writing files").option("--agent-hooks", "install native non-blocking agent hooks for Claude Code and Cursor").option("-c, --cwd <cwd>", "working directory", process.cwd()).option("--color", "force colored output").option("--no-color", "disable colored output (also honors NO_COLOR)").addHelpText("after", renderInstallHelpEpilog).action(installAction);
|
|
@@ -55694,6 +55753,7 @@ if (process.argv.includes("-V") && !strippedArgv.includes("-V")) {
|
|
|
55694
55753
|
process.exit(0);
|
|
55695
55754
|
}
|
|
55696
55755
|
applyColorPreference(strippedArgv);
|
|
55756
|
+
ensureWindowsUtf8Console();
|
|
55697
55757
|
const argv = normalizeHelpInvocation(strippedArgv, knownCommands);
|
|
55698
55758
|
Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => program.parseAsync(argv)).then(() => flushSentry()).catch(async (error) => {
|
|
55699
55759
|
const isUserError = isExpectedUserError(error);
|
|
@@ -55712,4 +55772,4 @@ Promise.resolve().then(() => assertNoRemovedFlags(process.argv)).then(() => prog
|
|
|
55712
55772
|
export {};
|
|
55713
55773
|
|
|
55714
55774
|
//# sourceMappingURL=cli.js.map
|
|
55715
|
-
//# debugId=
|
|
55775
|
+
//# debugId=1e81c0a2-fd08-5f5b-ba28-bd5f7587e0f4
|
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]="
|
|
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]="faa2d232-c423-58f8-97f4-ba8d41a199d7")}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";
|
|
@@ -37290,6 +37290,11 @@ const toCanonicalPath = (filePath) => {
|
|
|
37290
37290
|
};
|
|
37291
37291
|
const DEAD_CODE_PLUGIN = "deslop";
|
|
37292
37292
|
const DEAD_CODE_CATEGORY = "Maintainability";
|
|
37293
|
+
const REACT_DOCTOR_TOOLCHAIN_PACKAGES = new Set([
|
|
37294
|
+
"react-doctor",
|
|
37295
|
+
"eslint-plugin-react-doctor",
|
|
37296
|
+
"oxlint-plugin-react-doctor"
|
|
37297
|
+
]);
|
|
37293
37298
|
const DEAD_CODE_WORKER_SCRIPT = `
|
|
37294
37299
|
const inputChunks = [];
|
|
37295
37300
|
process.stdin.on("data", (chunk) => inputChunks.push(chunk));
|
|
@@ -37595,6 +37600,7 @@ const checkDeadCode = async (options) => {
|
|
|
37595
37600
|
});
|
|
37596
37601
|
}
|
|
37597
37602
|
for (const unusedDependency of result.unusedDependencies) {
|
|
37603
|
+
if (REACT_DOCTOR_TOOLCHAIN_PACKAGES.has(unusedDependency.name)) continue;
|
|
37598
37604
|
const label = unusedDependency.isDevDependency ? "devDependency" : "dependency";
|
|
37599
37605
|
diagnostics.push({
|
|
37600
37606
|
filePath: "package.json",
|
|
@@ -41645,4 +41651,4 @@ const toJsonReport = (result, options) => buildJsonReport({
|
|
|
41645
41651
|
export { AmbiguousProjectError, NoReactDependencyError, NotADirectoryError, PackageJsonNotFoundError, ProjectNotFoundError, ReactDoctorError, buildJsonReport, buildJsonReportError, clearCaches, defineConfig, diagnose, filterSourceFiles, getDiffInfo, isProjectDiscoveryError, isReactDoctorError, summarizeDiagnostics, toJsonReport };
|
|
41646
41652
|
|
|
41647
41653
|
//# sourceMappingURL=index.js.map
|
|
41648
|
-
//# debugId=
|
|
41654
|
+
//# debugId=faa2d232-c423-58f8-97f4-ba8d41a199d7
|
package/dist/lsp.js
CHANGED
|
@@ -37275,6 +37275,11 @@ const toCanonicalPath = (filePath) => {
|
|
|
37275
37275
|
};
|
|
37276
37276
|
const DEAD_CODE_PLUGIN = "deslop";
|
|
37277
37277
|
const DEAD_CODE_CATEGORY = "Maintainability";
|
|
37278
|
+
const REACT_DOCTOR_TOOLCHAIN_PACKAGES = new Set([
|
|
37279
|
+
"react-doctor",
|
|
37280
|
+
"eslint-plugin-react-doctor",
|
|
37281
|
+
"oxlint-plugin-react-doctor"
|
|
37282
|
+
]);
|
|
37278
37283
|
const DEAD_CODE_WORKER_SCRIPT = `
|
|
37279
37284
|
const inputChunks = [];
|
|
37280
37285
|
process.stdin.on("data", (chunk) => inputChunks.push(chunk));
|
|
@@ -37580,6 +37585,7 @@ const checkDeadCode = async (options) => {
|
|
|
37580
37585
|
});
|
|
37581
37586
|
}
|
|
37582
37587
|
for (const unusedDependency of result.unusedDependencies) {
|
|
37588
|
+
if (REACT_DOCTOR_TOOLCHAIN_PACKAGES.has(unusedDependency.name)) continue;
|
|
37583
37589
|
const label = unusedDependency.isDevDependency ? "devDependency" : "dependency";
|
|
37584
37590
|
diagnostics.push({
|
|
37585
37591
|
filePath: "package.json",
|
|
@@ -43047,7 +43053,8 @@ const METRIC = {
|
|
|
43047
43053
|
lspScanCompleted: "lsp.scan.completed",
|
|
43048
43054
|
lspScanDuration: "lsp.scan.duration",
|
|
43049
43055
|
lspScanDiagnostics: "lsp.scan.diagnostics",
|
|
43050
|
-
aiTrainingWarningShown: "ai.training.warning_shown"
|
|
43056
|
+
aiTrainingWarningShown: "ai.training.warning_shown",
|
|
43057
|
+
jsonOutUsed: "json.out_used"
|
|
43051
43058
|
};
|
|
43052
43059
|
//#endregion
|
|
43053
43060
|
//#region src/cli/utils/to-category-key.ts
|
|
@@ -43364,5 +43371,5 @@ const startLanguageServer = () => {
|
|
|
43364
43371
|
};
|
|
43365
43372
|
//#endregion
|
|
43366
43373
|
export { startLanguageServer };
|
|
43367
|
-
!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]="
|
|
43368
|
-
//# debugId=
|
|
43374
|
+
!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]="47940f68-872d-59ed-bdd2-40ef7848566c")}catch(e){}}();
|
|
43375
|
+
//# debugId=47940f68-872d-59ed-bdd2-40ef7848566c
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-doctor",
|
|
3
|
-
"version": "0.5.8-dev.
|
|
3
|
+
"version": "0.5.8-dev.b6d1a87",
|
|
4
4
|
"description": "Your agent writes bad React. This catches it",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"accessibility",
|
|
@@ -60,12 +60,12 @@
|
|
|
60
60
|
"magicast": "^0.5.3",
|
|
61
61
|
"oxlint": ">=1.66.0 <1.67.0",
|
|
62
62
|
"prompts": "^2.4.2",
|
|
63
|
-
"typescript": ">=5.0.4 <
|
|
63
|
+
"typescript": ">=5.0.4 <6",
|
|
64
64
|
"vscode-languageserver": "^9.0.1",
|
|
65
65
|
"vscode-languageserver-textdocument": "^1.0.12",
|
|
66
66
|
"vscode-uri": "^3.1.0",
|
|
67
67
|
"deslop-js": "0.5.8",
|
|
68
|
-
"oxlint-plugin-react-doctor": "0.5.8-dev.
|
|
68
|
+
"oxlint-plugin-react-doctor": "0.5.8-dev.b6d1a87"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
71
|
"@types/babel__code-frame": "^7.27.0",
|