Skip to content

Commit 059f67e

Browse files
Created global build script & new setupErrorExceptionHandler script.
1 parent 2d56de7 commit 059f67e

File tree

3 files changed

+298
-0
lines changed

3 files changed

+298
-0
lines changed

scripts/build-config.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"scripts": [
3+
"setupBatteryOptimizations.js",
4+
"notificationSounds.ts",
5+
"setupExceptionHandler.js"
6+
]
7+
}

scripts/buildScripts.js

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
const fs = require("fs");
2+
const path = require("path");
3+
const { execSync } = require("child_process");
4+
5+
// Configuration file path
6+
const CONFIG_FILE = path.join(__dirname, "build-config.json");
7+
8+
// Default configuration
9+
const DEFAULT_CONFIG = {
10+
scripts: [
11+
"setupBatteryOptimizations.js",
12+
"notificationSounds.ts",
13+
"setupExceptionHandler.js",
14+
],
15+
// Add enabled: false to disable a script without removing it from the list
16+
// Example: { path: "scriptName.js", enabled: false }
17+
};
18+
19+
// Create default config if it doesn't exist
20+
function ensureConfigExists() {
21+
if (!fs.existsSync(CONFIG_FILE)) {
22+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(DEFAULT_CONFIG, null, 2));
23+
console.log(`Created default build config at ${CONFIG_FILE}`);
24+
}
25+
}
26+
27+
// Get the list of scripts to run
28+
function getScriptsToRun() {
29+
ensureConfigExists();
30+
const config = JSON.parse(fs.readFileSync(CONFIG_FILE, "utf8"));
31+
return config.scripts
32+
.filter((script) => {
33+
if (typeof script === "string") return true;
34+
return script.enabled !== false;
35+
})
36+
.map((script) => {
37+
if (typeof script === "string") return script;
38+
return script.path;
39+
});
40+
}
41+
42+
// Run all enabled scripts
43+
function runBuildScripts() {
44+
console.log("🚀 Running build scripts...");
45+
46+
const scripts = getScriptsToRun();
47+
48+
scripts.forEach((scriptPath) => {
49+
const fullPath = path.join(__dirname, scriptPath);
50+
51+
if (!fs.existsSync(fullPath)) {
52+
console.error(`❌ Script not found: ${fullPath}`);
53+
return;
54+
}
55+
56+
console.log(`▶️ Running: ${scriptPath}`);
57+
58+
try {
59+
if (scriptPath.endsWith(".ts")) {
60+
// For TypeScript files, use ts-node
61+
execSync(`npx ts-node ${fullPath}`, { stdio: "inherit" });
62+
} else {
63+
// For JavaScript files, use node
64+
execSync(`node ${fullPath}`, { stdio: "inherit" });
65+
}
66+
console.log(`✅ Completed: ${scriptPath}`);
67+
} catch (error) {
68+
console.error(`❌ Error running ${scriptPath}:`, error.message);
69+
}
70+
});
71+
72+
console.log("✨ Build scripts completed");
73+
}
74+
75+
// Run the function if this is the main module
76+
if (require.main === module) {
77+
runBuildScripts();
78+
}
79+
80+
module.exports = runBuildScripts;

scripts/setupExceptionHandler.js

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
const fs = require("fs");
2+
const path = require("path");
3+
const { execSync } = require("child_process");
4+
5+
// Get the app package name
6+
function getPackageName() {
7+
try {
8+
const appJson = JSON.parse(fs.readFileSync("./app.json", "utf8"));
9+
return (
10+
appJson.expo.android?.package || "com.codebuilderinc.codebuilderapps"
11+
);
12+
} catch (error) {
13+
console.error("Error getting package name:", error);
14+
return "com.codebuilderinc.codebuilderapps"; // Fallback
15+
}
16+
}
17+
18+
// Setup the TS global handler that works with your actual error service
19+
function setupTSErrorHandler() {
20+
console.log("📱 Setting up TypeScript global error handler...");
21+
22+
const utilsDir = path.join("src", "utils");
23+
const handlerPath = path.join(utilsDir, "errorHandler.ts");
24+
25+
// Create utils directory if it doesn't exist
26+
if (!fs.existsSync(utilsDir)) {
27+
fs.mkdirSync(utilsDir, { recursive: true });
28+
}
29+
30+
// Content for the error handler that uses your actual error reporting service
31+
const handlerContent = `
32+
import { setJSExceptionHandler, setNativeExceptionHandler } from 'react-native-exception-handler';
33+
import { Alert } from 'react-native';
34+
import { errorReportingService } from '../services/errorReporting.service';
35+
36+
// JavaScript Error Handler
37+
export const setupGlobalErrorHandlers = () => {
38+
setJSExceptionHandler((error, isFatal) => {
39+
// Log the error to your existing error reporting service
40+
errorReportingService.submitError?.(error) ||
41+
errorReportingService.reportError?.(error) ||
42+
console.error('Caught JS Exception:', error);
43+
44+
// Show a friendly message instead of crashing
45+
Alert.alert(
46+
'Unexpected Error Occurred',
47+
'We encountered an issue. The app will continue running, and our team has been notified.',
48+
[{ text: 'OK' }]
49+
);
50+
}, true);
51+
52+
// Native Exception Handler
53+
setNativeExceptionHandler(
54+
(exceptionString) => {
55+
// Handle native exceptions
56+
const error = new Error(\`Native Exception: \${exceptionString}\`);
57+
errorReportingService.submitError?.(error) ||
58+
errorReportingService.reportError?.(error) ||
59+
console.error('Caught Native Exception:', exceptionString);
60+
},
61+
false, // don't force app to quit
62+
true // should catch all exceptions
63+
);
64+
};
65+
`;
66+
67+
// Write the file
68+
fs.writeFileSync(handlerPath, handlerContent);
69+
console.log(`✅ Created error handler at ${handlerPath}`);
70+
}
71+
72+
// Setup the Android native exception handler
73+
function setupNativeExceptionHandler() {
74+
console.log("📱 Setting up Android native exception handler...");
75+
76+
// Get package name and calculate path
77+
const packageName = getPackageName();
78+
const packagePath = packageName.replace(/\./g, "/");
79+
80+
// Path to MainActivity.java
81+
const mainActivityPath = path.join(
82+
"android",
83+
"app",
84+
"src",
85+
"main",
86+
"java",
87+
packagePath,
88+
"MainActivity.java"
89+
);
90+
91+
// Check if file exists
92+
if (!fs.existsSync(mainActivityPath)) {
93+
console.log(
94+
`❌ MainActivity.java not found at ${mainActivityPath}. Run 'npx expo prebuild' first.`
95+
);
96+
return;
97+
}
98+
99+
// Read the file
100+
let mainActivityContent = fs.readFileSync(mainActivityPath, "utf8");
101+
102+
// Check if we've already modified this file
103+
if (mainActivityContent.includes("// CUSTOM EXCEPTION HANDLER")) {
104+
console.log("✅ Exception handler already set up in MainActivity.java");
105+
return;
106+
}
107+
108+
// Find the onCreate method or prepare to add it
109+
const onCreateRegex =
110+
/protected void onCreate\s*\(\s*Bundle savedInstanceState\s*\)\s*\{/;
111+
const hasOnCreate = onCreateRegex.test(mainActivityContent);
112+
113+
let exceptionHandlerCode;
114+
115+
if (hasOnCreate) {
116+
// Append to existing onCreate method
117+
exceptionHandlerCode = `
118+
// CUSTOM EXCEPTION HANDLER
119+
final Thread.UncaughtExceptionHandler defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
120+
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
121+
@Override
122+
public void uncaughtException(Thread thread, Throwable throwable) {
123+
// Log the exception but don't crash the app
124+
System.err.println("Caught unhandled exception: " + throwable.getMessage());
125+
throwable.printStackTrace();
126+
127+
// Don't call the default handler to prevent crash screen
128+
// defaultHandler.uncaughtException(thread, throwable);
129+
}
130+
});`;
131+
132+
// Insert the code after the opening brace of onCreate
133+
mainActivityContent = mainActivityContent.replace(
134+
onCreateRegex,
135+
`protected void onCreate(Bundle savedInstanceState) {
136+
super.onCreate(savedInstanceState);${exceptionHandlerCode}`
137+
);
138+
} else {
139+
// Need to add the entire onCreate method
140+
exceptionHandlerCode = `
141+
// CUSTOM EXCEPTION HANDLER
142+
@Override
143+
protected void onCreate(Bundle savedInstanceState) {
144+
super.onCreate(savedInstanceState);
145+
146+
final Thread.UncaughtExceptionHandler defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
147+
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
148+
@Override
149+
public void uncaughtException(Thread thread, Throwable throwable) {
150+
// Log the exception but don't crash the app
151+
System.err.println("Caught unhandled exception: " + throwable.getMessage());
152+
throwable.printStackTrace();
153+
154+
// Don't call the default handler to prevent crash screen
155+
// defaultHandler.uncaughtException(thread, throwable);
156+
}
157+
});
158+
}
159+
`;
160+
161+
// Add the import for Bundle if needed
162+
if (!mainActivityContent.includes("import android.os.Bundle;")) {
163+
mainActivityContent = mainActivityContent.replace(
164+
"import com.facebook.react.ReactActivity;",
165+
"import com.facebook.react.ReactActivity;\nimport android.os.Bundle;"
166+
);
167+
}
168+
169+
// Find the closing brace of the class and insert before it
170+
const lastBraceIndex = mainActivityContent.lastIndexOf("}");
171+
mainActivityContent =
172+
mainActivityContent.substring(0, lastBraceIndex) +
173+
exceptionHandlerCode +
174+
mainActivityContent.substring(lastBraceIndex);
175+
}
176+
177+
// Write the modified file
178+
fs.writeFileSync(mainActivityPath, mainActivityContent);
179+
console.log("✅ Exception handler successfully added to MainActivity.java");
180+
}
181+
182+
// Run both setup functions
183+
function setupExceptionHandlers() {
184+
// Setup the JS error handler utility
185+
setupTSErrorHandler();
186+
187+
// Setup the native Android exception handler
188+
setupNativeExceptionHandler();
189+
190+
// Reminder for necessary package
191+
console.log(`
192+
🔔 Next steps:
193+
1. Add 'react-native-exception-handler' to your dependencies:
194+
npm install react-native-exception-handler
195+
196+
2. Import and use the handler in your _layout.tsx:
197+
import { setupGlobalErrorHandlers } from '../src/utils/errorHandler';
198+
199+
// Add inside a useEffect:
200+
useEffect(() => {
201+
setupGlobalErrorHandlers();
202+
}, []);
203+
`);
204+
}
205+
206+
// Run the function if this is the main module
207+
if (require.main === module) {
208+
setupExceptionHandlers();
209+
}
210+
211+
module.exports = setupExceptionHandlers;

0 commit comments

Comments
 (0)