diff --git a/.changeset/bright-experts-grab.md b/.changeset/bright-experts-grab.md new file mode 100644 index 0000000000..c1cfbf2a42 --- /dev/null +++ b/.changeset/bright-experts-grab.md @@ -0,0 +1,5 @@ +--- +"@react-router/dev": patch +--- + +Support project root directories without a `package.json` if it exists in a parent directory diff --git a/.changeset/cold-seals-count.md b/.changeset/cold-seals-count.md new file mode 100644 index 0000000000..90e188ca03 --- /dev/null +++ b/.changeset/cold-seals-count.md @@ -0,0 +1,5 @@ +--- +"@react-router/dev": patch +--- + +When providing a custom Vite config path via the CLI `--config`/`-c` flag, default the project root directory to the directory containing the Vite config when not explicitly provided diff --git a/.changeset/forty-ants-teach.md b/.changeset/forty-ants-teach.md new file mode 100644 index 0000000000..4b5476cb2a --- /dev/null +++ b/.changeset/forty-ants-teach.md @@ -0,0 +1,5 @@ +--- +"@react-router/dev": patch +--- + +Ensure consistent project root directory resolution logic in CLI commands diff --git a/packages/react-router-dev/cli/commands.ts b/packages/react-router-dev/cli/commands.ts index 62005266eb..071c8068a3 100644 --- a/packages/react-router-dev/cli/commands.ts +++ b/packages/react-router-dev/cli/commands.ts @@ -17,13 +17,13 @@ import * as Typegen from "../typegen"; import { preloadVite, getVite } from "../vite/vite"; export async function routes( - reactRouterRoot?: string, + rootDirectory?: string, flags: { config?: string; json?: boolean; } = {} ): Promise { - let rootDirectory = reactRouterRoot ?? process.cwd(); + rootDirectory = resolveRootDirectory(rootDirectory, flags); let configResult = await loadConfig({ rootDirectory }); if (!configResult.ok) { @@ -39,9 +39,7 @@ export async function build( root?: string, options: ViteBuildOptions = {} ): Promise { - if (!root) { - root = process.env.REACT_ROUTER_ROOT || process.cwd(); - } + root = resolveRootDirectory(root, options); let { build } = await import("../vite/build"); if (options.profile) { @@ -54,12 +52,14 @@ export async function build( } } -export async function dev(root: string, options: ViteDevOptions = {}) { +export async function dev(root?: string, options: ViteDevOptions = {}) { let { dev } = await import("../vite/dev"); if (options.profile) { await profiler.start(); } exitHook(() => profiler.stop(console.info)); + + root = resolveRootDirectory(root, options); await dev(root, options); // keep `react-router dev` alive by waiting indefinitely @@ -77,7 +77,7 @@ let conjunctionListFormat = new Intl.ListFormat("en", { export async function generateEntry( entry?: string, - reactRouterRoot?: string, + rootDirectory?: string, flags: { typescript?: boolean; config?: string; @@ -85,12 +85,12 @@ export async function generateEntry( ) { // if no entry passed, attempt to create both if (!entry) { - await generateEntry("entry.client", reactRouterRoot, flags); - await generateEntry("entry.server", reactRouterRoot, flags); + await generateEntry("entry.client", rootDirectory, flags); + await generateEntry("entry.server", rootDirectory, flags); return; } - let rootDirectory = reactRouterRoot ?? process.cwd(); + rootDirectory = resolveRootDirectory(rootDirectory, flags); let configResult = await loadConfig({ rootDirectory }); if (!configResult.ok) { @@ -162,6 +162,17 @@ export async function generateEntry( ); } +function resolveRootDirectory(root?: string, flags?: { config?: string }) { + if (root) { + return path.resolve(root); + } + + return ( + process.env.REACT_ROUTER_ROOT || + (flags?.config ? path.dirname(path.resolve(flags.config)) : process.cwd()) + ); +} + async function checkForEntry( rootDirectory: string, appDirectory: string, @@ -198,8 +209,14 @@ async function createClientEntry( return contents; } -export async function typegen(root: string, flags: { watch: boolean }) { - root ??= process.cwd(); +export async function typegen( + root: string, + flags: { + watch?: boolean; + config?: string; + } +) { + root = resolveRootDirectory(root, flags); if (flags.watch) { await preloadVite(); diff --git a/packages/react-router-dev/config/config.ts b/packages/react-router-dev/config/config.ts index 999e19eec3..6eaf7dabe8 100644 --- a/packages/react-router-dev/config/config.ts +++ b/packages/react-router-dev/config/config.ts @@ -688,7 +688,20 @@ export async function resolveEntryFiles({ let entryServerFile: string; let entryClientFile = userEntryClientFile || "entry.client.tsx"; - let pkgJson = await PackageJson.load(rootDirectory); + let packageJsonPath = findEntry(rootDirectory, "package", { + extensions: [".json"], + absolute: true, + walkParents: true, + }); + + if (!packageJsonPath) { + throw new Error( + `Could not find package.json in ${rootDirectory} or any of its parent directories` + ); + } + + let packageJsonDirectory = path.dirname(packageJsonPath); + let pkgJson = await PackageJson.load(packageJsonDirectory); let deps = pkgJson.content.dependencies ?? {}; if (userEntryServerFile) { @@ -717,7 +730,7 @@ export async function resolveEntryFiles({ let packageManager = detectPackageManager() ?? "npm"; execSync(`${packageManager} install`, { - cwd: rootDirectory, + cwd: packageJsonDirectory, stdio: "inherit", }); } @@ -741,14 +754,34 @@ const entryExts = [".js", ".jsx", ".ts", ".tsx"]; function findEntry( dir: string, basename: string, - options?: { absolute?: boolean } + options?: { + absolute?: boolean; + extensions?: string[]; + walkParents?: boolean; + } ): string | undefined { - for (let ext of entryExts) { - let file = path.resolve(dir, basename + ext); - if (fs.existsSync(file)) { - return options?.absolute ?? false ? file : path.relative(dir, file); + let currentDir = path.resolve(dir); + let { root } = path.parse(currentDir); + + while (true) { + for (let ext of options?.extensions ?? entryExts) { + let file = path.resolve(currentDir, basename + ext); + if (fs.existsSync(file)) { + return options?.absolute ?? false ? file : path.relative(dir, file); + } + } + + if (!options?.walkParents) { + return undefined; } - } - return undefined; + let parentDir = path.dirname(currentDir); + // Break out when we've reached the root directory or we're about to get + // stuck in a loop where `path.dirname` keeps returning "/" + if (currentDir === root || parentDir === currentDir) { + return undefined; + } + + currentDir = parentDir; + } }