Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 228c959

Browse files
feat support sourcemap expo
1 parent f6806c0 commit 228c959

File tree

3 files changed

+141
-101
lines changed

3 files changed

+141
-101
lines changed

plugin/src/withInstabug.ts

Lines changed: 32 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { ConfigPlugin } from 'expo/config-plugins';
22
import { createRunOncePlugin } from 'expo/config-plugins';
3+
34
import { withInstabugAndroid } from './withInstabugAndroid';
45
import { withInstabugIOS } from './withInstabugIOS';
56

@@ -9,46 +10,47 @@ export interface PluginProps {
910
enableMediaUploadBugReporting?: boolean;
1011
}
1112

12-
const withInstabugPlugin: ConfigPlugin<PluginProps> = (config, props) => {
13-
let cfg = config;
14-
if (props.forceUploadSourceMaps === null) {
15-
props.forceUploadSourceMaps = false;
16-
}
13+
const instabugPackage = require('../../package.json') as {
14+
name: string;
15+
version: string;
16+
};
1717

18-
if (props.enableMediaUpload === null) {
19-
props.enableMediaUpload = true;
20-
}
21-
if (props.forceUploadSourceMaps === true) {
18+
const withInstabugPlugin: ConfigPlugin<PluginProps> = (config, props = {}) => {
19+
const { forceUploadSourceMaps = false, enableMediaUploadBugReporting = false } = props;
20+
21+
const sharedProps = {
22+
...props,
23+
name: instabugPackage.name,
24+
forceUploadSourceMaps,
25+
enableMediaUploadBugReporting,
26+
};
27+
28+
let updatedConfig = config;
29+
30+
// Android configuration (only if source maps are enabled)
31+
if (forceUploadSourceMaps) {
2232
try {
23-
cfg = withInstabugAndroid(cfg, {
24-
...props,
25-
name: instabugPackage.name,
26-
});
27-
} catch (e) {
28-
console.warn(`There was a problem with configuring your native Android project: ${e}`);
33+
updatedConfig = withInstabugAndroid(updatedConfig, sharedProps);
34+
} catch (err) {
35+
console.warn(
36+
'[Instabug] Failed to configure Android project:',
37+
(err as Error).message ?? err,
38+
);
2939
}
3040
}
41+
42+
// iOS configuration
3143
try {
32-
cfg = withInstabugIOS(cfg, {
33-
...props,
34-
name: instabugPackage.name,
35-
});
36-
} catch (e) {
37-
console.warn(`There was a problem with configuring your native iOS project: ${e}`);
44+
updatedConfig = withInstabugIOS(updatedConfig, sharedProps);
45+
} catch (err) {
46+
console.warn('[Instabug] Failed to configure iOS project:', (err as Error).message ?? err);
3847
}
3948

40-
return cfg;
49+
return updatedConfig;
4150
};
4251

43-
const instabugPackage: {
44-
name: string;
45-
version: string;
46-
} = require('../../package.json');
47-
48-
const withInstabug = createRunOncePlugin(
52+
export const withInstabug = createRunOncePlugin(
4953
withInstabugPlugin,
5054
instabugPackage.name,
5155
instabugPackage.version,
5256
);
53-
54-
export { withInstabug };

plugin/src/withInstabugAndroid.ts

Lines changed: 43 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,42 +4,66 @@ import type { PluginProps } from './withInstabug';
44

55
export const withInstabugAndroid: ConfigPlugin<PluginProps> = (config, props) => {
66
return withAppBuildGradle(config, (configAndroid) => {
7-
if (configAndroid.modResults.language === 'groovy') {
8-
configAndroid.modResults.contents = modifyAppGradleFile(
9-
configAndroid.modResults.contents,
10-
props.name!,
11-
);
7+
const gradle = configAndroid.modResults;
8+
const packageName = props.name;
9+
10+
if (!packageName) {
11+
console.warn('[Instabug] Missing "name" in plugin props. Skipping Android configuration.');
12+
return configAndroid;
13+
}
14+
15+
if (gradle.language === 'groovy') {
16+
gradle.contents = injectGroovyScript(gradle.contents, packageName);
17+
} else if (gradle.language === 'kt') {
18+
gradle.contents = injectKotlinScript(gradle.contents, packageName);
1219
} else {
13-
throw new Error('Cannot configure Instabug because the build.gradle is not groovy');
20+
throw new Error(
21+
'[Instabug] Unsupported Gradle language. Only Groovy and Kotlin DSL are supported.',
22+
);
1423
}
24+
1525
return configAndroid;
1626
});
1727
};
1828

19-
export function modifyAppGradleFile(buildGradle: string, name: string): string {
29+
function injectGroovyScript(buildGradle: string, packageName: string): string {
2030
if (buildGradle.includes('sourcemaps.gradle')) {
2131
return buildGradle;
2232
}
2333

24-
const pattern = /^android {/m;
25-
26-
if (!buildGradle.match(pattern)) {
27-
console.warn('Cannot configure Instabug');
34+
const androidBlockPattern = /^android\s*{/m;
35+
if (!androidBlockPattern.test(buildGradle)) {
36+
console.warn('[Instabug] Could not find "android {" block in Groovy build.gradle.');
2837
return buildGradle;
2938
}
3039

31-
console.log(name);
32-
33-
const resolveInstabugReactNativePathGroovy = `
34-
def instabugPath = ["node", "--print", "require('path').dirname(require.resolve('${name}/package.json'))"]
40+
const script = `
41+
def instabugPath = ["node", "--print", "require('path').dirname(require.resolve('${packageName}/package.json'))"]
3542
.execute()
3643
.text
3744
.trim()
3845
apply from: new File(instabugPath, "android/sourcemaps.gradle")
3946
`.trim();
4047

41-
return buildGradle.replace(
42-
pattern,
43-
(match) => `${resolveInstabugReactNativePathGroovy}\n\n${match}`,
44-
);
48+
return buildGradle.replace(androidBlockPattern, `${script}\n\nandroid {`);
49+
}
50+
51+
function injectKotlinScript(buildGradle: string, packageName: string): string {
52+
if (buildGradle.includes('sourcemaps.gradle')) {
53+
return buildGradle;
54+
}
55+
56+
const androidBlockPattern = /^android\s*{/m;
57+
if (!androidBlockPattern.test(buildGradle)) {
58+
console.warn('[Instabug] Could not find "android {" block in Kotlin build.gradle.kts.');
59+
return buildGradle;
60+
}
61+
62+
const script = `
63+
val instabugPath = listOf("node", "--print", "require('path').dirname(require.resolve("${packageName}/package.json"))")
64+
.let { ProcessBuilder(it).start().inputStream.bufferedReader().readText().trim() }
65+
apply(from = File(instabugPath, "android/sourcemaps.gradle"))
66+
`.trim();
67+
68+
return buildGradle.replace(androidBlockPattern, `${script}\n\nandroid {`);
4569
}

plugin/src/withInstabugIOS.ts

Lines changed: 66 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -9,84 +9,98 @@ const PHASE_COMMENT = 'Bundle React Native code and images';
99
const INSTABUG_BUILD_PHASE = '[instabug-reactnative] Upload Sourcemap';
1010

1111
export const withInstabugIOS: ConfigPlugin<PluginProps> = (config, props) => {
12-
let cfg = withXcodeProject(config, (configXcode) => {
13-
const xcodeProject: XcodeProject = configXcode.modResults;
12+
let updatedConfig = withXcodeProject(config, (configXcode) => {
13+
const xcodeProject = configXcode.modResults;
1414
const buildPhases = xcodeProject.hash.project.objects[BUILD_PHASE];
1515

1616
if (!buildPhases) {
1717
console.warn('[Instabug] No build phases found in Xcode project.');
1818
return configXcode;
1919
}
2020

21-
const findPhaseByName = (targetName: string) => {
22-
return Object.entries(buildPhases).find(([, phase]: any) => {
23-
const rawName = phase?.name ?? '';
24-
const cleanName = rawName
25-
.toLowerCase()
26-
.replace('[cp-user] ', '')
27-
.replace(/^"+|"+$/g, '')
28-
.trim();
29-
const target = targetName.toLowerCase().trim();
30-
return cleanName === target;
31-
})?.[1];
32-
};
33-
34-
// Add Instabug build phase if not present
35-
const instabugPhase = findPhaseByName(INSTABUG_BUILD_PHASE);
36-
37-
if (instabugPhase == null && props.forceUploadSourceMaps) {
38-
const packagePath = require.resolve(`${props.name}/package.json`);
39-
const packageDir = path.dirname(packagePath);
40-
const sourcemapsPath = path.join(packageDir, 'ios/sourcemaps.sh');
41-
42-
if (fs.existsSync(sourcemapsPath)) {
43-
xcodeProject.addBuildPhase([], BUILD_PHASE, INSTABUG_BUILD_PHASE, null, {
44-
shellPath: '/bin/sh',
45-
shellScript: '/bin/sh ' + sourcemapsPath,
46-
});
47-
} else {
48-
console.warn(`Could not find sourcemaps.sh at path: ${sourcemapsPath}`);
49-
}
50-
}
21+
// Add Instabug build phase if not already present
22+
const hasInstabugPhase = Boolean(findBuildPhase(buildPhases, INSTABUG_BUILD_PHASE));
5123

52-
const bundleReactNativePhase = xcodeProject.pbxItemByComment(PHASE_COMMENT, BUILD_PHASE);
24+
if (!hasInstabugPhase && props.forceUploadSourceMaps) {
25+
addInstabugBuildPhase(xcodeProject, props.name);
26+
}
5327

54-
if (bundleReactNativePhase?.shellScript) {
55-
bundleReactNativePhase.shellScript = addSourceMapExport(bundleReactNativePhase.shellScript);
28+
// Patch bundle React Native phase with source map export
29+
const bundlePhase = xcodeProject.pbxItemByComment(PHASE_COMMENT, BUILD_PHASE);
30+
if (bundlePhase?.shellScript) {
31+
bundlePhase.shellScript = injectSourceMapExport(bundlePhase.shellScript);
5632
}
5733

5834
return configXcode;
5935
});
36+
37+
// Add media permissions to Info.plist if enabled
6038
if (props.enableMediaUploadBugReporting) {
61-
const instabugConfig = config?.extra?.instabug || {}; // Read custom configuration from extra.instabug
39+
const instabugConfig = config.extra?.instabug ?? {};
6240

6341
const microphonePermission =
6442
instabugConfig.microphonePermission || 'This app needs access to your microphone.';
6543
const photoLibraryPermission =
6644
instabugConfig.photoLibraryPermission || 'This app needs access to your photos.';
6745

68-
// Modify Info.plist for iOS
69-
cfg = withInfoPlist(config, (configXcode) => {
70-
configXcode.ios.infoPlist = {
71-
...configXcode.ios.infoPlist,
72-
NSMicrophoneUsageDescription: microphonePermission,
73-
NSPhotoLibraryUsageDescription: photoLibraryPermission,
74-
};
75-
return config;
46+
updatedConfig = withInfoPlist(updatedConfig, (configXcode) => {
47+
const plist = configXcode.ios.infoPlist ?? {};
48+
49+
if (!plist.NSMicrophoneUsageDescription) {
50+
plist.NSMicrophoneUsageDescription = microphonePermission;
51+
}
52+
53+
if (!plist.NSPhotoLibraryUsageDescription) {
54+
plist.NSPhotoLibraryUsageDescription = photoLibraryPermission;
55+
}
56+
57+
configXcode.ios.infoPlist = plist;
58+
return configXcode;
7659
});
7760
}
78-
return cfg;
61+
62+
return updatedConfig;
7963
};
8064

81-
function addSourceMapExport(script: string): string {
82-
const exportLine = 'export SOURCEMAP_FILE="$DERIVED_FILE_DIR/main.jsbundle.map"';
83-
const escapedLine = exportLine.replace(/\$/g, '\\$').replace(/"/g, '\\"');
65+
// Find a build phase by its clean name
66+
function findBuildPhase(buildPhases: any, targetName: string): any | undefined {
67+
const target = targetName.toLowerCase().trim();
68+
return Object.values(buildPhases).find((phase: any) => {
69+
const rawName = phase?.name ?? '';
70+
const cleanName = rawName
71+
.toLowerCase()
72+
.replace('[cp-user] ', '')
73+
.replace(/^"+|"+$/g, '')
74+
.trim();
75+
return cleanName === target;
76+
});
77+
}
8478

85-
const injectedLine = `${escapedLine}\\n`;
79+
// Inject Instabug shell script phase
80+
function addInstabugBuildPhase(xcodeProject: XcodeProject, packageName: string): void {
81+
try {
82+
const packagePath = require.resolve(`${packageName}/package.json`);
83+
const sourcemapScriptPath = path.join(path.dirname(packagePath), 'ios/sourcemaps.sh');
84+
85+
if (!fs.existsSync(sourcemapScriptPath)) {
86+
console.warn(`[Instabug] sourcemaps.sh not found at: ${sourcemapScriptPath}`);
87+
return;
88+
}
8689

87-
if (script.includes(escapedLine)) {
88-
return script;
90+
xcodeProject.addBuildPhase([], BUILD_PHASE, INSTABUG_BUILD_PHASE, null, {
91+
shellPath: '/bin/sh',
92+
shellScript: `/bin/sh ${sourcemapScriptPath}`,
93+
});
94+
} catch (err) {
95+
console.warn(`[Instabug] Failed to resolve package path for "${packageName}":`, err);
8996
}
97+
}
98+
99+
// Inject source map export line into the shell script
100+
function injectSourceMapExport(script: string): string {
101+
const exportLine = 'export SOURCEMAP_FILE="$DERIVED_FILE_DIR/main.jsbundle.map"';
102+
const escapedLine = exportLine.replace(/\$/g, '\\$').replace(/"/g, '\\"');
103+
const injectedLine = `${escapedLine}\\n`;
90104

91-
return script.replace(/^"/, `"${injectedLine}`);
105+
return script.includes(escapedLine) ? script : script.replace(/^"/, `"${injectedLine}`);
92106
}

0 commit comments

Comments
 (0)