Skip to content

Commit db3772f

Browse files
committed
fix expo resolve
1 parent 03a9edf commit db3772f

File tree

2 files changed

+173
-34
lines changed

2 files changed

+173
-34
lines changed

src/bundle-runner.ts

Lines changed: 87 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,90 @@ interface GradleConfig {
3232
enableHermes?: boolean;
3333
}
3434

35+
const dependencyFields = [
36+
'dependencies',
37+
'devDependencies',
38+
'peerDependencies',
39+
'optionalDependencies',
40+
] as const;
41+
42+
type ResolvedExpoCli = {
43+
cliPath: string;
44+
usingExpo: boolean;
45+
};
46+
47+
export function hasProjectDependency(
48+
dependencyName: string,
49+
projectRoot = process.cwd(),
50+
): boolean {
51+
try {
52+
const packageJson = JSON.parse(
53+
fs.readFileSync(path.join(projectRoot, 'package.json'), 'utf8'),
54+
) as Record<string, unknown>;
55+
56+
return dependencyFields.some((field) => {
57+
const dependencies = packageJson[field];
58+
if (typeof dependencies !== 'object' || dependencies === null) {
59+
return false;
60+
}
61+
return dependencyName in dependencies;
62+
});
63+
} catch {
64+
return false;
65+
}
66+
}
67+
68+
export function resolveExpoCli(projectRoot = process.cwd()): ResolvedExpoCli {
69+
if (!hasProjectDependency('expo', projectRoot)) {
70+
return {
71+
cliPath: '',
72+
usingExpo: false,
73+
};
74+
}
75+
76+
try {
77+
const searchPaths = [projectRoot];
78+
79+
try {
80+
const expoPackageJsonPath = require.resolve('expo/package.json', {
81+
paths: [projectRoot],
82+
});
83+
searchPaths.push(path.dirname(expoPackageJsonPath));
84+
} catch {
85+
// expo 包不存在,忽略
86+
}
87+
88+
const cliPath = require.resolve('@expo/cli', {
89+
paths: searchPaths,
90+
});
91+
const expoCliVersion = JSON.parse(
92+
fs.readFileSync(
93+
require.resolve('@expo/cli/package.json', {
94+
paths: searchPaths,
95+
}),
96+
'utf8',
97+
),
98+
).version;
99+
100+
if (!satisfies(expoCliVersion, '>= 0.10.17')) {
101+
return {
102+
cliPath: '',
103+
usingExpo: false,
104+
};
105+
}
106+
107+
return {
108+
cliPath,
109+
usingExpo: true,
110+
};
111+
} catch {
112+
return {
113+
cliPath: '',
114+
usingExpo: false,
115+
};
116+
}
117+
}
118+
35119
export async function runReactNativeBundleCommand({
36120
bundleName,
37121
dev,
@@ -65,40 +149,9 @@ export async function runReactNativeBundleCommand({
65149
let usingExpo = false;
66150

67151
const getExpoCli = () => {
68-
try {
69-
const searchPaths = [process.cwd()];
70-
71-
try {
72-
const expoPath = require.resolve('expo/package.json', {
73-
paths: [process.cwd()],
74-
});
75-
const expoDir = expoPath.replace(/\/package\.json$/, '');
76-
searchPaths.push(expoDir);
77-
} catch {
78-
// expo 包不存在,忽略
79-
}
80-
81-
cliPath = require.resolve('@expo/cli', {
82-
paths: searchPaths,
83-
});
84-
85-
const expoCliVersion = JSON.parse(
86-
fs
87-
.readFileSync(
88-
require.resolve('@expo/cli/package.json', {
89-
paths: searchPaths,
90-
}),
91-
)
92-
.toString(),
93-
).version;
94-
if (satisfies(expoCliVersion, '>= 0.10.17')) {
95-
usingExpo = true;
96-
} else {
97-
cliPath = '';
98-
}
99-
} catch {
100-
// fallback 到 RN CLI
101-
}
152+
const resolvedExpoCli = resolveExpoCli();
153+
cliPath = resolvedExpoCli.cliPath;
154+
usingExpo = resolvedExpoCli.usingExpo;
102155
};
103156

104157
const getRnCli = () => {

tests/bundle-runner.test.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
2+
import fs from 'fs';
3+
import os from 'os';
4+
import path from 'path';
5+
import { resolveExpoCli } from '../src/bundle-runner';
6+
7+
function mkTempDir(prefix: string): string {
8+
return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
9+
}
10+
11+
function writeJson(filePath: string, value: unknown): void {
12+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
13+
fs.writeFileSync(filePath, JSON.stringify(value, null, 2));
14+
}
15+
16+
function writeFile(filePath: string, content = ''): void {
17+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
18+
fs.writeFileSync(filePath, content);
19+
}
20+
21+
describe('bundle-runner expo cli detection', () => {
22+
let originalCwd = '';
23+
let tempRoot = '';
24+
25+
beforeEach(() => {
26+
originalCwd = process.cwd();
27+
tempRoot = mkTempDir('rn-update-bundle-runner-');
28+
});
29+
30+
afterEach(() => {
31+
process.chdir(originalCwd);
32+
if (tempRoot && fs.existsSync(tempRoot)) {
33+
fs.rmSync(tempRoot, { recursive: true, force: true });
34+
}
35+
});
36+
37+
test('skips expo cli when project package.json does not depend on expo', () => {
38+
writeJson(path.join(tempRoot, 'package.json'), {
39+
name: 'plain-rn-app',
40+
dependencies: {
41+
'react-native': '0.82.1',
42+
},
43+
});
44+
writeJson(path.join(tempRoot, 'node_modules/@expo/cli/package.json'), {
45+
name: '@expo/cli',
46+
version: '0.10.17',
47+
main: 'build/index.js',
48+
});
49+
writeFile(path.join(tempRoot, 'node_modules/@expo/cli/build/index.js'));
50+
51+
const resolved = resolveExpoCli(tempRoot);
52+
53+
expect(resolved).toEqual({
54+
cliPath: '',
55+
usingExpo: false,
56+
});
57+
});
58+
59+
test('uses expo cli when project package.json declares expo dependency', () => {
60+
writeJson(path.join(tempRoot, 'package.json'), {
61+
name: 'expo-app',
62+
dependencies: {
63+
expo: '^54.0.0',
64+
},
65+
});
66+
writeJson(path.join(tempRoot, 'node_modules/expo/package.json'), {
67+
name: 'expo',
68+
version: '54.0.0',
69+
});
70+
writeJson(path.join(tempRoot, 'node_modules/@expo/cli/package.json'), {
71+
name: '@expo/cli',
72+
version: '0.10.17',
73+
main: 'build/index.js',
74+
});
75+
writeFile(path.join(tempRoot, 'node_modules/@expo/cli/build/index.js'));
76+
77+
const resolved = resolveExpoCli(tempRoot);
78+
79+
expect(resolved.usingExpo).toBe(true);
80+
expect(fs.realpathSync(resolved.cliPath)).toBe(
81+
fs.realpathSync(
82+
path.join(tempRoot, 'node_modules/@expo/cli/build/index.js'),
83+
),
84+
);
85+
});
86+
});

0 commit comments

Comments
 (0)