Compare commits

...
2 changed files with 278 additions and 0 deletions

No files matched your search

@@ -309,4 +309,217 @@ Refer to "Xcode Logs" below for additional, more detailed logs.`);
`Gradle build failed with unknown error. See logs for the "Run gradlew" phase for more information.`
);
});
it('detects NPM_ERESOLVE tracking code', async () => {
const err = await resolveBuildPhaseErrorAsync(
new Error(),
[
'npm ERR! code ERESOLVE',
'npm ERR! ERESOLVE could not resolve',
'npm ERR! While resolving: [email protected]',
],
{
job: { platform: Platform.ANDROID } as Job,
phase: BuildPhase.INSTALL_DEPENDENCIES,
env: {},
},
'/fake/path'
);
expect(err.errorCode).toBe(errors.ErrorCode.UNKNOWN_ERROR);
expect(err.trackingCode).toBe('NPM_ERESOLVE');
});
it('detects METRO_UNABLE_TO_RESOLVE tracking code', async () => {
const err = await resolveBuildPhaseErrorAsync(
new Error(),
[
'error: Unable to resolve module ./src/missing from /home/expo/workingdir/build/index.js: ',
'None of these files exist:',
],
{
job: { platform: Platform.ANDROID } as Job,
phase: BuildPhase.PREBUILD,
env: {},
},
'/fake/path'
);
expect(err.errorCode).toBe(errors.ErrorCode.UNKNOWN_ERROR);
expect(err.trackingCode).toBe('METRO_UNABLE_TO_RESOLVE');
});
it('detects METRO_UNABLE_TO_RESOLVE without phase restriction', async () => {
const err = await resolveBuildPhaseErrorAsync(
new Error(),
[
'error: Unable to resolve module @react-native/assets from /home/expo/workingdir/build/App.js',
],
{
job: { platform: Platform.ANDROID } as Job,
phase: BuildPhase.EAGER_BUNDLE,
env: {},
},
'/fake/path'
);
expect(err.errorCode).toBe(errors.ErrorCode.UNKNOWN_ERROR);
expect(err.trackingCode).toBe('METRO_UNABLE_TO_RESOLVE');
});
it('detects PNPM_ERROR tracking code', async () => {
const err = await resolveBuildPhaseErrorAsync(
new Error(),
[
' ERR_PNPM_PEER_DEP_ISSUES Unmet peer dependencies',
'hint: If you want peer dependencies to be automatically installed, add "auto-install-peers=true" to an .npmrc file.',
],
{
job: { platform: Platform.ANDROID } as Job,
phase: BuildPhase.INSTALL_DEPENDENCIES,
env: {},
},
'/fake/path'
);
expect(err.errorCode).toBe(errors.ErrorCode.UNKNOWN_ERROR);
expect(err.trackingCode).toBe('PNPM_ERROR');
});
it('does not detect PNPM_ERROR outside INSTALL_DEPENDENCIES phase', async () => {
const err = await resolveBuildPhaseErrorAsync(
new Error(),
[' ERR_PNPM_PEER_DEP_ISSUES Unmet peer dependencies'],
{
job: { platform: Platform.ANDROID } as Job,
phase: BuildPhase.PREBUILD,
env: {},
},
'/fake/path'
);
expect(err.errorCode).toBe(errors.ErrorCode.UNKNOWN_ERROR);
expect(err.trackingCode).toBeUndefined();
});
it('detects PREBUILD_DANGEROUS_MOD_ENOENT tracking code', async () => {
const err = await resolveBuildPhaseErrorAsync(
new Error(),
[
"Error: [android.dangerous]: withAndroidDangerousBaseMod: ENOENT: no such file or directory, open './assets/splash.png'",
],
{
job: { platform: Platform.ANDROID } as Job,
phase: BuildPhase.PREBUILD,
env: {},
},
'/fake/path'
);
expect(err.errorCode).toBe(errors.ErrorCode.UNKNOWN_ERROR);
expect(err.trackingCode).toBe('PREBUILD_DANGEROUS_MOD_ENOENT');
});
it('detects PREBUILD_DANGEROUS_MOD_ENOENT for iOS', async () => {
const err = await resolveBuildPhaseErrorAsync(
new Error(),
[
"Error: [ios.dangerous]: withIosDangerousBaseMod: ENOENT: no such file or directory, open './assets/fonts/CustomFont.ttf'",
],
{
job: { platform: Platform.IOS } as Job,
phase: BuildPhase.PREBUILD,
env: {},
},
'/fake/path'
);
expect(err.errorCode).toBe(errors.ErrorCode.UNKNOWN_ERROR);
expect(err.trackingCode).toBe('PREBUILD_DANGEROUS_MOD_ENOENT');
});
it('detects SYNTAX_ERROR tracking code', async () => {
const err = await resolveBuildPhaseErrorAsync(
new Error(),
['SyntaxError: Unexpected token } in JSON at position 1234'],
{
job: { platform: Platform.ANDROID } as Job,
phase: BuildPhase.PREBUILD,
env: {},
},
'/fake/path'
);
expect(err.errorCode).toBe(errors.ErrorCode.UNKNOWN_ERROR);
expect(err.trackingCode).toBe('SYNTAX_ERROR');
});
it('detects MONOREPO_PACKAGE_JSON_NOT_FOUND tracking code', async () => {
const err = await resolveBuildPhaseErrorAsync(
new Error(),
['Error: package.json does not exist at /home/expo/workingdir/build/packages/app'],
{
job: { platform: Platform.ANDROID } as Job,
phase: BuildPhase.INSTALL_DEPENDENCIES,
env: {},
},
'/fake/path'
);
expect(err.errorCode).toBe(errors.ErrorCode.UNKNOWN_ERROR);
expect(err.trackingCode).toBe('MONOREPO_PACKAGE_JSON_NOT_FOUND');
});
it('detects EXPO_CONFIG_ERROR tracking code', async () => {
const err = await resolveBuildPhaseErrorAsync(
new Error(),
['ConfigError: Property "expo.ios.bundleIdentifier" in app.json is invalid.'],
{
job: { platform: Platform.IOS } as Job,
phase: BuildPhase.PREBUILD,
env: {},
},
'/fake/path'
);
expect(err.errorCode).toBe(errors.ErrorCode.UNKNOWN_ERROR);
expect(err.trackingCode).toBe('EXPO_CONFIG_ERROR');
});
it('detects RUNTIME_VERSION_MISMATCH tracking code', async () => {
const err = await resolveBuildPhaseErrorAsync(
new Error(),
['Error: runtimeVersion in Expo.plist policies must be set to a valid value.'],
{
job: { platform: Platform.IOS } as Job,
phase: BuildPhase.CONFIGURE_EXPO_UPDATES,
env: {},
},
'/fake/path'
);
expect(err.errorCode).toBe(errors.ErrorCode.UNKNOWN_ERROR);
expect(err.trackingCode).toBe('RUNTIME_VERSION_MISMATCH');
});
it('does not detect RUNTIME_VERSION_MISMATCH outside CONFIGURE_EXPO_UPDATES phase', async () => {
const err = await resolveBuildPhaseErrorAsync(
new Error(),
['Error: runtimeVersion in Expo.plist policies must be set to a valid value.'],
{
job: { platform: Platform.IOS } as Job,
phase: BuildPhase.PREBUILD,
env: {},
},
'/fake/path'
);
expect(err.errorCode).toBe(errors.ErrorCode.UNKNOWN_ERROR);
expect(err.trackingCode).toBeUndefined();
});
it('detects CONFIG_PLUGIN_RESOLVE_ERROR tracking code', async () => {
const err = await resolveBuildPhaseErrorAsync(
new Error(),
[
'Error: Failed to resolve plugin for module "expo-camera" relative to "/home/expo/workingdir/build"',
],
{
job: { platform: Platform.ANDROID } as Job,
phase: BuildPhase.PREBUILD,
env: {},
},
'/fake/path'
);
expect(err.errorCode).toBe(errors.ErrorCode.UNKNOWN_ERROR);
expect(err.trackingCode).toBe('CONFIG_PLUGIN_RESOLVE_ERROR');
});
});
@@ -361,4 +361,69 @@ export const buildErrorHandlers: ErrorHandler<TrackedBuildError>[] = [
'fastlane: Bundle React Native code and images failed.'
),
},
// --- Specific patterns for reducing unknown_error bucket ---
{
phase: BuildPhase.INSTALL_DEPENDENCIES,
// npm ERR! ERESOLVE could not resolve
regexp: /npm ERR!.*ERESOLVE|ERESOLVE could not resolve/,
createError: () => new TrackedBuildError('NPM_ERESOLVE', 'npm: ERESOLVE could not resolve.'),
},
{
phase: BuildPhase.INSTALL_DEPENDENCIES,
// ERR_PNPM_PEER_DEP_ISSUES or other pnpm errors
regexp: /ERR_PNPM_/,
createError: () => new TrackedBuildError('PNPM_ERROR', 'pnpm: error during install.'),
},
{
// Can occur in PREBUILD, EAGER_BUNDLE, or other phases
// Unable to resolve module `./src/missing` from `index.js`
regexp: /Unable to resolve module/,
createError: () =>
new TrackedBuildError('METRO_UNABLE_TO_RESOLVE', 'metro: Unable to resolve module.'),
},
{
phase: BuildPhase.PREBUILD,
// Error: [android.dangerous]: withAndroidDangerousBaseMod: ENOENT: no such file or directory, open './assets/splash.png'
// Must come AFTER EXPO_CLI_MISSING_ICON which matches the icon-specific subset
regexp: /with(?:Android|Ios)DangerousBaseMod:.*ENOENT/,
createError: () =>
new TrackedBuildError(
'PREBUILD_DANGEROUS_MOD_ENOENT',
'prebuild: ENOENT in dangerous base mod.'
),
},
{
// SyntaxError: Unexpected token ...
regexp: /SyntaxError:/,
createError: () => new TrackedBuildError('SYNTAX_ERROR', 'SyntaxError encountered.'),
},
{
// package.json does not exist (common in monorepos with wrong working directory)
regexp: /package\.json does not exist/,
createError: () =>
new TrackedBuildError('MONOREPO_PACKAGE_JSON_NOT_FOUND', 'package.json does not exist.'),
},
{
// ConfigError: Property ... in app.json is invalid
regexp: /ConfigError:/,
createError: () => new TrackedBuildError('EXPO_CONFIG_ERROR', 'expo: ConfigError encountered.'),
},
{
phase: BuildPhase.CONFIGURE_EXPO_UPDATES,
// runtimeVersion policies must be set ...
// runtime version is not equal ...
regexp: /runtimeVersion.*policies.*must.*set|runtime version.*not.*equal/i,
createError: () =>
new TrackedBuildError('RUNTIME_VERSION_MISMATCH', 'expo-updates: runtime version mismatch.'),
},
{
phase: BuildPhase.PREBUILD,
// Failed to resolve plugin for module "expo-camera" ...
regexp: /Failed to resolve plugin/,
createError: () =>
new TrackedBuildError(
'CONFIG_PLUGIN_RESOLVE_ERROR',
'prebuild: Failed to resolve config plugin.'
),
},
];