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

Skip to content

feat: expo-sourcemap-support #1392

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 24 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
7a51fd5
feat: add expo sourcemap support
ahmedAlaaInstabug May 14, 2025
e6560be
feat: add expo sourcemap support
ahmedAlaaInstabug May 14, 2025
9b8f9c3
feat: add expo sourcemap support
ahmedAlaaInstabug May 14, 2025
efdb693
feat: add expo sourcemap support
ahmedAlaaInstabug May 14, 2025
09df27e
feat: add expo sourcemap support
ahmedAlaaInstabug May 14, 2025
f9d8aed
feat: add expo sourcemap support
ahmedAlaaInstabug May 14, 2025
c393b59
feat: add expo sourcemap support
ahmedAlaaInstabug May 14, 2025
6cd4358
feat: add expo sourcemap support
ahmedAlaaInstabug May 14, 2025
2faf0d2
feat: add expo sourcemap support
ahmedAlaaInstabug May 15, 2025
724589e
feat: update sourcemaps.gradle
ahmedAlaaInstabug May 15, 2025
e8e834e
feat: update sourcemaps.gradle
ahmedAlaaInstabug May 17, 2025
f8e6579
feat: update sourcemaps.gradle
ahmedAlaaInstabug May 17, 2025
8716d32
feat: update sourcemaps.gradle
ahmedAlaaInstabug May 17, 2025
f1dd2a9
feat: update sourcemaps.gradle
ahmedAlaaInstabug May 17, 2025
59366fc
feat: update sourcemaps.gradle
ahmedAlaaInstabug May 20, 2025
f6806c0
feat: add permissions
ahmedAlaaInstabug May 21, 2025
228c959
feat support sourcemap expo
ahmedAlaaInstabug May 21, 2025
e563c4b
feat support sourcemap expo
ahmedAlaaInstabug May 21, 2025
705e434
feat: add manifest permissions
ahmedAlaaInstabug May 25, 2025
edd0524
feat: add manifest permissions
ahmedAlaaInstabug May 25, 2025
7fbaa4f
feat: add manifest permissions
ahmedAlaaInstabug May 25, 2025
bea99b3
fix: upload command
ahmedAlaaInstabug May 28, 2025
eaa462f
fix: upload command
ahmedAlaaInstabug May 28, 2025
4d45668
fix: upload command
ahmedAlaaInstabug May 28, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 87 additions & 12 deletions android/sourcemaps.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import org.apache.tools.ant.taskdefs.condition.Os

gradle.projectsEvaluated {
project.afterEvaluate {
// Works for both `bundleReleaseJsAndAssets` and `createBundleReleaseJsAndAssets` and product flavors
def suffix = 'ReleaseJsAndAssets'
def bundleTasks = project(':app').tasks.findAll {
Expand All @@ -15,11 +15,13 @@ gradle.projectsEvaluated {
def flavor = name.substring(start, end).uncapitalize()
def defaultVersion = getDefaultVersion(flavor)

task.finalizedBy createUploadSourcemapsTask(flavor, defaultVersion.name, defaultVersion.code)


task.finalizedBy createUploadSourcemapsTask(flavor, defaultVersion.name, defaultVersion.code,task)
}
}

Task createUploadSourcemapsTask(String flavor, String defaultVersionName, String defaultVersionCode) {
Task createUploadSourcemapsTask(String flavor, String defaultVersionName, String defaultVersionCode, Task task) {
def name = 'uploadSourcemaps' + flavor.capitalize()

// Don't recreate the task if it already exists.
Expand All @@ -39,18 +41,26 @@ Task createUploadSourcemapsTask(String flavor, String defaultVersionName, String
try {
def appProject = project(':app')
def appDir = appProject.projectDir
def sourceMapFile = getSourceMapFile(appDir, flavor)
def sourceMapFile = getSourceMapFile(appDir, flavor,task)
println "✅ Resolved sourcemap file path: ${sourceMapFile.absolutePath}"

def jsProjectDir = rootDir.parentFile
def instabugDir = new File(['node', '-p', 'require.resolve("instabug-reactnative/package.json")'].execute(null, rootDir).text.trim()).getParentFile()

def tokenShellFile = new File(instabugDir, 'scripts/find-token.sh')
def inferredToken = executeShellScript(tokenShellFile, jsProjectDir)
def tokenJsFile = new File(instabugDir, 'scripts/find-token.js')
def inferredToken = executeNodeScript(tokenJsFile, jsProjectDir)

if (!inferredToken) {
throw new GradleException("❌ Unable to infer Instabug token from script: ${tokenShellFile.absolutePath}")
}

def appToken = resolveVar('App Token', 'INSTABUG_APP_TOKEN', inferredToken)

def versionName = resolveVar('Version Name', 'INSTABUG_VERSION_NAME', defaultVersionName)
def versionCode = resolveVar('Version Code', 'INSTABUG_VERSION_CODE', defaultVersionCode)

println "📦 Uploading with versionName=${versionName}, versionCode=${versionCode}, appToken=${appToken.take(5)}..."

exec {
def osCompatibility = Os.isFamily(Os.FAMILY_WINDOWS) ? ['cmd', '/c'] : []
def args = [
Expand All @@ -67,34 +77,57 @@ Task createUploadSourcemapsTask(String flavor, String defaultVersionName, String
} catch (exception) {
project.logger.error "Failed to upload source map file.\n" +
"Reason: ${exception.message}"
throw exception

}
}
}

return provider.get()
}

File getSourceMapFile(File appDir, String flavor) {
File getSourceMapFile(File appDir, String flavor, Task task) {
def defaultFlavorPath = flavor.empty ? 'release' : "${flavor}Release"
def defaultSourceMapDest = "build/generated/sourcemaps/react/${defaultFlavorPath}/index.android.bundle.map"
def defaultSourceMapFile = new File(appDir, defaultSourceMapDest)
def props = task.getProperties()

def bundleAssetName = props.containsKey("bundleAssetName") ? props.bundleAssetName?.getOrNull() : null
def jsSourceMapsDir = props.containsKey("jsSourceMapsDir") ? props.jsSourceMapsDir?.getOrNull() : null
def jsIntermediateSourceMapsDir = props.containsKey("jsIntermediateSourceMapsDir") ? props.jsIntermediateSourceMapsDir?.getOrNull() : null

if (bundleAssetName && jsSourceMapsDir) {
def outputSourceMap = new File(jsSourceMapsDir.asFile.absolutePath, "${bundleAssetName}.map")
if (outputSourceMap.exists()) {
return outputSourceMap
}
}

if (bundleAssetName && jsIntermediateSourceMapsDir) {
def packagerOutputSourceMap = new File(jsIntermediateSourceMapsDir.asFile.absolutePath, "${bundleAssetName}.packager.map")
if (packagerOutputSourceMap.exists()) {
return packagerOutputSourceMap
}
}

if (defaultSourceMapFile.exists()) {
return defaultSourceMapFile
}

if (flavor.empty) {
throw new InvalidUserDataException("Unable to find source map file at: ${defaultSourceMapFile.absolutePath}.")
println"Source map file not found at: ${defaultSourceMapFile.absolutePath}. Skipping."
return null
}

def fallbackSourceMapDest = "build/generated/sourcemaps/react/${flavor}/release/index.android.bundle.map"
def fallbackSourceMapFile = new File(appDir, fallbackSourceMapDest)

project.logger.info "Unable to find source map file at: ${defaultSourceMapFile.absolutePath}.\n" +
println "Unable to find source map file at: ${defaultSourceMapFile.absolutePath}.\n" +
"Falling back to ${fallbackSourceMapFile.absolutePath}."

if (!fallbackSourceMapFile.exists()) {
throw new InvalidUserDataException("Unable to find source map file at: ${fallbackSourceMapFile.absolutePath} either.")
println "Fallback source map file not found at: ${fallbackSourceMapFile.absolutePath}. Skipping."
return null
}

return fallbackSourceMapFile
Expand Down Expand Up @@ -155,14 +188,56 @@ String resolveVar(String name, String envKey, String defaultValue) {
return value
}

static String executeNodeScript(File script, File workingDir) {
if (!script.exists()) {
println "Script not found: ${script.absolutePath}"
return null
}

def output = new StringBuffer()
def error = new StringBuffer()

try {
def process = ['node', script.getAbsolutePath()].execute(null, workingDir)
process.waitForProcessOutput(output, error)

if (process.exitValue() != 0) {
println "Script failed with exit code ${process.exitValue()}"
println "Standard Error:\n${error.toString().trim()}"
println "Standard Output:\n${output.toString().trim()}"
return null
}

return output.toString().trim()

} catch (Exception e) {
println "Exception while executing Node.js script: ${e.message}"
e.printStackTrace()
return null
}
}

static String executeShellScript(File script, File workingDir) {
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
return null
}

if (!script.canExecute()) {
// Try to set executable permission
script.setExecutable(true)
}

def output = new StringBuffer()
def error = new StringBuffer()

// Using 'sh' instead of './' to avoid needing exec permission, but keeping chmod above just in case
def process = ['sh', script.getAbsolutePath()].execute(null, workingDir)
process?.waitForProcessOutput(output, new StringBuffer())
process?.waitForProcessOutput(output, error)

if (process.exitValue() != 0) {
println "Error running script: ${error.toString().trim()}"
return null
}

return process?.exitValue() == 0 ? output.toString().trim() : null
return output.toString().trim()
}
1 change: 1 addition & 0 deletions app.plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('./expo');
36 changes: 36 additions & 0 deletions cli/commands/UploadEasUpdatesSourcemaps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Command, Option } from 'commander';
import { UploadEasUpdatesSourcemaps, UploadEasUpdatesSourcemapsOptions } from '../upload';

export const uploadEasUpdatesSourcemapsCommand = new Command();

uploadEasUpdatesSourcemapsCommand
.name('upload-eas-updates-sourcemaps')
.addOption(
new Option('-f, --file <path>', 'The path of eas update folder')
.makeOptionMandatory()
.default('dist'),
)
.addOption(
new Option('-t, --token <value>', 'Your App Token')
.env('INSTABUG_APP_TOKEN')
.makeOptionMandatory(),
)
.addOption(
new Option('-n, --name <value>', 'The app version name')
.env('INSTABUG_APP_VERSION_NAME')
.makeOptionMandatory(),
)
.addOption(
new Option('-c, --code <value>', 'The app version code')
.env('INSTABUG_APP_VERSION_CODE')
.makeOptionMandatory(),
)
.addOption(
new Option('--androidUpdateId <value>', "The CodePush label if it's a CodePush release"),
)
.addOption(new Option('--iosUpdateId <value>', "The CodePush label if it's a CodePush release"))
.action(function (this: Command) {
const options = this.opts<UploadEasUpdatesSourcemapsOptions>();
UploadEasUpdatesSourcemaps(options);
})
.showHelpAfterError();
4 changes: 3 additions & 1 deletion cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Command } from 'commander';

import { uploadSourcemapsCommand } from './commands/UploadSourcemaps';
import { UploadSoFilesCommand } from './commands/UploadSoFiles';
import { uploadEasUpdatesSourcemapsCommand } from './commands/UploadEasUpdatesSourcemaps';

const program = new Command();

Expand All @@ -12,6 +13,7 @@ program
.description('A CLI for uploading source maps to Instabug dashboard.')
.usage('[command]')
.addCommand(uploadSourcemapsCommand)
.addCommand(UploadSoFilesCommand);
.addCommand(UploadSoFilesCommand)
.addCommand(uploadEasUpdatesSourcemapsCommand);

program.parse(process.argv);
1 change: 1 addition & 0 deletions cli/upload/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './uploadSourcemaps';
export * from './uploadSoFiles';
export * from './uploadEasUpdatesSourcemaps';
74 changes: 74 additions & 0 deletions cli/upload/uploadEasUpdatesSourcemaps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import fs from 'fs';
import { uploadSourcemaps } from './uploadSourcemaps';
import * as path from 'path';

export interface UploadEasUpdatesSourcemapsOptions {
file: string;
token: string;
name: string;
code: string;
androidUpdateId?: string;
iosUpdateId?: string;
/**
* Disables logging to the console and prevents process exit on error.
*
* @default false
* */
silent?: boolean;
}

function getMapFile(folderPath: string): string | null {
try {
if (fs.existsSync(folderPath)) {
const files = fs.readdirSync(folderPath);
const mapFile = files.find((file) => file.endsWith('.map'));
if (!mapFile) {
return null;
}
return path.join(folderPath, mapFile);
}
return null;
} catch (err) {
console.error('Failed to read folder:', err);
return null;
}
}

/**
* Uploads JavaScript sourcemaps to Instabug.
*
* @param opts Options for the sourcemaps upload process.
* @returns A promise that resolves to a boolean indicating whether the upload was successful.
*/
export const UploadEasUpdatesSourcemaps = async (
opts: UploadEasUpdatesSourcemapsOptions,
): Promise<boolean> => {
const jsFolderPath = path.join(opts.file, '_expo', 'static', 'js');

const androidFile = getMapFile(path.join(jsFolderPath, 'android'));
const iosFile = getMapFile(path.join(jsFolderPath, 'ios'));
if (androidFile && fs.existsSync(androidFile)) {
await uploadSourcemaps({
platform: 'android',
name: opts.name,
code: opts.code,
token: opts.token,
label: opts.androidUpdateId,
file: androidFile,
silent: opts.silent,
});
}

if (iosFile && fs.existsSync(iosFile)) {
await uploadSourcemaps({
platform: 'ios',
name: opts.name,
code: opts.code,
token: opts.token,
label: opts.iosUpdateId,
file: iosFile,
silent: opts.silent,
});
}
return true;
};
Loading