Skip to content

Commit 7afd95f

Browse files
Fix root directory resolution logic (#13472)
1 parent fc7239f commit 7afd95f

File tree

5 files changed

+86
-21
lines changed

5 files changed

+86
-21
lines changed

.changeset/bright-experts-grab.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@react-router/dev": patch
3+
---
4+
5+
Support project root directories without a `package.json` if it exists in a parent directory

.changeset/cold-seals-count.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@react-router/dev": patch
3+
---
4+
5+
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

.changeset/forty-ants-teach.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@react-router/dev": patch
3+
---
4+
5+
Ensure consistent project root directory resolution logic in CLI commands

packages/react-router-dev/cli/commands.ts

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@ import * as Typegen from "../typegen";
1717
import { preloadVite, getVite } from "../vite/vite";
1818

1919
export async function routes(
20-
reactRouterRoot?: string,
20+
rootDirectory?: string,
2121
flags: {
2222
config?: string;
2323
json?: boolean;
2424
} = {}
2525
): Promise<void> {
26-
let rootDirectory = reactRouterRoot ?? process.cwd();
26+
rootDirectory = resolveRootDirectory(rootDirectory, flags);
2727
let configResult = await loadConfig({ rootDirectory });
2828

2929
if (!configResult.ok) {
@@ -39,9 +39,7 @@ export async function build(
3939
root?: string,
4040
options: ViteBuildOptions = {}
4141
): Promise<void> {
42-
if (!root) {
43-
root = process.env.REACT_ROUTER_ROOT || process.cwd();
44-
}
42+
root = resolveRootDirectory(root, options);
4543

4644
let { build } = await import("../vite/build");
4745
if (options.profile) {
@@ -54,12 +52,14 @@ export async function build(
5452
}
5553
}
5654

57-
export async function dev(root: string, options: ViteDevOptions = {}) {
55+
export async function dev(root?: string, options: ViteDevOptions = {}) {
5856
let { dev } = await import("../vite/dev");
5957
if (options.profile) {
6058
await profiler.start();
6159
}
6260
exitHook(() => profiler.stop(console.info));
61+
62+
root = resolveRootDirectory(root, options);
6363
await dev(root, options);
6464

6565
// keep `react-router dev` alive by waiting indefinitely
@@ -77,20 +77,20 @@ let conjunctionListFormat = new Intl.ListFormat("en", {
7777

7878
export async function generateEntry(
7979
entry?: string,
80-
reactRouterRoot?: string,
80+
rootDirectory?: string,
8181
flags: {
8282
typescript?: boolean;
8383
config?: string;
8484
} = {}
8585
) {
8686
// if no entry passed, attempt to create both
8787
if (!entry) {
88-
await generateEntry("entry.client", reactRouterRoot, flags);
89-
await generateEntry("entry.server", reactRouterRoot, flags);
88+
await generateEntry("entry.client", rootDirectory, flags);
89+
await generateEntry("entry.server", rootDirectory, flags);
9090
return;
9191
}
9292

93-
let rootDirectory = reactRouterRoot ?? process.cwd();
93+
rootDirectory = resolveRootDirectory(rootDirectory, flags);
9494
let configResult = await loadConfig({ rootDirectory });
9595

9696
if (!configResult.ok) {
@@ -162,6 +162,17 @@ export async function generateEntry(
162162
);
163163
}
164164

165+
function resolveRootDirectory(root?: string, flags?: { config?: string }) {
166+
if (root) {
167+
return path.resolve(root);
168+
}
169+
170+
return (
171+
process.env.REACT_ROUTER_ROOT ||
172+
(flags?.config ? path.dirname(path.resolve(flags.config)) : process.cwd())
173+
);
174+
}
175+
165176
async function checkForEntry(
166177
rootDirectory: string,
167178
appDirectory: string,
@@ -198,8 +209,14 @@ async function createClientEntry(
198209
return contents;
199210
}
200211

201-
export async function typegen(root: string, flags: { watch: boolean }) {
202-
root ??= process.cwd();
212+
export async function typegen(
213+
root: string,
214+
flags: {
215+
watch?: boolean;
216+
config?: string;
217+
}
218+
) {
219+
root = resolveRootDirectory(root, flags);
203220

204221
if (flags.watch) {
205222
await preloadVite();

packages/react-router-dev/config/config.ts

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -754,7 +754,20 @@ export async function resolveEntryFiles({
754754
let entryServerFile: string;
755755
let entryClientFile = userEntryClientFile || "entry.client.tsx";
756756

757-
let pkgJson = await PackageJson.load(rootDirectory);
757+
let packageJsonPath = findEntry(rootDirectory, "package", {
758+
extensions: [".json"],
759+
absolute: true,
760+
walkParents: true,
761+
});
762+
763+
if (!packageJsonPath) {
764+
throw new Error(
765+
`Could not find package.json in ${rootDirectory} or any of its parent directories`
766+
);
767+
}
768+
769+
let packageJsonDirectory = path.dirname(packageJsonPath);
770+
let pkgJson = await PackageJson.load(packageJsonDirectory);
758771
let deps = pkgJson.content.dependencies ?? {};
759772

760773
if (userEntryServerFile) {
@@ -783,7 +796,7 @@ export async function resolveEntryFiles({
783796
let packageManager = detectPackageManager() ?? "npm";
784797

785798
execSync(`${packageManager} install`, {
786-
cwd: rootDirectory,
799+
cwd: packageJsonDirectory,
787800
stdio: "inherit",
788801
});
789802
}
@@ -807,14 +820,34 @@ const entryExts = [".js", ".jsx", ".ts", ".tsx"];
807820
function findEntry(
808821
dir: string,
809822
basename: string,
810-
options?: { absolute?: boolean }
823+
options?: {
824+
absolute?: boolean;
825+
extensions?: string[];
826+
walkParents?: boolean;
827+
}
811828
): string | undefined {
812-
for (let ext of entryExts) {
813-
let file = path.resolve(dir, basename + ext);
814-
if (fs.existsSync(file)) {
815-
return options?.absolute ?? false ? file : path.relative(dir, file);
829+
let currentDir = path.resolve(dir);
830+
let { root } = path.parse(currentDir);
831+
832+
while (true) {
833+
for (let ext of options?.extensions ?? entryExts) {
834+
let file = path.resolve(currentDir, basename + ext);
835+
if (fs.existsSync(file)) {
836+
return options?.absolute ?? false ? file : path.relative(dir, file);
837+
}
838+
}
839+
840+
if (!options?.walkParents) {
841+
return undefined;
816842
}
817-
}
818843

819-
return undefined;
844+
let parentDir = path.dirname(currentDir);
845+
// Break out when we've reached the root directory or we're about to get
846+
// stuck in a loop where `path.dirname` keeps returning "/"
847+
if (currentDir === root || parentDir === currentDir) {
848+
return undefined;
849+
}
850+
851+
currentDir = parentDir;
852+
}
820853
}

0 commit comments

Comments
 (0)