From dee89ce0d438a1bb7f12b2555530ce3167977416 Mon Sep 17 00:00:00 2001 From: Fabian Schliski Date: Mon, 26 Jan 2026 00:15:42 +0100 Subject: [PATCH] WIP: Strip debug logs in production builds --- .env.example | 6 +- babel.config.js | 10 +++ eas.json | 3 +- plugins/babel-plugin-strip-debug-calls.js | 103 ++++++++++++++++++++++ 4 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 plugins/babel-plugin-strip-debug-calls.js diff --git a/.env.example b/.env.example index dabebd5..dd5f323 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,7 @@ EXPO_NO_TELEMETRY=true EXPO_PUBLIC_GITHUB_RELEASES_URL=https://github.com/DodoraApp/DodoStream/releases -NODE_ENV=development \ No newline at end of file +NODE_ENV=development + +# Strip debug logger calls in production builds (defaults to true if not set) +# Set to 'false' to keep debug logs in production builds +STRIP_DEBUG_LOGS=true \ No newline at end of file diff --git a/babel.config.js b/babel.config.js index 0b4de7d..bae253c 100644 --- a/babel.config.js +++ b/babel.config.js @@ -4,6 +4,16 @@ module.exports = function (api) { plugins.push('react-native-worklets/plugin'); + // Strip debug calls in production builds + // For Expo: use caller.dev to detect production mode (when --dev false is used) + const stripDebugLogs = process.env.STRIP_DEBUG_LOGS !== 'false'; + const isProduction = process.env.NODE_ENV === 'production'; + + if (isProduction && stripDebugLogs) { + console.log("Babel: Stripping debug calls from production build"); + plugins.push('./plugins/babel-plugin-strip-debug-calls.js'); + } + return { presets: ['babel-preset-expo'], plugins, diff --git a/eas.json b/eas.json index f7c2b04..c103075 100644 --- a/eas.json +++ b/eas.json @@ -37,7 +37,8 @@ "environment": "production", "channel": "production", "env": { - "APP_VARIANT": "prod" + "APP_VARIANT": "prod", + "STRIP_DEBUG_LOGS": "true" }, "autoIncrement": true }, diff --git a/plugins/babel-plugin-strip-debug-calls.js b/plugins/babel-plugin-strip-debug-calls.js new file mode 100644 index 0000000..0f1a010 --- /dev/null +++ b/plugins/babel-plugin-strip-debug-calls.js @@ -0,0 +1,103 @@ +/** + * Babel plugin to strip debug logger calls in production builds + * + * This plugin safely removes: + * - Imports from '@/utils/debug' + * - Variables initialized with useDebugLogger() or createDebugLogger() + * - All calls to those debug logger variables (regardless of variable name) + * + * Uses proper scope tracking and binding management to avoid removing unrelated code. + */ +module.exports = function ({ types: t }) { + return { + name: 'strip-debug-calls', + visitor: { + Program: { + exit(path, state) { + // Collect debug logger bindings + const debugLoggerBindings = new Set(); + const debugImportNames = new Set(); + + // Find all imports from @/utils/debug + path.traverse({ + ImportDeclaration(importPath) { + const source = importPath.node.source.value; + if (source !== '@/utils/debug') return; + + // Track imported function names + importPath.node.specifiers.forEach(specifier => { + if (t.isImportSpecifier(specifier) || t.isImportDefaultSpecifier(specifier)) { + debugImportNames.add(specifier.local.name); + } + }); + } + }); + + // Find all variable declarations that use the imported debug functions + path.traverse({ + VariableDeclarator(declaratorPath) { + const init = declaratorPath.node.init; + const id = declaratorPath.node.id; + + if (!init || !t.isIdentifier(id)) return; + + // Check if it's a call to useDebugLogger or createDebugLogger + if ( + t.isCallExpression(init) && + t.isIdentifier(init.callee) && + debugImportNames.has(init.callee.name) + ) { + // Get the binding for this debug logger variable + const binding = declaratorPath.scope.getBinding(id.name); + if (binding) { + debugLoggerBindings.add(binding); + } + } + } + }); + + // Remove all references to debug loggers (calls to them) + debugLoggerBindings.forEach(binding => { + binding.referencePaths.forEach(refPath => { + // Only remove if it's being called + const parent = refPath.parent; + if (t.isCallExpression(parent) && parent.callee === refPath.node) { + const statement = refPath.getStatementParent(); + if (statement) { + statement.remove(); + } + } + }); + }); + + // Remove the variable declarations for debug loggers + debugLoggerBindings.forEach(binding => { + const declarator = binding.path; + if (declarator && t.isVariableDeclarator(declarator.node)) { + const declaration = declarator.parentPath; + if ( + declaration && + t.isVariableDeclaration(declaration.node) && + declaration.node.declarations.length === 1 + ) { + declaration.remove(); + } else { + declarator.remove(); + } + } + }); + + // Remove imports from @/utils/debug + path.traverse({ + ImportDeclaration(importPath) { + const source = importPath.node.source.value; + if (source === '@/utils/debug') { + importPath.remove(); + } + } + }); + }, + }, + }, + }; +};