Skip to content

Commit 24fb2f7

Browse files
committed
fix(cli): traverse parent directories to find vite.config.ts in vp pack
1 parent 4f22251 commit 24fb2f7

3 files changed

Lines changed: 182 additions & 2 deletions

File tree

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import fs from 'node:fs';
2+
import path from 'node:path';
3+
4+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
5+
import { mkdtempSync } from 'node:fs';
6+
import { tmpdir } from 'node:os';
7+
8+
import { findViteConfigUp } from '../resolve-vite-config';
9+
10+
describe('findViteConfigUp', () => {
11+
let tempDir: string;
12+
13+
beforeEach(() => {
14+
tempDir = mkdtempSync(path.join(tmpdir(), 'vite-config-test-'));
15+
});
16+
17+
afterEach(() => {
18+
fs.rmSync(tempDir, { recursive: true, force: true });
19+
});
20+
21+
it('should find config in the start directory', () => {
22+
fs.writeFileSync(path.join(tempDir, 'vite.config.ts'), '');
23+
const result = findViteConfigUp(tempDir, tempDir);
24+
expect(result).toBe(path.join(tempDir, 'vite.config.ts'));
25+
});
26+
27+
it('should find config in a parent directory', () => {
28+
const subDir = path.join(tempDir, 'packages', 'my-lib');
29+
fs.mkdirSync(subDir, { recursive: true });
30+
fs.writeFileSync(path.join(tempDir, 'vite.config.ts'), '');
31+
32+
const result = findViteConfigUp(subDir, tempDir);
33+
expect(result).toBe(path.join(tempDir, 'vite.config.ts'));
34+
});
35+
36+
it('should find config in an intermediate directory', () => {
37+
const subDir = path.join(tempDir, 'packages', 'my-lib', 'src');
38+
fs.mkdirSync(subDir, { recursive: true });
39+
fs.writeFileSync(path.join(tempDir, 'packages', 'vite.config.ts'), '');
40+
41+
const result = findViteConfigUp(subDir, tempDir);
42+
expect(result).toBe(path.join(tempDir, 'packages', 'vite.config.ts'));
43+
});
44+
45+
it('should return undefined when no config exists', () => {
46+
const subDir = path.join(tempDir, 'packages', 'my-lib');
47+
fs.mkdirSync(subDir, { recursive: true });
48+
49+
const result = findViteConfigUp(subDir, tempDir);
50+
expect(result).toBeUndefined();
51+
});
52+
53+
it('should not traverse beyond stopDir', () => {
54+
const parentConfig = path.join(tempDir, 'vite.config.ts');
55+
fs.writeFileSync(parentConfig, '');
56+
const stopDir = path.join(tempDir, 'packages');
57+
const subDir = path.join(stopDir, 'my-lib');
58+
fs.mkdirSync(subDir, { recursive: true });
59+
60+
const result = findViteConfigUp(subDir, stopDir);
61+
// Should not find the config in tempDir because stopDir is packages/
62+
expect(result).toBeUndefined();
63+
});
64+
65+
it('should prefer the closest config file', () => {
66+
const subDir = path.join(tempDir, 'packages', 'my-lib');
67+
fs.mkdirSync(subDir, { recursive: true });
68+
fs.writeFileSync(path.join(tempDir, 'vite.config.ts'), '');
69+
fs.writeFileSync(path.join(tempDir, 'packages', 'vite.config.ts'), '');
70+
71+
const result = findViteConfigUp(subDir, tempDir);
72+
expect(result).toBe(path.join(tempDir, 'packages', 'vite.config.ts'));
73+
});
74+
75+
it('should find .js config files', () => {
76+
const subDir = path.join(tempDir, 'packages', 'my-lib');
77+
fs.mkdirSync(subDir, { recursive: true });
78+
fs.writeFileSync(path.join(tempDir, 'vite.config.js'), '');
79+
80+
const result = findViteConfigUp(subDir, tempDir);
81+
expect(result).toBe(path.join(tempDir, 'vite.config.js'));
82+
});
83+
84+
it('should find .mts config files', () => {
85+
const subDir = path.join(tempDir, 'packages', 'my-lib');
86+
fs.mkdirSync(subDir, { recursive: true });
87+
fs.writeFileSync(path.join(tempDir, 'vite.config.mts'), '');
88+
89+
const result = findViteConfigUp(subDir, tempDir);
90+
expect(result).toBe(path.join(tempDir, 'vite.config.mts'));
91+
});
92+
});

packages/cli/src/pack-bin.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ cli
128128
}
129129

130130
async function runBuild() {
131-
const viteConfig = await resolveViteConfig(process.cwd());
131+
const viteConfig = await resolveViteConfig(process.cwd(), { traverseUp: true });
132132

133133
const configFiles: string[] = [];
134134
if (viteConfig.configFile) {

packages/cli/src/resolve-vite-config.ts

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,96 @@
1+
import fs from 'node:fs';
2+
import path from 'node:path';
3+
4+
const VITE_CONFIG_FILES = [
5+
'vite.config.js',
6+
'vite.config.mjs',
7+
'vite.config.ts',
8+
'vite.config.cjs',
9+
'vite.config.mts',
10+
'vite.config.cts',
11+
];
12+
13+
/**
14+
* Find a vite config file by walking up from `startDir` to `stopDir`.
15+
* Returns the absolute path of the first config file found, or undefined.
16+
*/
17+
export function findViteConfigUp(startDir: string, stopDir: string): string | undefined {
18+
let dir = path.resolve(startDir);
19+
const stop = path.resolve(stopDir);
20+
21+
while (true) {
22+
for (const filename of VITE_CONFIG_FILES) {
23+
const filePath = path.join(dir, filename);
24+
if (fs.existsSync(filePath)) {
25+
return filePath;
26+
}
27+
}
28+
const parent = path.dirname(dir);
29+
if (parent === dir || !dir.startsWith(stop)) {
30+
break;
31+
}
32+
dir = parent;
33+
}
34+
return undefined;
35+
}
36+
37+
function hasViteConfig(dir: string): boolean {
38+
return VITE_CONFIG_FILES.some((f) => fs.existsSync(path.join(dir, f)));
39+
}
40+
41+
/**
42+
* Find the workspace root by walking up from `startDir` looking for
43+
* monorepo indicators (pnpm-workspace.yaml, workspaces in package.json, lerna.json).
44+
*/
45+
function findWorkspaceRoot(startDir: string): string | undefined {
46+
let dir = path.resolve(startDir);
47+
while (true) {
48+
if (fs.existsSync(path.join(dir, 'pnpm-workspace.yaml'))) {
49+
return dir;
50+
}
51+
const pkgPath = path.join(dir, 'package.json');
52+
if (fs.existsSync(pkgPath)) {
53+
try {
54+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
55+
if (pkg.workspaces) {
56+
return dir;
57+
}
58+
} catch {
59+
// ignore
60+
}
61+
}
62+
if (fs.existsSync(path.join(dir, 'lerna.json'))) {
63+
return dir;
64+
}
65+
const parent = path.dirname(dir);
66+
if (parent === dir) {
67+
break;
68+
}
69+
dir = parent;
70+
}
71+
return undefined;
72+
}
73+
74+
export interface ResolveViteConfigOptions {
75+
traverseUp?: boolean;
76+
}
77+
178
/**
279
* Resolve vite.config.ts and return the config object.
380
*/
4-
export async function resolveViteConfig(cwd: string) {
81+
export async function resolveViteConfig(cwd: string, options?: ResolveViteConfigOptions) {
582
const { resolveConfig } = await import('./index.js');
83+
84+
if (options?.traverseUp && !hasViteConfig(cwd)) {
85+
const workspaceRoot = findWorkspaceRoot(cwd);
86+
if (workspaceRoot) {
87+
const configFile = findViteConfigUp(path.dirname(cwd), workspaceRoot);
88+
if (configFile) {
89+
return resolveConfig({ root: cwd, configFile }, 'build');
90+
}
91+
}
92+
}
93+
694
return resolveConfig({ root: cwd }, 'build');
795
}
896

0 commit comments

Comments
 (0)