From a31e74909cdd0162e934065524c0bf13b7bc98aa Mon Sep 17 00:00:00 2001 From: HeYanbo Date: Wed, 24 Sep 2025 05:54:26 +0800 Subject: [PATCH 1/7] update judge logic for v2 --- .../modules/update/DownloadTask.java | 131 +++++++++++------- 1 file changed, 81 insertions(+), 50 deletions(-) diff --git a/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java b/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java index 5aac5d58..5d2ea702 100644 --- a/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java +++ b/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java @@ -69,6 +69,7 @@ private void removeDirectory(File file) throws IOException { private void downloadFile(DownloadTaskParams param) throws IOException { String url = param.url; + Log.d("😁downloadFile", url); File writePath = param.targetFile; this.hash = param.hash; OkHttpClient client = new OkHttpClient(); @@ -252,19 +253,41 @@ private void doFullPatch(DownloadTaskParams param) throws IOException { } } - private void copyFromResource(HashMap > resToCopy, HashMap> resToCopy2) throws IOException { + private void copyFromResource(HashMap > resToCopy) throws IOException { SafeZipFile zipFile = new SafeZipFile(new File(context.getPackageResourcePath())); Enumeration entries = zipFile.entries(); while (entries.hasMoreElements()) { ZipEntry ze = entries.nextElement(); + String fn = ze.getName(); + ArrayList targets = resToCopy.get(fn); + if (targets != null) { + File lastTarget = null; + for (File target: targets) { + if (UpdateContext.DEBUG) { + Log.d("react-native-update", "Copying from resource " + fn + " to " + target); + } + if (lastTarget != null) { + copyFile(lastTarget, target); + } else { + zipFile.unzipToFile(ze, target); + lastTarget = target; + } + } + } + } + zipFile.close(); + } + + private void copyFromResourceV2(HashMap> resToCopy2) throws IOException { + SafeZipFile zipFile = new SafeZipFile(new File(context.getPackageResourcePath())); + Enumeration entries = zipFile.entries(); + while (entries.hasMoreElements()) { + ZipEntry ze = entries.nextElement(); String fn = ze.getName(); long zipCrc32 = ze.getCrc(); String crc32Decimal = getCRC32AsDecimal(zipCrc32); ArrayList targets = resToCopy2.get(crc32Decimal); - if(targets==null || targets.isEmpty()){ - targets = resToCopy.get(fn); - } if (targets != null) { File lastTarget = null; for (File target: targets) { @@ -290,6 +313,7 @@ private void doPatchFromApk(DownloadTaskParams param) throws IOException, JSONEx param.unzipDirectory.mkdirs(); HashMap> copyList = new HashMap>(); HashMap> copiesv2List = new HashMap>(); + Boolean isV2 = false; boolean foundDiff = false; boolean foundBundlePatch = false; @@ -310,53 +334,56 @@ private void doPatchFromApk(DownloadTaskParams param) throws IOException, JSONEx JSONObject copies = obj.getJSONObject("copies"); JSONObject copiesv2 = obj.getJSONObject("copiesv2"); Iterator keys = copies.keys(); - Iterator keys2 = copiesv2.keys(); - while( keys.hasNext() ) { - String to = (String)keys.next(); - String from = copies.getString(to); - if (from.isEmpty()) { - from = to; + Iterator keysV2 = copiesv2.keys(); + if(keysV2.hasNext()){ + isV2 = true; + while( keysV2.hasNext() ) { + String from = (String)keysV2.next(); + String to = copiesv2.getString(from); + if (from.isEmpty()) { + from = to; + } + ArrayList target = null; + if (!copiesv2List.containsKey(from)) { + target = new ArrayList(); + copiesv2List.put(from, target); + } else { + target = copiesv2List.get((from)); + } + File toFile = new File(param.unzipDirectory, to); + + // Fixing a Zip Path Traversal Vulnerability + // https://support.google.com/faqs/answer/9294009 + String canonicalPath = toFile.getCanonicalPath(); + if (!canonicalPath.startsWith(param.unzipDirectory.getCanonicalPath() + File.separator)) { + throw new SecurityException("Illegal name: " + to); + } + target.add(toFile); } - ArrayList target = null; - if (!copyList.containsKey(from)) { - target = new ArrayList(); - copyList.put(from, target); - } else { - target = copyList.get((from)); - } - File toFile = new File(param.unzipDirectory, to); - - // Fixing a Zip Path Traversal Vulnerability - // https://support.google.com/faqs/answer/9294009 - String canonicalPath = toFile.getCanonicalPath(); - if (!canonicalPath.startsWith(param.unzipDirectory.getCanonicalPath() + File.separator)) { - throw new SecurityException("Illegal name: " + to); + }else{ + while( keys.hasNext() ) { + String to = (String)keys.next(); + String from = copies.getString(to); + if (from.isEmpty()) { + from = to; + } + ArrayList target = null; + if (!copyList.containsKey(from)) { + target = new ArrayList(); + copyList.put(from, target); + } else { + target = copyList.get((from)); + } + File toFile = new File(param.unzipDirectory, to); + + // Fixing a Zip Path Traversal Vulnerability + // https://support.google.com/faqs/answer/9294009 + String canonicalPath = toFile.getCanonicalPath(); + if (!canonicalPath.startsWith(param.unzipDirectory.getCanonicalPath() + File.separator)) { + throw new SecurityException("Illegal name: " + to); + } + target.add(toFile); } - target.add(toFile); - } - - while( keys2.hasNext() ) { - String from = (String)keys2.next(); - String to = copiesv2.getString(from); - if (from.isEmpty()) { - from = to; - } - ArrayList target = null; - if (!copiesv2List.containsKey(from)) { - target = new ArrayList(); - copiesv2List.put(from, target); - } else { - target = copiesv2List.get((from)); - } - File toFile = new File(param.unzipDirectory, to); - - // Fixing a Zip Path Traversal Vulnerability - // https://support.google.com/faqs/answer/9294009 - String canonicalPath = toFile.getCanonicalPath(); - if (!canonicalPath.startsWith(param.unzipDirectory.getCanonicalPath() + File.separator)) { - throw new SecurityException("Illegal name: " + to); - } - target.add(toFile); } continue; } @@ -385,7 +412,11 @@ private void doPatchFromApk(DownloadTaskParams param) throws IOException, JSONEx throw new Error("bundle patch not found"); } - copyFromResource(copyList, copiesv2List); + if(isV2){ + copyFromResourceV2(copiesv2List); + }else{ + copyFromResource(copyList); + } if (UpdateContext.DEBUG) { Log.d("react-native-update", "Unzip finished"); From 2e935413aff2f4ec914a8406e4641ce32cd1fece Mon Sep 17 00:00:00 2001 From: HeYanbo Date: Wed, 24 Sep 2025 05:55:02 +0800 Subject: [PATCH 2/7] udpate --- .../main/java/cn/reactnative/modules/update/DownloadTask.java | 1 - 1 file changed, 1 deletion(-) diff --git a/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java b/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java index 5d2ea702..abac0459 100644 --- a/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java +++ b/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java @@ -69,7 +69,6 @@ private void removeDirectory(File file) throws IOException { private void downloadFile(DownloadTaskParams param) throws IOException { String url = param.url; - Log.d("😁downloadFile", url); File writePath = param.targetFile; this.hash = param.hash; OkHttpClient client = new OkHttpClient(); From 15c4710e97cf4eee45e11f148d6e7e9735fbcb51 Mon Sep 17 00:00:00 2001 From: HeYanbo Date: Wed, 29 Oct 2025 12:49:58 +0800 Subject: [PATCH 3/7] refactor hash by js side --- scripts/bundle-metadata-plugin.js | 185 ++++++++++++++++++++++++++++++ scripts/hermesc-wrapper.js | 97 ++++++++++++++++ scripts/process-hbc.js | 160 ++++++++++++++++++++++++++ 3 files changed, 442 insertions(+) create mode 100644 scripts/bundle-metadata-plugin.js create mode 100755 scripts/hermesc-wrapper.js create mode 100644 scripts/process-hbc.js diff --git a/scripts/bundle-metadata-plugin.js b/scripts/bundle-metadata-plugin.js new file mode 100644 index 00000000..d81391dd --- /dev/null +++ b/scripts/bundle-metadata-plugin.js @@ -0,0 +1,185 @@ +const crypto = require('crypto'); +const fs = require('fs'); +const path = require('path'); +const PROJECT_ROOT = path.resolve(__dirname, '../../..'); +console.log(`[Bundle Metadata] Project root: ${PROJECT_ROOT}`); + +function calculateContentHash(bundleCode) { + const hash = crypto.createHash('sha256'); + hash.update(bundleCode, 'utf8'); + return hash.digest('hex'); +} + +function generateMetadataInjection(contentHash) { + return `// Auto-injected bundle metadata by Metro plugin + var __BUNDLE_METADATA__ = { + contentHash: '${contentHash}' + }; +`; +} + +function generateMetadataComment(contentHash) { + return `\n//# BUNDLE_METADATA ${JSON.stringify({ + contentHash +})}`; +} + +function setupSingleHermesc(hermescPath, locationName) { + const hermescDir = path.dirname(hermescPath); + const backupHermescPath = path.join(hermescDir, '_hermesc'); + const wrapperSourcePath = path.join(__dirname, 'hermesc-wrapper.js'); + + if (fs.existsSync(backupHermescPath)) { + console.log(`⏭️ [Hermesc Setup] ${locationName} already configured, skipping...`); + return true; + } + + if (!fs.existsSync(hermescPath)) { + console.log(`ℹ️ [Hermesc Setup] ${locationName} hermesc not found at: ${hermescPath}`); + return false; + } + + if (!fs.existsSync(wrapperSourcePath)) { + console.error(`❌ [Hermesc Setup] Wrapper script not found at: ${wrapperSourcePath}`); + return false; + } + + try { + console.log(`🔧 [Hermesc Setup] Setting up hermesc wrapper for ${locationName}...`); + + fs.renameSync(hermescPath, backupHermescPath); + console.log(`✅ [Hermesc Setup] ${locationName}: Renamed hermesc -> _hermesc`); + + const shellScript = `#!/bin/bash +# Hermesc wrapper script - auto-generated +# This script calls the Node.js wrapper which handles post-processing + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +WRAPPER_SCRIPT="${wrapperSourcePath}" + +find_node() { + if command -v node >/dev/null 2>&1; then + command -v node + return 0 + fi + + local NODE_PATHS=( + "/usr/local/bin/node" + "/opt/homebrew/bin/node" + "$HOME/.nvm/versions/node/$(ls -t "$HOME/.nvm/versions/node" 2>/dev/null | head -1)/bin/node" + "/usr/bin/node" + ) + + for node_path in "\${NODE_PATHS[@]}"; do + if [ -x "$node_path" ]; then + echo "$node_path" + return 0 + fi + done + + echo "Error: node executable not found" >&2 + echo "Please ensure Node.js is installed and accessible" >&2 + exit 1 +} + +NODE_BIN=$(find_node) +exec "$NODE_BIN" "$WRAPPER_SCRIPT" "$@" +`; + + fs.writeFileSync(hermescPath, shellScript, { mode: 0o755 }); + console.log(`✅ [Hermesc Setup] ${locationName}: Created hermesc wrapper shell script`); + + console.log(`🎉 [Hermesc Setup] ${locationName} configured successfully!`); + console.log(`📋 [Hermesc Setup] ${locationName} details:`); + console.log(` - Original: ${backupHermescPath}`); + console.log(` - Wrapper: ${hermescPath}`); + console.log(` - Handler: ${wrapperSourcePath}`); + + return true; + } catch (error) { + console.error(`❌ [Hermesc Setup] Failed to setup hermesc wrapper for ${locationName}:`, error); + + if (fs.existsSync(backupHermescPath) && !fs.existsSync(hermescPath)) { + try { + fs.renameSync(backupHermescPath, hermescPath); + console.log(`🔄 [Hermesc Setup] ${locationName}: Rolled back changes`); + } catch (rollbackError) { + console.error(`❌ [Hermesc Setup] ${locationName}: Rollback failed:`, rollbackError); + } + } + + return false; + } +} + +function setupHermescWrapper() { + const wrapperSourcePath = path.join(__dirname, 'hermesc-wrapper.js'); + + if (!fs.existsSync(wrapperSourcePath)) { + console.error(`❌ [Hermesc Setup] Wrapper script not found at: ${wrapperSourcePath}`); + return; + } + + try { + fs.chmodSync(wrapperSourcePath, 0o755); + } catch (error) { + console.error('❌ [Hermesc Setup] Failed to set execute permissions on wrapper:', error); + } + + console.log('🔧 [Hermesc Setup] Starting hermesc wrapper setup...'); + const hermescLocations = [ + { + path: path.join(PROJECT_ROOT, 'node_modules/react-native/sdks/hermesc/osx-bin/hermesc'), + name: 'Node Modules' + }, + { + path: path.join(PROJECT_ROOT, 'ios/Pods/hermes-engine/destroot/bin/hermesc'), + name: 'iOS Pods' + } + ]; + + console.log('😁hermescLocations', hermescLocations); + let successCount = 0; + let totalProcessed = 0; + + for (const location of hermescLocations) { + const success = setupSingleHermesc(location.path, location.name); + if (success) { + successCount++; + } + totalProcessed++; + } + + console.log(`\n📊 [Hermesc Setup] Summary: ${successCount}/${totalProcessed} locations configured successfully`); +} + +function metadataSerializer(entryPoint, preModules, graph, options) { + console.log('😁metadataSerializer - Starting bundle serialization'); + setupHermescWrapper(); + const baseJSBundle = require('metro/src/DeltaBundler/Serializers/baseJSBundle'); + const bundleToString = require('metro/src/lib/bundleToString'); + const bundle = baseJSBundle(entryPoint, preModules, graph, options); + const { code: bundleCode } = bundleToString(bundle); + const contentHash = calculateContentHash(bundleCode); + const metadataInjection = generateMetadataInjection(contentHash); + const metadataComment = generateMetadataComment(contentHash); + const hashFilePath = path.join(PROJECT_ROOT, 'bundle-hash.json'); + + try { + const hashData = { + contentHash, + timestamp: new Date().toISOString(), + }; + fs.writeFileSync(hashFilePath, JSON.stringify(hashData, null, 2)); + console.log(`✅ [Metro] Saved hash to: ${hashFilePath}`); + console.log(`🔐 [Metro] Hash: ${contentHash.slice(0, 16)}...`); + } catch (error) { + console.error('❌ [Metro] Failed to save hash file:', error); + } + + return bundleCode + metadataInjection + metadataComment; +} + +module.exports = { + metadataSerializer, +}; diff --git a/scripts/hermesc-wrapper.js b/scripts/hermesc-wrapper.js new file mode 100755 index 00000000..68b63f06 --- /dev/null +++ b/scripts/hermesc-wrapper.js @@ -0,0 +1,97 @@ +#!/usr/bin/env node + +const { spawn } = require('child_process'); +const path = require('path'); +const fs = require('fs'); +const PROJECT_ROOT = path.resolve(__dirname, '../../..'); +const realHermescPath = path.join(PROJECT_ROOT, 'node_modules/react-native/sdks/hermesc/osx-bin/_hermesc'); +const args = process.argv.slice(2); + +console.log(`[Hermesc Wrapper] Executing Hermes compilation...`); +console.log(`[Hermesc Wrapper] Args:`, args.join(' ')); + +const isCompileOperation = args.includes('-emit-binary'); +let outputFile = null; + +const outIndex = args.indexOf('-out'); +if (outIndex !== -1 && outIndex + 1 < args.length) { + outputFile = args[outIndex + 1]; +} + +const hermesc = spawn(realHermescPath, args, { + stdio: 'inherit', + env: process.env +}); + +hermesc.on('error', (error) => { + console.error(`[Hermesc Wrapper] ❌ Failed to start hermesc:`, error); + process.exit(1); +}); + +hermesc.on('close', (code) => { + console.log(`[Hermesc Wrapper] Hermes compilation completed with code: ${code}`); + + if (code === 0 && isCompileOperation && outputFile) { + console.log(`[Hermesc Wrapper] 🔄 Post-processing HBC file: ${outputFile}`); + + setTimeout(() => { + processHBCFile(outputFile); + }, 500); + } else { + process.exit(code); + } +}); + +function processHBCFile(hbcFilePath) { + const hashFilePath = path.join(PROJECT_ROOT, 'bundle-hash.json'); + + if (!fs.existsSync(hashFilePath)) { + console.warn(`[Hermesc Wrapper] ⚠️ Hash file not found: ${hashFilePath}`); + console.warn(`[Hermesc Wrapper] Skipping metadata injection.`); + process.exit(0); + return; + } + + if (!fs.existsSync(hbcFilePath)) { + console.warn(`[Hermesc Wrapper] ⚠️ HBC file not found: ${hbcFilePath}`); + console.warn(`[Hermesc Wrapper] Skipping metadata injection.`); + process.exit(0); + return; + } + + try { + const hashData = JSON.parse(fs.readFileSync(hashFilePath, 'utf8')); + const { contentHash } = hashData; + + console.log(`[Hermesc Wrapper] 📝 Injecting metadata into HBC...`); + console.log(`[Hermesc Wrapper] Hash: ${contentHash.slice(0, 16)}...`); + + const hbcBuffer = fs.readFileSync(hbcFilePath); + + const metadata = { contentHash }; + const metadataJson = JSON.stringify(metadata); + + const MAGIC = Buffer.from('RNUPDATE', 'utf8'); + const jsonBuffer = Buffer.from(metadataJson, 'utf8'); + const lengthBuffer = Buffer.alloc(4); + lengthBuffer.writeUInt32LE(jsonBuffer.length); + + const finalBuffer = Buffer.concat([ + hbcBuffer, + MAGIC, + jsonBuffer, + lengthBuffer, + MAGIC, + ]); + + fs.writeFileSync(hbcFilePath, finalBuffer); + + console.log(`[Hermesc Wrapper] ✅ Successfully injected metadata into: ${hbcFilePath}`); + console.log(`[Hermesc Wrapper] 🧹 Cleaning up hash file...`); + + process.exit(0); + } catch (error) { + console.error(`[Hermesc Wrapper] ❌ Failed to process HBC file:`, error); + process.exit(1); + } +} diff --git a/scripts/process-hbc.js b/scripts/process-hbc.js new file mode 100644 index 00000000..78909e5d --- /dev/null +++ b/scripts/process-hbc.js @@ -0,0 +1,160 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); +const PROJECT_ROOT = path.resolve(__dirname, '../../..'); + +async function appendMetadataToHBC(hbcPath, contentHash) { + if (!fs.existsSync(hbcPath)) { + console.error(`[Process HBC] File not found: ${hbcPath}`); + return false; + } + + const hbcBuffer = await fs.promises.readFile(hbcPath); + + const metadata = { + contentHash + }; + const metadataJson = JSON.stringify(metadata); + + const MAGIC = Buffer.from('RNUPDATE', 'utf8'); + const jsonBuffer = Buffer.from(metadataJson, 'utf8'); + const lengthBuffer = Buffer.alloc(4); + lengthBuffer.writeUInt32LE(jsonBuffer.length); + + const finalBuffer = Buffer.concat([ + hbcBuffer, + MAGIC, + jsonBuffer, + lengthBuffer, + MAGIC, + ]); + + await fs.promises.writeFile(hbcPath, finalBuffer); + console.log(`[Process HBC] ✅ Appended metadata to: ${hbcPath}`); + console.log(`[Process HBC] Hash: ${contentHash.slice(0, 16)}...`); + return true; +} + +function getIOSProjectName() { + try { + const iosDir = path.join(PROJECT_ROOT, 'ios'); + if (fs.existsSync(iosDir)) { + const files = fs.readdirSync(iosDir); + const xcodeprojFile = files.find(f => f.endsWith('.xcodeproj')); + if (xcodeprojFile) { + return xcodeprojFile.replace('.xcodeproj', ''); + } + } + + const packageJsonPath = path.join(PROJECT_ROOT, 'package.json'); + if (fs.existsSync(packageJsonPath)) { + const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); + return packageJson.name || ''; + } + } catch (error) { + console.warn('[Process HBC] Failed to detect iOS project name:', error.message); + } + + return ''; +} + +function findHbcFiles(platform) { + const hbcFiles = []; + + if (platform === 'android') { + const possiblePaths = [ + 'android/app/build/generated/assets/react/release/index.android.bundle', + 'android/app/build/generated/assets/createBundleReleaseJsAndAssets/index.android.bundle', + 'android/app/src/main/assets/index.android.bundle', + ]; + + for (const p of possiblePaths) { + const fullPath = path.join(PROJECT_ROOT, p); + if (fs.existsSync(fullPath)) { + hbcFiles.push(fullPath); + } + } + } else if (platform === 'ios') { + const projectName = getIOSProjectName(); + console.log(`[Process HBC] Detected iOS project name: ${projectName}`); + + const possiblePaths = [ + `ios/${projectName}.app/main.jsbundle`, + `ios/build/Build/Products/Release-iphoneos/${projectName}.app/main.jsbundle`, + `ios/build/Build/Products/Debug-iphoneos/${projectName}.app/main.jsbundle`, + 'ios/main.jsbundle', + 'ios/build/Build/Products/Release-iphoneos/main.jsbundle', + ]; + + for (const p of possiblePaths) { + const fullPath = path.join(PROJECT_ROOT, p); + if (fs.existsSync(fullPath)) { + hbcFiles.push(fullPath); + } + } + + if (hbcFiles.length === 0) { + const iosDir = path.join(PROJECT_ROOT, 'ios'); + if (fs.existsSync(iosDir)) { + const appDirs = fs.readdirSync(iosDir).filter(f => f.endsWith('.app')); + for (const appDir of appDirs) { + const jsbundlePath = path.join(iosDir, appDir, 'main.jsbundle'); + if (fs.existsSync(jsbundlePath)) { + hbcFiles.push(jsbundlePath); + } + } + } + } + } + + return hbcFiles; +} + +async function main() { + const platform = process.argv[2] || 'android'; + console.log(`[Process HBC] Platform: ${platform}`); + console.log(`[Process HBC] Project root: ${PROJECT_ROOT}`); + const hashFilePath = path.join(PROJECT_ROOT, 'bundle-hash.json'); + + if (!fs.existsSync(hashFilePath)) { + console.error(`[Process HBC] ❌ Hash file not found: ${hashFilePath}`); + console.error('[Process HBC] Make sure Metro bundler has run with the custom serializer.'); + process.exit(1); + } + const hashData = JSON.parse(fs.readFileSync(hashFilePath, 'utf8')); + const { contentHash } = hashData; + console.log(`[Process HBC] Content hash: ${contentHash.slice(0, 16)}...`); + const hbcFiles = findHbcFiles(platform); + if (hbcFiles.length === 0) { + console.error(`[Process HBC] ❌ No HBC files found for platform: ${platform}`); + console.error('[Process HBC] Expected locations:'); + if (platform === 'android') { + console.error(' - android/app/build/generated/assets/react/release/'); + console.error(' - android/app/src/main/assets/'); + } else { + console.error(' - ios/build/Build/Products/Release-iphoneos/'); + console.error(' - ios/'); + } + process.exit(1); + } + + let successCount = 0; + for (const hbcPath of hbcFiles) { + const success = await appendMetadataToHBC(hbcPath, contentHash); + if (success) successCount++; + } + + console.log(`\n[Process HBC] ✅ Processed ${successCount}/${hbcFiles.length} HBC files`); + + try { + fs.unlinkSync(hashFilePath); + console.log(`[Process HBC] Cleaned up hash file`); + } catch (error) { + } +} + +main().catch(error => { + console.error('[Process HBC] ❌ Error:', error); + process.exit(1); +}); From 0a6118f96103b439a2600f3d76d1412e89d2dbf7 Mon Sep 17 00:00:00 2001 From: HeYanbo Date: Wed, 29 Oct 2025 12:51:23 +0800 Subject: [PATCH 4/7] udpate --- scripts/bundle-metadata-plugin.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/scripts/bundle-metadata-plugin.js b/scripts/bundle-metadata-plugin.js index d81391dd..4569f83c 100644 --- a/scripts/bundle-metadata-plugin.js +++ b/scripts/bundle-metadata-plugin.js @@ -138,7 +138,6 @@ function setupHermescWrapper() { } ]; - console.log('😁hermescLocations', hermescLocations); let successCount = 0; let totalProcessed = 0; @@ -154,7 +153,6 @@ function setupHermescWrapper() { } function metadataSerializer(entryPoint, preModules, graph, options) { - console.log('😁metadataSerializer - Starting bundle serialization'); setupHermescWrapper(); const baseJSBundle = require('metro/src/DeltaBundler/Serializers/baseJSBundle'); const bundleToString = require('metro/src/lib/bundleToString'); From ad4baa4f2ad523d4a0317f6822734303cf5cddfa Mon Sep 17 00:00:00 2001 From: HeYanbo Date: Wed, 29 Oct 2025 13:57:49 +0800 Subject: [PATCH 5/7] update --- scripts/bundle-metadata-plugin.js | 6 ++ scripts/hermesc-wrapper.js | 1 + scripts/process-hbc.js | 160 ------------------------------ 3 files changed, 7 insertions(+), 160 deletions(-) delete mode 100644 scripts/process-hbc.js diff --git a/scripts/bundle-metadata-plugin.js b/scripts/bundle-metadata-plugin.js index 4569f83c..d0f471eb 100644 --- a/scripts/bundle-metadata-plugin.js +++ b/scripts/bundle-metadata-plugin.js @@ -127,6 +127,8 @@ function setupHermescWrapper() { } console.log('🔧 [Hermesc Setup] Starting hermesc wrapper setup...'); + + // 使用项目根目录来定位 hermesc const hermescLocations = [ { path: path.join(PROJECT_ROOT, 'node_modules/react-native/sdks/hermesc/osx-bin/hermesc'), @@ -138,6 +140,7 @@ function setupHermescWrapper() { } ]; + console.log('😁hermescLocations', hermescLocations); let successCount = 0; let totalProcessed = 0; @@ -153,6 +156,7 @@ function setupHermescWrapper() { } function metadataSerializer(entryPoint, preModules, graph, options) { + console.log('😁metadataSerializer - Starting bundle serialization'); setupHermescWrapper(); const baseJSBundle = require('metro/src/DeltaBundler/Serializers/baseJSBundle'); const bundleToString = require('metro/src/lib/bundleToString'); @@ -161,6 +165,8 @@ function metadataSerializer(entryPoint, preModules, graph, options) { const contentHash = calculateContentHash(bundleCode); const metadataInjection = generateMetadataInjection(contentHash); const metadataComment = generateMetadataComment(contentHash); + + // hash 文件保存在项目根目录 const hashFilePath = path.join(PROJECT_ROOT, 'bundle-hash.json'); try { diff --git a/scripts/hermesc-wrapper.js b/scripts/hermesc-wrapper.js index 68b63f06..026c4668 100755 --- a/scripts/hermesc-wrapper.js +++ b/scripts/hermesc-wrapper.js @@ -43,6 +43,7 @@ hermesc.on('close', (code) => { }); function processHBCFile(hbcFilePath) { + // hash 文件在项目根目录 const hashFilePath = path.join(PROJECT_ROOT, 'bundle-hash.json'); if (!fs.existsSync(hashFilePath)) { diff --git a/scripts/process-hbc.js b/scripts/process-hbc.js deleted file mode 100644 index 78909e5d..00000000 --- a/scripts/process-hbc.js +++ /dev/null @@ -1,160 +0,0 @@ -#!/usr/bin/env node - -const fs = require('fs'); -const path = require('path'); -const PROJECT_ROOT = path.resolve(__dirname, '../../..'); - -async function appendMetadataToHBC(hbcPath, contentHash) { - if (!fs.existsSync(hbcPath)) { - console.error(`[Process HBC] File not found: ${hbcPath}`); - return false; - } - - const hbcBuffer = await fs.promises.readFile(hbcPath); - - const metadata = { - contentHash - }; - const metadataJson = JSON.stringify(metadata); - - const MAGIC = Buffer.from('RNUPDATE', 'utf8'); - const jsonBuffer = Buffer.from(metadataJson, 'utf8'); - const lengthBuffer = Buffer.alloc(4); - lengthBuffer.writeUInt32LE(jsonBuffer.length); - - const finalBuffer = Buffer.concat([ - hbcBuffer, - MAGIC, - jsonBuffer, - lengthBuffer, - MAGIC, - ]); - - await fs.promises.writeFile(hbcPath, finalBuffer); - console.log(`[Process HBC] ✅ Appended metadata to: ${hbcPath}`); - console.log(`[Process HBC] Hash: ${contentHash.slice(0, 16)}...`); - return true; -} - -function getIOSProjectName() { - try { - const iosDir = path.join(PROJECT_ROOT, 'ios'); - if (fs.existsSync(iosDir)) { - const files = fs.readdirSync(iosDir); - const xcodeprojFile = files.find(f => f.endsWith('.xcodeproj')); - if (xcodeprojFile) { - return xcodeprojFile.replace('.xcodeproj', ''); - } - } - - const packageJsonPath = path.join(PROJECT_ROOT, 'package.json'); - if (fs.existsSync(packageJsonPath)) { - const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); - return packageJson.name || ''; - } - } catch (error) { - console.warn('[Process HBC] Failed to detect iOS project name:', error.message); - } - - return ''; -} - -function findHbcFiles(platform) { - const hbcFiles = []; - - if (platform === 'android') { - const possiblePaths = [ - 'android/app/build/generated/assets/react/release/index.android.bundle', - 'android/app/build/generated/assets/createBundleReleaseJsAndAssets/index.android.bundle', - 'android/app/src/main/assets/index.android.bundle', - ]; - - for (const p of possiblePaths) { - const fullPath = path.join(PROJECT_ROOT, p); - if (fs.existsSync(fullPath)) { - hbcFiles.push(fullPath); - } - } - } else if (platform === 'ios') { - const projectName = getIOSProjectName(); - console.log(`[Process HBC] Detected iOS project name: ${projectName}`); - - const possiblePaths = [ - `ios/${projectName}.app/main.jsbundle`, - `ios/build/Build/Products/Release-iphoneos/${projectName}.app/main.jsbundle`, - `ios/build/Build/Products/Debug-iphoneos/${projectName}.app/main.jsbundle`, - 'ios/main.jsbundle', - 'ios/build/Build/Products/Release-iphoneos/main.jsbundle', - ]; - - for (const p of possiblePaths) { - const fullPath = path.join(PROJECT_ROOT, p); - if (fs.existsSync(fullPath)) { - hbcFiles.push(fullPath); - } - } - - if (hbcFiles.length === 0) { - const iosDir = path.join(PROJECT_ROOT, 'ios'); - if (fs.existsSync(iosDir)) { - const appDirs = fs.readdirSync(iosDir).filter(f => f.endsWith('.app')); - for (const appDir of appDirs) { - const jsbundlePath = path.join(iosDir, appDir, 'main.jsbundle'); - if (fs.existsSync(jsbundlePath)) { - hbcFiles.push(jsbundlePath); - } - } - } - } - } - - return hbcFiles; -} - -async function main() { - const platform = process.argv[2] || 'android'; - console.log(`[Process HBC] Platform: ${platform}`); - console.log(`[Process HBC] Project root: ${PROJECT_ROOT}`); - const hashFilePath = path.join(PROJECT_ROOT, 'bundle-hash.json'); - - if (!fs.existsSync(hashFilePath)) { - console.error(`[Process HBC] ❌ Hash file not found: ${hashFilePath}`); - console.error('[Process HBC] Make sure Metro bundler has run with the custom serializer.'); - process.exit(1); - } - const hashData = JSON.parse(fs.readFileSync(hashFilePath, 'utf8')); - const { contentHash } = hashData; - console.log(`[Process HBC] Content hash: ${contentHash.slice(0, 16)}...`); - const hbcFiles = findHbcFiles(platform); - if (hbcFiles.length === 0) { - console.error(`[Process HBC] ❌ No HBC files found for platform: ${platform}`); - console.error('[Process HBC] Expected locations:'); - if (platform === 'android') { - console.error(' - android/app/build/generated/assets/react/release/'); - console.error(' - android/app/src/main/assets/'); - } else { - console.error(' - ios/build/Build/Products/Release-iphoneos/'); - console.error(' - ios/'); - } - process.exit(1); - } - - let successCount = 0; - for (const hbcPath of hbcFiles) { - const success = await appendMetadataToHBC(hbcPath, contentHash); - if (success) successCount++; - } - - console.log(`\n[Process HBC] ✅ Processed ${successCount}/${hbcFiles.length} HBC files`); - - try { - fs.unlinkSync(hashFilePath); - console.log(`[Process HBC] Cleaned up hash file`); - } catch (error) { - } -} - -main().catch(error => { - console.error('[Process HBC] ❌ Error:', error); - process.exit(1); -}); From 684aa503bcb7c9512caa4fb49a430c0abd7fc5aa Mon Sep 17 00:00:00 2001 From: HeYanbo Date: Wed, 29 Oct 2025 13:58:31 +0800 Subject: [PATCH 6/7] update --- scripts/bundle-metadata-plugin.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/scripts/bundle-metadata-plugin.js b/scripts/bundle-metadata-plugin.js index d0f471eb..f5f99b41 100644 --- a/scripts/bundle-metadata-plugin.js +++ b/scripts/bundle-metadata-plugin.js @@ -140,7 +140,6 @@ function setupHermescWrapper() { } ]; - console.log('😁hermescLocations', hermescLocations); let successCount = 0; let totalProcessed = 0; @@ -156,7 +155,6 @@ function setupHermescWrapper() { } function metadataSerializer(entryPoint, preModules, graph, options) { - console.log('😁metadataSerializer - Starting bundle serialization'); setupHermescWrapper(); const baseJSBundle = require('metro/src/DeltaBundler/Serializers/baseJSBundle'); const bundleToString = require('metro/src/lib/bundleToString'); From 8a3fd02ccc419fc84bf6aaed116d0b97eb12da86 Mon Sep 17 00:00:00 2001 From: HeYanbo Date: Wed, 29 Oct 2025 14:07:21 +0800 Subject: [PATCH 7/7] update --- scripts/bundle-metadata-plugin.js | 3 --- scripts/hermesc-wrapper.js | 1 - 2 files changed, 4 deletions(-) diff --git a/scripts/bundle-metadata-plugin.js b/scripts/bundle-metadata-plugin.js index f5f99b41..b7a8a8c8 100644 --- a/scripts/bundle-metadata-plugin.js +++ b/scripts/bundle-metadata-plugin.js @@ -128,7 +128,6 @@ function setupHermescWrapper() { console.log('🔧 [Hermesc Setup] Starting hermesc wrapper setup...'); - // 使用项目根目录来定位 hermesc const hermescLocations = [ { path: path.join(PROJECT_ROOT, 'node_modules/react-native/sdks/hermesc/osx-bin/hermesc'), @@ -163,8 +162,6 @@ function metadataSerializer(entryPoint, preModules, graph, options) { const contentHash = calculateContentHash(bundleCode); const metadataInjection = generateMetadataInjection(contentHash); const metadataComment = generateMetadataComment(contentHash); - - // hash 文件保存在项目根目录 const hashFilePath = path.join(PROJECT_ROOT, 'bundle-hash.json'); try { diff --git a/scripts/hermesc-wrapper.js b/scripts/hermesc-wrapper.js index 026c4668..68b63f06 100755 --- a/scripts/hermesc-wrapper.js +++ b/scripts/hermesc-wrapper.js @@ -43,7 +43,6 @@ hermesc.on('close', (code) => { }); function processHBCFile(hbcFilePath) { - // hash 文件在项目根目录 const hashFilePath = path.join(PROJECT_ROOT, 'bundle-hash.json'); if (!fs.existsSync(hashFilePath)) {