react-doctor 0.2.9 → 0.2.11
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-logger-BliQX9s8.js → cli-logger-pbFEieEc.js} +342 -40
- package/dist/cli.js +113 -85
- package/dist/index.d.ts +21 -2
- package/dist/index.js +340 -38
- package/package.json +4 -4
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { i as __toESM, n as __exportAll, r as __require, t as __commonJSMin } from "./rolldown-runtime-uZX_iqCz.js";
|
|
2
|
-
import { A as isReactDoctorError, C as filterSourceFiles, D as groupBy, E as getDiffInfo, F as runInspect, I as toRelativePath, M as listWorkspacePackages, N as resolveScanTarget, O as highlighter, P as restoreLegacyThrow, S as filterDiagnosticsForSurface, T as formatReactDoctorError, _ as Score, a as DeadCode, b as buildJsonReportError, c as LintPartialFailures, d as OXLINT_NODE_REQUIREMENT, f as Progress, g as SKILL_NAME, h as SHARE_BASE_URL, i as Config, j as layerOtlp, k as isMonorepoRoot, l as Linter, m as Reporter, o as Files, p as Project, r as CANONICAL_GITHUB_URL, s as Git, t as cliLogger, u as NodeResolver, v as StagedFiles, w as formatErrorChain, x as discoverReactSubprojects, y as buildJsonReport } from "./cli-logger-
|
|
2
|
+
import { A as isReactDoctorError, C as filterSourceFiles, D as groupBy, E as getDiffInfo, F as runInspect, I as toRelativePath, M as listWorkspacePackages, N as resolveScanTarget, O as highlighter, P as restoreLegacyThrow, S as filterDiagnosticsForSurface, T as formatReactDoctorError, _ as Score, a as DeadCode, b as buildJsonReportError, c as LintPartialFailures, d as OXLINT_NODE_REQUIREMENT, f as Progress, g as SKILL_NAME, h as SHARE_BASE_URL, i as Config, j as layerOtlp, k as isMonorepoRoot, l as Linter, m as Reporter, o as Files, p as Project, r as CANONICAL_GITHUB_URL, s as Git, t as cliLogger, u as NodeResolver, v as StagedFiles, w as formatErrorChain, x as discoverReactSubprojects, y as buildJsonReport } from "./cli-logger-pbFEieEc.js";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { execFileSync, execSync } from "node:child_process";
|
|
5
5
|
import path, { join } from "node:path";
|
|
@@ -6320,11 +6320,11 @@ const colorizeByScore = (text, score) => {
|
|
|
6320
6320
|
return highlighter.error(text);
|
|
6321
6321
|
};
|
|
6322
6322
|
//#endregion
|
|
6323
|
+
//#region src/cli/utils/constants.ts
|
|
6324
|
+
const STAGED_FILES_TEMP_DIR_PREFIX = "react-doctor-staged-";
|
|
6325
|
+
const INTERNAL_ERROR_JSON_FALLBACK = "{\"schemaVersion\":1,\"ok\":false,\"error\":{\"message\":\"Internal error\",\"name\":\"Error\",\"chain\":[]}}\n";
|
|
6326
|
+
//#endregion
|
|
6323
6327
|
//#region src/cli/utils/render-score-header.ts
|
|
6324
|
-
const SCORE_BAR_ANIMATION_FRAME_COUNT = 40;
|
|
6325
|
-
const SCORE_BAR_ANIMATION_FRAME_DELAY_MS = 50;
|
|
6326
|
-
const PERFECT_SCORE_RAINBOW_FRAME_COUNT = 16;
|
|
6327
|
-
const PERFECT_SCORE_RAINBOW_FRAME_DELAY_MS = 50;
|
|
6328
6328
|
const RAINBOW_HUE_SHIFT_PER_FRAME = 9;
|
|
6329
6329
|
const RAINBOW_GRADIENT_WIDTH = 80;
|
|
6330
6330
|
const RAINBOW_OKLCH_LIGHTNESS = .638;
|
|
@@ -6433,8 +6433,8 @@ const buildInitialScoreHeaderLine = ({ isPerfectScore, shouldAnimate, lineIndex,
|
|
|
6433
6433
|
};
|
|
6434
6434
|
const printAnimatedScore = (scoreFaceLine, barFaceLine, score, label, projectName) => Effect.gen(function* () {
|
|
6435
6435
|
const isPerfectScore = score === 100;
|
|
6436
|
-
for (let frame = 0; frame <=
|
|
6437
|
-
const progress = easeOutCubic(frame /
|
|
6436
|
+
for (let frame = 0; frame <= 40; frame += 1) {
|
|
6437
|
+
const progress = easeOutCubic(frame / 40);
|
|
6438
6438
|
const animatedScore = Math.round(score * progress);
|
|
6439
6439
|
if (isPerfectScore) {
|
|
6440
6440
|
yield* writeScoreHeaderLine(`${frame === 0 ? "" : "\x1B[4A"}\r${buildRainbowScoreHeaderFrame({
|
|
@@ -6444,16 +6444,16 @@ const printAnimatedScore = (scoreFaceLine, barFaceLine, score, label, projectNam
|
|
|
6444
6444
|
frame,
|
|
6445
6445
|
projectName
|
|
6446
6446
|
})}`);
|
|
6447
|
-
if (frame <
|
|
6447
|
+
if (frame < 40) yield* sleep(50);
|
|
6448
6448
|
continue;
|
|
6449
6449
|
}
|
|
6450
6450
|
const animatedScoreLine = buildScoreLine(animatedScore, score, label, projectName);
|
|
6451
6451
|
const animatedBarLine = buildScoreBar(animatedScore, score);
|
|
6452
6452
|
yield* writeScoreHeaderLine(`${frame === 0 ? "" : "\x1B[2A"}\r${buildScoreHeaderLine(scoreFaceLine, animatedScoreLine)}\n\r${buildScoreHeaderLine(barFaceLine, animatedBarLine)}\n`);
|
|
6453
|
-
if (frame <
|
|
6453
|
+
if (frame < 40) yield* sleep(50);
|
|
6454
6454
|
}
|
|
6455
6455
|
if (!isPerfectScore) return;
|
|
6456
|
-
for (let frame = 0; frame <
|
|
6456
|
+
for (let frame = 0; frame < 16; frame += 1) {
|
|
6457
6457
|
yield* writeScoreHeaderLine(`\x1b[4A\r${buildRainbowScoreHeaderFrame({
|
|
6458
6458
|
score,
|
|
6459
6459
|
displayScore: score,
|
|
@@ -6461,9 +6461,9 @@ const printAnimatedScore = (scoreFaceLine, barFaceLine, score, label, projectNam
|
|
|
6461
6461
|
frame,
|
|
6462
6462
|
projectName
|
|
6463
6463
|
})}`);
|
|
6464
|
-
yield* sleep(
|
|
6464
|
+
yield* sleep(50);
|
|
6465
6465
|
}
|
|
6466
|
-
yield* writeScoreHeaderLine(`\x1b[4A\r${buildFinalPerfectScoreHeaderFrame(score, label,
|
|
6466
|
+
yield* writeScoreHeaderLine(`\x1b[4A\r${buildFinalPerfectScoreHeaderFrame(score, label, 16, projectName)}\x1b[2A`);
|
|
6467
6467
|
});
|
|
6468
6468
|
const printScoreHeader = (scoreResult, projectName) => Effect.gen(function* () {
|
|
6469
6469
|
const isPerfectScore = scoreResult.score === 100;
|
|
@@ -6666,7 +6666,7 @@ const resolveOxlintNodeEffect = (isLintEnabled, isQuiet) => Effect.gen(function*
|
|
|
6666
6666
|
const resolveOxlintNode = (isLintEnabled, isQuiet) => Effect.runPromise(resolveOxlintNodeEffect(isLintEnabled, isQuiet).pipe(Effect.provide(NodeResolver.layerNode)));
|
|
6667
6667
|
//#endregion
|
|
6668
6668
|
//#region src/cli/utils/version.ts
|
|
6669
|
-
const VERSION = "0.2.
|
|
6669
|
+
const VERSION = "0.2.11";
|
|
6670
6670
|
//#endregion
|
|
6671
6671
|
//#region src/inspect.ts
|
|
6672
6672
|
const silentConsole = makeNoopConsole();
|
|
@@ -6854,10 +6854,6 @@ const finalizeAndRender = (input) => Effect.gen(function* () {
|
|
|
6854
6854
|
return buildResult();
|
|
6855
6855
|
});
|
|
6856
6856
|
//#endregion
|
|
6857
|
-
//#region src/cli/utils/constants.ts
|
|
6858
|
-
const STAGED_FILES_TEMP_DIR_PREFIX = "react-doctor-staged-";
|
|
6859
|
-
const INTERNAL_ERROR_JSON_FALLBACK = "{\"schemaVersion\":1,\"ok\":false,\"error\":{\"message\":\"Internal error\",\"name\":\"Error\",\"chain\":[]}}\n";
|
|
6860
|
-
//#endregion
|
|
6861
6857
|
//#region src/cli/utils/get-staged-files.ts
|
|
6862
6858
|
const stagedFilesLayer = StagedFiles.layerNode.pipe(Layer.provide(Git.layerNode));
|
|
6863
6859
|
const getStagedSourceFiles = async (directory) => {
|
|
@@ -7416,7 +7412,7 @@ const disableSetupPrompt = (projectRoot, storeOptions = {}) => {
|
|
|
7416
7412
|
return true;
|
|
7417
7413
|
};
|
|
7418
7414
|
const shouldPromptInstallSetup = (options) => {
|
|
7419
|
-
if (!options.hasScoredScan) return false;
|
|
7415
|
+
if (!(options.hasCompletedScan ?? options.hasScoredScan ?? false)) return false;
|
|
7420
7416
|
if (options.isJsonMode) return false;
|
|
7421
7417
|
if (options.isScoreOnly) return false;
|
|
7422
7418
|
if (options.isStaged) return false;
|
|
@@ -7426,13 +7422,13 @@ const shouldPromptInstallSetup = (options) => {
|
|
|
7426
7422
|
return !hasDoctorScript(options.projectRoot);
|
|
7427
7423
|
};
|
|
7428
7424
|
const resolveInstallSetupProjectRoot = (options) => {
|
|
7429
|
-
if (options.
|
|
7425
|
+
if (options.scanDirectories.length === 0) return findNearestPackageDirectory(options.scanRoot) ?? options.scanRoot;
|
|
7430
7426
|
const packageDirectories = /* @__PURE__ */ new Set();
|
|
7431
|
-
for (const scanDirectory of options.
|
|
7427
|
+
for (const scanDirectory of options.scanDirectories) {
|
|
7432
7428
|
const packageDirectory = findNearestPackageDirectory(scanDirectory, options.scanRoot) ?? findNearestPackageDirectory(scanDirectory) ?? scanDirectory;
|
|
7433
7429
|
packageDirectories.add(packageDirectory);
|
|
7434
7430
|
}
|
|
7435
|
-
if (packageDirectories.size !== 1) return
|
|
7431
|
+
if (packageDirectories.size !== 1) return findNearestPackageDirectory(options.scanRoot, options.scanRoot);
|
|
7436
7432
|
return [...packageDirectories][0] ?? null;
|
|
7437
7433
|
};
|
|
7438
7434
|
const defaultWait = (milliseconds) => new Promise((resolve) => {
|
|
@@ -7467,7 +7463,7 @@ const warnSetupPromptFailure = async (options, error) => {
|
|
|
7467
7463
|
return;
|
|
7468
7464
|
}
|
|
7469
7465
|
try {
|
|
7470
|
-
const { cliLogger } = await import("./cli-logger-
|
|
7466
|
+
const { cliLogger } = await import("./cli-logger-pbFEieEc.js").then((n) => n.n);
|
|
7471
7467
|
cliLogger.warn(message);
|
|
7472
7468
|
} catch {}
|
|
7473
7469
|
};
|
|
@@ -7483,7 +7479,7 @@ const promptInstallSetup = async (options) => {
|
|
|
7483
7479
|
writeLine("You can always run `npx react-doctor@latest install` to set it up later.");
|
|
7484
7480
|
return;
|
|
7485
7481
|
}
|
|
7486
|
-
const install = options.install ?? (await Promise.resolve().then(() =>
|
|
7482
|
+
const install = options.install ?? (await Promise.resolve().then(() => install_react_doctor_exports)).runInstallReactDoctor;
|
|
7487
7483
|
const previousExitCode = process.exitCode;
|
|
7488
7484
|
let setupExitCode;
|
|
7489
7485
|
try {
|
|
@@ -7502,7 +7498,7 @@ const promptInstallSetup = async (options) => {
|
|
|
7502
7498
|
}
|
|
7503
7499
|
};
|
|
7504
7500
|
const shouldShowAgentInstallHint = (options) => {
|
|
7505
|
-
if (!options.hasScoredScan) return false;
|
|
7501
|
+
if (!(options.hasCompletedScan ?? options.hasScoredScan ?? false)) return false;
|
|
7506
7502
|
if (options.isJsonMode) return false;
|
|
7507
7503
|
if (options.isScoreOnly) return false;
|
|
7508
7504
|
if (options.isStaged) return false;
|
|
@@ -7567,7 +7563,7 @@ const resolveDiffMode = async (diffInfo, effectiveDiff, shouldSkipPrompts, isQui
|
|
|
7567
7563
|
const { scanScope } = await prompts({
|
|
7568
7564
|
type: "select",
|
|
7569
7565
|
name: "scanScope",
|
|
7570
|
-
message: "
|
|
7566
|
+
message: "Choose what to scan",
|
|
7571
7567
|
choices: [{
|
|
7572
7568
|
title: "Full codebase",
|
|
7573
7569
|
value: "full"
|
|
@@ -7709,7 +7705,7 @@ const selectProjects = async (rootDirectory, projectFlag, skipPrompts) => {
|
|
|
7709
7705
|
}
|
|
7710
7706
|
if (packages.length === 0) return [rootDirectory];
|
|
7711
7707
|
if (packages.length === 1) {
|
|
7712
|
-
cliLogger.log(`${highlighter.success("✔")} Select projects
|
|
7708
|
+
cliLogger.log(`${highlighter.success("✔")} Select projects ${highlighter.dim("›")} ${packages[0].name}`);
|
|
7713
7709
|
return [packages[0].directory];
|
|
7714
7710
|
}
|
|
7715
7711
|
if (projectFlag) return resolveProjectFlag(projectFlag, packages);
|
|
@@ -7733,13 +7729,13 @@ const resolveProjectFlag = (projectFlag, workspacePackages) => {
|
|
|
7733
7729
|
return resolvedDirectories;
|
|
7734
7730
|
};
|
|
7735
7731
|
const printDiscoveredProjects = (packages) => {
|
|
7736
|
-
cliLogger.log(`${highlighter.success("✔")} Select projects
|
|
7732
|
+
cliLogger.log(`${highlighter.success("✔")} Select projects ${highlighter.dim("›")} ${packages.map((workspacePackage) => workspacePackage.name).join(", ")}`);
|
|
7737
7733
|
};
|
|
7738
7734
|
const promptProjectSelection = async (workspacePackages, rootDirectory) => {
|
|
7739
7735
|
const { selectedDirectories } = await prompts({
|
|
7740
7736
|
type: "multiselect",
|
|
7741
7737
|
name: "selectedDirectories",
|
|
7742
|
-
message: "Select projects
|
|
7738
|
+
message: "Select projects",
|
|
7743
7739
|
choices: workspacePackages.map((workspacePackage) => ({
|
|
7744
7740
|
title: workspacePackage.name,
|
|
7745
7741
|
description: path.relative(rootDirectory, workspacePackage.directory),
|
|
@@ -7996,13 +7992,13 @@ const inspectAction = async (directory, flags) => {
|
|
|
7996
7992
|
});
|
|
7997
7993
|
const setupProjectRoot = resolveInstallSetupProjectRoot({
|
|
7998
7994
|
scanRoot: resolvedDirectory,
|
|
7999
|
-
|
|
7995
|
+
scanDirectories: projectDirectories
|
|
8000
7996
|
});
|
|
8001
7997
|
if (setupProjectRoot !== null) {
|
|
8002
|
-
const
|
|
7998
|
+
const hasCompletedScan = completedScans.length > 0;
|
|
8003
7999
|
await promptInstallSetup({
|
|
8004
8000
|
projectRoot: setupProjectRoot,
|
|
8005
|
-
|
|
8001
|
+
hasCompletedScan,
|
|
8006
8002
|
issueCount: filterDiagnosticsForSurface(allDiagnostics, scanOptions.outputSurface ?? "cli", userConfig).length,
|
|
8007
8003
|
isJsonMode,
|
|
8008
8004
|
isScoreOnly,
|
|
@@ -8011,7 +8007,7 @@ const inspectAction = async (directory, flags) => {
|
|
|
8011
8007
|
});
|
|
8012
8008
|
if (shouldShowAgentInstallHint({
|
|
8013
8009
|
projectRoot: setupProjectRoot,
|
|
8014
|
-
|
|
8010
|
+
hasCompletedScan,
|
|
8015
8011
|
isJsonMode,
|
|
8016
8012
|
isScoreOnly,
|
|
8017
8013
|
isStaged: Boolean(flags.staged)
|
|
@@ -8643,8 +8639,12 @@ const installReactDoctorGitHook = (options) => {
|
|
|
8643
8639
|
return installDirectGitHook(options);
|
|
8644
8640
|
};
|
|
8645
8641
|
//#endregion
|
|
8646
|
-
//#region src/cli/utils/install-
|
|
8647
|
-
var
|
|
8642
|
+
//#region src/cli/utils/install-react-doctor.ts
|
|
8643
|
+
var install_react_doctor_exports = /* @__PURE__ */ __exportAll({ runInstallReactDoctor: () => runInstallReactDoctor });
|
|
8644
|
+
const SETUP_OPTION_GIT_HOOK = "git-hook";
|
|
8645
|
+
const SETUP_OPTION_AGENT_HOOKS = "agent-hooks";
|
|
8646
|
+
const SETUP_OPTION_WORKFLOW = "workflow";
|
|
8647
|
+
const SETUP_OPTION_SKIP = "skip";
|
|
8648
8648
|
const CONFIG_ONLY_GIT_HOOK_KINDS = new Set([
|
|
8649
8649
|
"ghooks",
|
|
8650
8650
|
"git-hooks-js",
|
|
@@ -8825,7 +8825,30 @@ const getSkillSourceDirectory = () => {
|
|
|
8825
8825
|
const distDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
8826
8826
|
return path.join(distDirectory, "skills", SKILL_NAME);
|
|
8827
8827
|
};
|
|
8828
|
-
const
|
|
8828
|
+
const canInstallNativeAgentHooks = (agents) => agents.some((agent) => agent === "claude-code" || agent === "cursor");
|
|
8829
|
+
const buildWorkflowContent = () => [
|
|
8830
|
+
"name: React Doctor",
|
|
8831
|
+
"",
|
|
8832
|
+
"on:",
|
|
8833
|
+
" pull_request:",
|
|
8834
|
+
" branches: [main]",
|
|
8835
|
+
"",
|
|
8836
|
+
"permissions:",
|
|
8837
|
+
" contents: read",
|
|
8838
|
+
" pull-requests: write",
|
|
8839
|
+
"",
|
|
8840
|
+
"jobs:",
|
|
8841
|
+
" react-doctor:",
|
|
8842
|
+
" runs-on: ubuntu-latest",
|
|
8843
|
+
" steps:",
|
|
8844
|
+
" - uses: actions/checkout@v4",
|
|
8845
|
+
" - uses: millionco/react-doctor@main",
|
|
8846
|
+
" with:",
|
|
8847
|
+
" github-token: ${{ secrets.GITHUB_TOKEN }}",
|
|
8848
|
+
" diff: main",
|
|
8849
|
+
""
|
|
8850
|
+
].join("\n");
|
|
8851
|
+
const runInstallReactDoctor = async (options = {}) => {
|
|
8829
8852
|
const requestedProjectRoot = options.projectRoot ?? process.cwd();
|
|
8830
8853
|
const projectRoot = findNearestPackageDirectory(requestedProjectRoot) ?? requestedProjectRoot;
|
|
8831
8854
|
const sourceDir = options.sourceDir ?? getSkillSourceDirectory();
|
|
@@ -8846,7 +8869,8 @@ const runInstallSkill = async (options = {}) => {
|
|
|
8846
8869
|
const gitHookTarget = options.gitHookPath === void 0 ? detectGitHookTarget(projectRoot) : options.gitHookPath === null ? null : buildManualGitHookTarget(options.gitHookPath, projectRoot);
|
|
8847
8870
|
const gitHookPath = gitHookTarget?.hookPath;
|
|
8848
8871
|
const promptOptions = options.onPromptCancel === void 0 ? {} : { onCancel: options.onPromptCancel };
|
|
8849
|
-
const
|
|
8872
|
+
const prompt = options.prompt ?? prompts;
|
|
8873
|
+
const selectedAgents = skipPrompts ? detectedAgents : (await prompt({
|
|
8850
8874
|
type: "multiselect",
|
|
8851
8875
|
name: "agents",
|
|
8852
8876
|
message: `Install the ${highlighter.info(`/react-doctor`)} skill for:`,
|
|
@@ -8859,13 +8883,48 @@ const runInstallSkill = async (options = {}) => {
|
|
|
8859
8883
|
min: 1
|
|
8860
8884
|
}, promptOptions)).agents ?? [];
|
|
8861
8885
|
if (selectedAgents.length === 0) return;
|
|
8862
|
-
const
|
|
8863
|
-
|
|
8864
|
-
|
|
8865
|
-
|
|
8866
|
-
|
|
8867
|
-
|
|
8868
|
-
|
|
8886
|
+
const workflowsDirectory = path.join(projectRoot, ".github", "workflows");
|
|
8887
|
+
const workflowTargetPath = path.join(workflowsDirectory, "react-doctor.yml");
|
|
8888
|
+
const hasExistingWorkflows = existsSync(workflowsDirectory);
|
|
8889
|
+
const canInstallWorkflow = !existsSync(workflowTargetPath);
|
|
8890
|
+
const setupActionChoices = [
|
|
8891
|
+
...gitHookPath === null || gitHookPath === void 0 ? [] : [{
|
|
8892
|
+
title: "Pre-commit hook",
|
|
8893
|
+
description: "Check staged changes before each commit",
|
|
8894
|
+
value: SETUP_OPTION_GIT_HOOK,
|
|
8895
|
+
selected: true
|
|
8896
|
+
}],
|
|
8897
|
+
...canInstallNativeAgentHooks(selectedAgents) ? [{
|
|
8898
|
+
title: "Agent hooks",
|
|
8899
|
+
description: "Ask Claude Code or Cursor to scan after code edits",
|
|
8900
|
+
value: SETUP_OPTION_AGENT_HOOKS,
|
|
8901
|
+
selected: Boolean(options.agentHooks)
|
|
8902
|
+
}] : [],
|
|
8903
|
+
...canInstallWorkflow ? [{
|
|
8904
|
+
title: "GitHub Actions workflow",
|
|
8905
|
+
description: "Scan pull requests in CI",
|
|
8906
|
+
value: SETUP_OPTION_WORKFLOW,
|
|
8907
|
+
selected: hasExistingWorkflows
|
|
8908
|
+
}] : []
|
|
8909
|
+
];
|
|
8910
|
+
const setupChoices = setupActionChoices.length === 0 ? [] : [{
|
|
8911
|
+
title: "Skip optional setup",
|
|
8912
|
+
description: "Install only the agent skill and package setup",
|
|
8913
|
+
value: SETUP_OPTION_SKIP,
|
|
8914
|
+
selected: false
|
|
8915
|
+
}, ...setupActionChoices];
|
|
8916
|
+
const selectedSetupOptions = skipPrompts || setupChoices.length === 0 ? [] : (await prompt({
|
|
8917
|
+
type: "multiselect",
|
|
8918
|
+
name: "setupOptions",
|
|
8919
|
+
message: "Select additional React Doctor setup:",
|
|
8920
|
+
choices: setupChoices,
|
|
8921
|
+
instructions: false
|
|
8922
|
+
}, promptOptions)).setupOptions ?? [];
|
|
8923
|
+
const selectedSetupActions = selectedSetupOptions.filter((setupOption) => setupOption !== SETUP_OPTION_SKIP);
|
|
8924
|
+
const didSkipOptionalSetup = selectedSetupActions.length === 0 && selectedSetupOptions.includes(SETUP_OPTION_SKIP);
|
|
8925
|
+
const shouldInstallGitHook = gitHookPath !== null && gitHookPath !== void 0 && (Boolean(options.yes) || !didSkipOptionalSetup && selectedSetupActions.includes(SETUP_OPTION_GIT_HOOK));
|
|
8926
|
+
const shouldInstallAgentHooks = Boolean(options.agentHooks) || !didSkipOptionalSetup && selectedSetupActions.includes(SETUP_OPTION_AGENT_HOOKS);
|
|
8927
|
+
const shouldInstallWorkflow = !skipPrompts && !didSkipOptionalSetup && canInstallWorkflow && selectedSetupActions.includes(SETUP_OPTION_WORKFLOW);
|
|
8869
8928
|
if (options.dryRun) {
|
|
8870
8929
|
cliLogger.log(`Dry run — would install ${SKILL_NAME} skill for:`);
|
|
8871
8930
|
for (const agent of selectedAgents) cliLogger.dim(` - ${getSkillAgentConfig(agent).displayName}`);
|
|
@@ -8874,6 +8933,7 @@ const runInstallSkill = async (options = {}) => {
|
|
|
8874
8933
|
cliLogger.dim(" Dev dependency: react-doctor");
|
|
8875
8934
|
if (shouldInstallGitHook) cliLogger.dim(` Git hook: ${gitHookPath}`);
|
|
8876
8935
|
if (shouldInstallAgentHooks) cliLogger.dim(" Agent hooks: Claude Code / Cursor when selected");
|
|
8936
|
+
if (shouldInstallWorkflow) cliLogger.dim(` GitHub Actions workflow: ${path.relative(projectRoot, workflowTargetPath)}`);
|
|
8877
8937
|
return;
|
|
8878
8938
|
}
|
|
8879
8939
|
const installSpinner = spinner(`Installing ${SKILL_NAME} skill...`).start();
|
|
@@ -8921,47 +8981,15 @@ const runInstallSkill = async (options = {}) => {
|
|
|
8921
8981
|
throw error;
|
|
8922
8982
|
}
|
|
8923
8983
|
}
|
|
8924
|
-
|
|
8925
|
-
|
|
8926
|
-
|
|
8927
|
-
|
|
8928
|
-
|
|
8929
|
-
|
|
8930
|
-
|
|
8931
|
-
|
|
8932
|
-
|
|
8933
|
-
}, promptOptions);
|
|
8934
|
-
if (shouldInstallWorkflow) {
|
|
8935
|
-
if (!hasExistingWorkflows) mkdirSync(workflowsDirectory, { recursive: true });
|
|
8936
|
-
const workflowSpinner = spinner("Adding GitHub Actions workflow...").start();
|
|
8937
|
-
try {
|
|
8938
|
-
writeFileSync(workflowTargetPath, [
|
|
8939
|
-
"name: React Doctor",
|
|
8940
|
-
"",
|
|
8941
|
-
"on:",
|
|
8942
|
-
" pull_request:",
|
|
8943
|
-
" branches: [main]",
|
|
8944
|
-
"",
|
|
8945
|
-
"permissions:",
|
|
8946
|
-
" contents: read",
|
|
8947
|
-
" pull-requests: write",
|
|
8948
|
-
"",
|
|
8949
|
-
"jobs:",
|
|
8950
|
-
" react-doctor:",
|
|
8951
|
-
" runs-on: ubuntu-latest",
|
|
8952
|
-
" steps:",
|
|
8953
|
-
" - uses: actions/checkout@v4",
|
|
8954
|
-
" - uses: millionco/react-doctor@main",
|
|
8955
|
-
" with:",
|
|
8956
|
-
" github-token: ${{ secrets.GITHUB_TOKEN }}",
|
|
8957
|
-
" diff: main",
|
|
8958
|
-
""
|
|
8959
|
-
].join("\n"));
|
|
8960
|
-
workflowSpinner.succeed(`GitHub Actions workflow added at ${path.relative(projectRoot, workflowTargetPath)}.`);
|
|
8961
|
-
} catch (error) {
|
|
8962
|
-
workflowSpinner.fail("Failed to add GitHub Actions workflow.");
|
|
8963
|
-
throw error;
|
|
8964
|
-
}
|
|
8984
|
+
if (shouldInstallWorkflow) {
|
|
8985
|
+
if (!hasExistingWorkflows) mkdirSync(workflowsDirectory, { recursive: true });
|
|
8986
|
+
const workflowSpinner = spinner("Adding GitHub Actions workflow...").start();
|
|
8987
|
+
try {
|
|
8988
|
+
writeFileSync(workflowTargetPath, buildWorkflowContent());
|
|
8989
|
+
workflowSpinner.succeed(`GitHub Actions workflow added at ${path.relative(projectRoot, workflowTargetPath)}.`);
|
|
8990
|
+
} catch (error) {
|
|
8991
|
+
workflowSpinner.fail("Failed to add GitHub Actions workflow.");
|
|
8992
|
+
throw error;
|
|
8965
8993
|
}
|
|
8966
8994
|
}
|
|
8967
8995
|
};
|
|
@@ -8971,7 +8999,7 @@ const installAction = async (options, command) => {
|
|
|
8971
8999
|
Effect.runSync(printBrandedHeader);
|
|
8972
9000
|
try {
|
|
8973
9001
|
const parentOptions = command?.parent?.opts?.();
|
|
8974
|
-
await
|
|
9002
|
+
await runInstallReactDoctor({
|
|
8975
9003
|
yes: options.yes ?? parentOptions?.yes,
|
|
8976
9004
|
dryRun: options.dryRun,
|
|
8977
9005
|
agentHooks: options.agentHooks,
|
package/dist/index.d.ts
CHANGED
|
@@ -232,7 +232,9 @@ interface ReactDoctorConfig {
|
|
|
232
232
|
* `categories` field, but keyed by React Doctor's display
|
|
233
233
|
* categories (`"Server"`, `"React Native"`, `"Architecture"`,
|
|
234
234
|
* `"Bundle Size"`, `"State & Effects"`, `"Security"`,
|
|
235
|
-
* `"Accessibility"`, `"Performance"`, `"Correctness"`,
|
|
235
|
+
* `"Accessibility"`, `"Performance"`, `"Correctness"`,
|
|
236
|
+
* `"Next.js"`, `"Preact"`, `"TanStack Query"`,
|
|
237
|
+
* `"TanStack Start"`, …).
|
|
236
238
|
*
|
|
237
239
|
* ```json
|
|
238
240
|
* { "categories": { "React Native": "warn", "Server": "off" } }
|
|
@@ -296,7 +298,7 @@ interface Diagnostic {
|
|
|
296
298
|
}
|
|
297
299
|
//#endregion
|
|
298
300
|
//#region src/types/project-info.d.ts
|
|
299
|
-
type Framework = "nextjs" | "vite" | "cra" | "remix" | "gatsby" | "expo" | "react-native" | "tanstack-start" | "unknown";
|
|
301
|
+
type Framework = "nextjs" | "vite" | "cra" | "remix" | "gatsby" | "expo" | "react-native" | "tanstack-start" | "preact" | "unknown";
|
|
300
302
|
interface ProjectInfo {
|
|
301
303
|
rootDirectory: string;
|
|
302
304
|
projectName: string;
|
|
@@ -307,6 +309,16 @@ interface ProjectInfo {
|
|
|
307
309
|
hasTypeScript: boolean;
|
|
308
310
|
hasReactCompiler: boolean;
|
|
309
311
|
hasTanStackQuery: boolean;
|
|
312
|
+
/**
|
|
313
|
+
* `true` when `preact` is declared anywhere in the project's
|
|
314
|
+
* dependency manifest. Drives the `preact` capability in
|
|
315
|
+
* `buildCapabilities`, which gates every `preact-*` rule. Modeled
|
|
316
|
+
* on `hasTanStackQuery` rather than the `framework` field because
|
|
317
|
+
* the dominant Preact setup today is Preact-on-Vite — those
|
|
318
|
+
* projects classify as `framework: "vite"` for build-tool reasons
|
|
319
|
+
* but still need Preact-specific rules to fire.
|
|
320
|
+
*/
|
|
321
|
+
hasPreact: boolean;
|
|
310
322
|
/**
|
|
311
323
|
* `true` when the project (or any of its workspace packages) declares
|
|
312
324
|
* React Native or Expo as a dependency. Enables the `react-native`
|
|
@@ -321,6 +333,13 @@ interface ProjectInfo {
|
|
|
321
333
|
* — no `rn-*` rules load for the project at all.
|
|
322
334
|
*/
|
|
323
335
|
hasReactNativeWorkspace: boolean;
|
|
336
|
+
/**
|
|
337
|
+
* `true` when the project (or any of its workspace packages) declares
|
|
338
|
+
* `react-native-reanimated`. Lets diagnostics surface reanimated's
|
|
339
|
+
* Compiler-compatible `.get()` / `.set()` accessors only where they
|
|
340
|
+
* apply, instead of on every React Native project.
|
|
341
|
+
*/
|
|
342
|
+
hasReanimated: boolean;
|
|
324
343
|
sourceFileCount: number;
|
|
325
344
|
}
|
|
326
345
|
//#endregion
|