Skip to content

Commit 1d760f6

Browse files
committed
Fix root directory resolution logic
1 parent d1aaa2d commit 1d760f6

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
@@ -688,7 +688,20 @@ export async function resolveEntryFiles({
688688
let entryServerFile: string;
689689
let entryClientFile = userEntryClientFile || "entry.client.tsx";
690690

691-
let pkgJson = await PackageJson.load(rootDirectory);
691+
let packageJsonPath = findEntry(rootDirectory, "package", {
692+
extensions: [".json"],
693+
absolute: true,
694+
walkParents: true,
695+
});
696+
697+
if (!packageJsonPath) {
698+
throw new Error(
699+
`Could not find package.json in ${rootDirectory} or any of its parent directories`
700+
);
701+
}
702+
703+
let packageJsonDirectory = path.dirname(packageJsonPath);
704+
let pkgJson = await PackageJson.load(packageJsonDirectory);
692705
let deps = pkgJson.content.dependencies ?? {};
693706

694707
if (userEntryServerFile) {
@@ -717,7 +730,7 @@ export async function resolveEntryFiles({
717730
let packageManager = detectPackageManager() ?? "npm";
718731

719732
execSync(`${packageManager} install`, {
720-
cwd: rootDirectory,
733+
cwd: packageJsonDirectory,
721734
stdio: "inherit",
722735
});
723736
}
@@ -741,14 +754,34 @@ const entryExts = [".js", ".jsx", ".ts", ".tsx"];
741754
function findEntry(
742755
dir: string,
743756
basename: string,
744-
options?: { absolute?: boolean }
757+
options?: {
758+
absolute?: boolean;
759+
extensions?: string[];
760+
walkParents?: boolean;
761+
}
745762
): string | undefined {
746-
for (let ext of entryExts) {
747-
let file = path.resolve(dir, basename + ext);
748-
if (fs.existsSync(file)) {
749-
return options?.absolute ?? false ? file : path.relative(dir, file);
763+
let currentDir = path.resolve(dir);
764+
let { root } = path.parse(currentDir);
765+
766+
while (true) {
767+
for (let ext of options?.extensions ?? entryExts) {
768+
let file = path.resolve(currentDir, basename + ext);
769+
if (fs.existsSync(file)) {
770+
return options?.absolute ?? false ? file : path.relative(dir, file);
771+
}
772+
}
773+
774+
if (!options?.walkParents) {
775+
return undefined;
750776
}
751-
}
752777

753-
return undefined;
778+
let parentDir = path.dirname(currentDir);
779+
// Break out when we've reached the root directory or we're about to get
780+
// stuck in a loop where `path.dirname` keeps returning "/"
781+
if (currentDir === root || parentDir === currentDir) {
782+
return undefined;
783+
}
784+
785+
currentDir = parentDir;
786+
}
754787
}

0 commit comments

Comments
 (0)