diff --git a/packages/react-doctor/src/utils/load-config.ts b/packages/react-doctor/src/utils/load-config.ts index e817376..2402300 100644 --- a/packages/react-doctor/src/utils/load-config.ts +++ b/packages/react-doctor/src/utils/load-config.ts @@ -1,14 +1,15 @@ import fs from "node:fs"; import path from "node:path"; import type { ReactDoctorConfig } from "../types.js"; +import { findMonorepoRoot } from "./find-monorepo-root.js"; import { isFile } from "./is-file.js"; import { isPlainObject } from "./is-plain-object.js"; const CONFIG_FILENAME = "react-doctor.config.json"; const PACKAGE_JSON_CONFIG_KEY = "reactDoctor"; -export const loadConfig = (rootDirectory: string): ReactDoctorConfig | null => { - const configFilePath = path.join(rootDirectory, CONFIG_FILENAME); +const loadDirectoryConfig = (directory: string): ReactDoctorConfig | null => { + const configFilePath = path.join(directory, CONFIG_FILENAME); if (isFile(configFilePath)) { try { @@ -25,7 +26,7 @@ export const loadConfig = (rootDirectory: string): ReactDoctorConfig | null => { } } - const packageJsonPath = path.join(rootDirectory, "package.json"); + const packageJsonPath = path.join(directory, "package.json"); if (isFile(packageJsonPath)) { try { const fileContent = fs.readFileSync(packageJsonPath, "utf-8"); @@ -41,3 +42,17 @@ export const loadConfig = (rootDirectory: string): ReactDoctorConfig | null => { return null; }; + +export const loadConfig = (rootDirectory: string): ReactDoctorConfig | null => { + const localConfig = loadDirectoryConfig(rootDirectory); + if (localConfig) { + return localConfig; + } + + const monorepoRoot = findMonorepoRoot(rootDirectory); + if (!monorepoRoot || monorepoRoot === rootDirectory) { + return null; + } + + return loadDirectoryConfig(monorepoRoot); +}; diff --git a/packages/react-doctor/tests/load-config.test.ts b/packages/react-doctor/tests/load-config.test.ts index f16cec7..bd8fc83 100644 --- a/packages/react-doctor/tests/load-config.test.ts +++ b/packages/react-doctor/tests/load-config.test.ts @@ -93,6 +93,56 @@ describe("loadConfig", () => { }); }); + describe("monorepo root fallback", () => { + it("loads monorepo root config when workspace package has no local config", () => { + const monorepoRoot = path.join(tempRootDirectory, "monorepo-root-fallback"); + const workspaceDirectory = path.join(monorepoRoot, "packages", "app"); + + fs.mkdirSync(workspaceDirectory, { recursive: true }); + fs.writeFileSync( + path.join(monorepoRoot, "package.json"), + JSON.stringify({ name: "workspace-root", workspaces: ["packages/*"] }), + ); + fs.writeFileSync( + path.join(monorepoRoot, "react-doctor.config.json"), + JSON.stringify({ ignore: { rules: ["jsx-a11y/alt-text"] } }), + ); + fs.writeFileSync( + path.join(workspaceDirectory, "package.json"), + JSON.stringify({ name: "app" }), + ); + + const config = loadConfig(workspaceDirectory); + expect(config).toEqual({ ignore: { rules: ["jsx-a11y/alt-text"] } }); + }); + + it("prefers workspace-local config over monorepo root fallback", () => { + const monorepoRoot = path.join(tempRootDirectory, "monorepo-local-precedence"); + const workspaceDirectory = path.join(monorepoRoot, "packages", "app"); + + fs.mkdirSync(workspaceDirectory, { recursive: true }); + fs.writeFileSync( + path.join(monorepoRoot, "package.json"), + JSON.stringify({ name: "workspace-root", workspaces: ["packages/*"] }), + ); + fs.writeFileSync( + path.join(monorepoRoot, "react-doctor.config.json"), + JSON.stringify({ ignore: { rules: ["from-root"] } }), + ); + fs.writeFileSync( + path.join(workspaceDirectory, "package.json"), + JSON.stringify({ name: "app" }), + ); + fs.writeFileSync( + path.join(workspaceDirectory, "react-doctor.config.json"), + JSON.stringify({ ignore: { rules: ["from-workspace"] } }), + ); + + const config = loadConfig(workspaceDirectory); + expect(config).toEqual({ ignore: { rules: ["from-workspace"] } }); + }); + }); + describe("no config", () => { let emptyDirectory: string;