Skip to content

Commit 15c4710

Browse files
committed
refactor hash by js side
1 parent b5db66c commit 15c4710

File tree

3 files changed

+442
-0
lines changed

3 files changed

+442
-0
lines changed

scripts/bundle-metadata-plugin.js

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
const crypto = require('crypto');
2+
const fs = require('fs');
3+
const path = require('path');
4+
const PROJECT_ROOT = path.resolve(__dirname, '../../..');
5+
console.log(`[Bundle Metadata] Project root: ${PROJECT_ROOT}`);
6+
7+
function calculateContentHash(bundleCode) {
8+
const hash = crypto.createHash('sha256');
9+
hash.update(bundleCode, 'utf8');
10+
return hash.digest('hex');
11+
}
12+
13+
function generateMetadataInjection(contentHash) {
14+
return `// Auto-injected bundle metadata by Metro plugin
15+
var __BUNDLE_METADATA__ = {
16+
contentHash: '${contentHash}'
17+
};
18+
`;
19+
}
20+
21+
function generateMetadataComment(contentHash) {
22+
return `\n//# BUNDLE_METADATA ${JSON.stringify({
23+
contentHash
24+
})}`;
25+
}
26+
27+
function setupSingleHermesc(hermescPath, locationName) {
28+
const hermescDir = path.dirname(hermescPath);
29+
const backupHermescPath = path.join(hermescDir, '_hermesc');
30+
const wrapperSourcePath = path.join(__dirname, 'hermesc-wrapper.js');
31+
32+
if (fs.existsSync(backupHermescPath)) {
33+
console.log(`⏭️ [Hermesc Setup] ${locationName} already configured, skipping...`);
34+
return true;
35+
}
36+
37+
if (!fs.existsSync(hermescPath)) {
38+
console.log(`ℹ️ [Hermesc Setup] ${locationName} hermesc not found at: ${hermescPath}`);
39+
return false;
40+
}
41+
42+
if (!fs.existsSync(wrapperSourcePath)) {
43+
console.error(`❌ [Hermesc Setup] Wrapper script not found at: ${wrapperSourcePath}`);
44+
return false;
45+
}
46+
47+
try {
48+
console.log(`🔧 [Hermesc Setup] Setting up hermesc wrapper for ${locationName}...`);
49+
50+
fs.renameSync(hermescPath, backupHermescPath);
51+
console.log(`✅ [Hermesc Setup] ${locationName}: Renamed hermesc -> _hermesc`);
52+
53+
const shellScript = `#!/bin/bash
54+
# Hermesc wrapper script - auto-generated
55+
# This script calls the Node.js wrapper which handles post-processing
56+
57+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
58+
WRAPPER_SCRIPT="${wrapperSourcePath}"
59+
60+
find_node() {
61+
if command -v node >/dev/null 2>&1; then
62+
command -v node
63+
return 0
64+
fi
65+
66+
local NODE_PATHS=(
67+
"/usr/local/bin/node"
68+
"/opt/homebrew/bin/node"
69+
"$HOME/.nvm/versions/node/$(ls -t "$HOME/.nvm/versions/node" 2>/dev/null | head -1)/bin/node"
70+
"/usr/bin/node"
71+
)
72+
73+
for node_path in "\${NODE_PATHS[@]}"; do
74+
if [ -x "$node_path" ]; then
75+
echo "$node_path"
76+
return 0
77+
fi
78+
done
79+
80+
echo "Error: node executable not found" >&2
81+
echo "Please ensure Node.js is installed and accessible" >&2
82+
exit 1
83+
}
84+
85+
NODE_BIN=$(find_node)
86+
exec "$NODE_BIN" "$WRAPPER_SCRIPT" "$@"
87+
`;
88+
89+
fs.writeFileSync(hermescPath, shellScript, { mode: 0o755 });
90+
console.log(`✅ [Hermesc Setup] ${locationName}: Created hermesc wrapper shell script`);
91+
92+
console.log(`🎉 [Hermesc Setup] ${locationName} configured successfully!`);
93+
console.log(`📋 [Hermesc Setup] ${locationName} details:`);
94+
console.log(` - Original: ${backupHermescPath}`);
95+
console.log(` - Wrapper: ${hermescPath}`);
96+
console.log(` - Handler: ${wrapperSourcePath}`);
97+
98+
return true;
99+
} catch (error) {
100+
console.error(`❌ [Hermesc Setup] Failed to setup hermesc wrapper for ${locationName}:`, error);
101+
102+
if (fs.existsSync(backupHermescPath) && !fs.existsSync(hermescPath)) {
103+
try {
104+
fs.renameSync(backupHermescPath, hermescPath);
105+
console.log(`🔄 [Hermesc Setup] ${locationName}: Rolled back changes`);
106+
} catch (rollbackError) {
107+
console.error(`❌ [Hermesc Setup] ${locationName}: Rollback failed:`, rollbackError);
108+
}
109+
}
110+
111+
return false;
112+
}
113+
}
114+
115+
function setupHermescWrapper() {
116+
const wrapperSourcePath = path.join(__dirname, 'hermesc-wrapper.js');
117+
118+
if (!fs.existsSync(wrapperSourcePath)) {
119+
console.error(`❌ [Hermesc Setup] Wrapper script not found at: ${wrapperSourcePath}`);
120+
return;
121+
}
122+
123+
try {
124+
fs.chmodSync(wrapperSourcePath, 0o755);
125+
} catch (error) {
126+
console.error('❌ [Hermesc Setup] Failed to set execute permissions on wrapper:', error);
127+
}
128+
129+
console.log('🔧 [Hermesc Setup] Starting hermesc wrapper setup...');
130+
const hermescLocations = [
131+
{
132+
path: path.join(PROJECT_ROOT, 'node_modules/react-native/sdks/hermesc/osx-bin/hermesc'),
133+
name: 'Node Modules'
134+
},
135+
{
136+
path: path.join(PROJECT_ROOT, 'ios/Pods/hermes-engine/destroot/bin/hermesc'),
137+
name: 'iOS Pods'
138+
}
139+
];
140+
141+
console.log('😁hermescLocations', hermescLocations);
142+
let successCount = 0;
143+
let totalProcessed = 0;
144+
145+
for (const location of hermescLocations) {
146+
const success = setupSingleHermesc(location.path, location.name);
147+
if (success) {
148+
successCount++;
149+
}
150+
totalProcessed++;
151+
}
152+
153+
console.log(`\n📊 [Hermesc Setup] Summary: ${successCount}/${totalProcessed} locations configured successfully`);
154+
}
155+
156+
function metadataSerializer(entryPoint, preModules, graph, options) {
157+
console.log('😁metadataSerializer - Starting bundle serialization');
158+
setupHermescWrapper();
159+
const baseJSBundle = require('metro/src/DeltaBundler/Serializers/baseJSBundle');
160+
const bundleToString = require('metro/src/lib/bundleToString');
161+
const bundle = baseJSBundle(entryPoint, preModules, graph, options);
162+
const { code: bundleCode } = bundleToString(bundle);
163+
const contentHash = calculateContentHash(bundleCode);
164+
const metadataInjection = generateMetadataInjection(contentHash);
165+
const metadataComment = generateMetadataComment(contentHash);
166+
const hashFilePath = path.join(PROJECT_ROOT, 'bundle-hash.json');
167+
168+
try {
169+
const hashData = {
170+
contentHash,
171+
timestamp: new Date().toISOString(),
172+
};
173+
fs.writeFileSync(hashFilePath, JSON.stringify(hashData, null, 2));
174+
console.log(`✅ [Metro] Saved hash to: ${hashFilePath}`);
175+
console.log(`🔐 [Metro] Hash: ${contentHash.slice(0, 16)}...`);
176+
} catch (error) {
177+
console.error('❌ [Metro] Failed to save hash file:', error);
178+
}
179+
180+
return bundleCode + metadataInjection + metadataComment;
181+
}
182+
183+
module.exports = {
184+
metadataSerializer,
185+
};

scripts/hermesc-wrapper.js

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
#!/usr/bin/env node
2+
3+
const { spawn } = require('child_process');
4+
const path = require('path');
5+
const fs = require('fs');
6+
const PROJECT_ROOT = path.resolve(__dirname, '../../..');
7+
const realHermescPath = path.join(PROJECT_ROOT, 'node_modules/react-native/sdks/hermesc/osx-bin/_hermesc');
8+
const args = process.argv.slice(2);
9+
10+
console.log(`[Hermesc Wrapper] Executing Hermes compilation...`);
11+
console.log(`[Hermesc Wrapper] Args:`, args.join(' '));
12+
13+
const isCompileOperation = args.includes('-emit-binary');
14+
let outputFile = null;
15+
16+
const outIndex = args.indexOf('-out');
17+
if (outIndex !== -1 && outIndex + 1 < args.length) {
18+
outputFile = args[outIndex + 1];
19+
}
20+
21+
const hermesc = spawn(realHermescPath, args, {
22+
stdio: 'inherit',
23+
env: process.env
24+
});
25+
26+
hermesc.on('error', (error) => {
27+
console.error(`[Hermesc Wrapper] ❌ Failed to start hermesc:`, error);
28+
process.exit(1);
29+
});
30+
31+
hermesc.on('close', (code) => {
32+
console.log(`[Hermesc Wrapper] Hermes compilation completed with code: ${code}`);
33+
34+
if (code === 0 && isCompileOperation && outputFile) {
35+
console.log(`[Hermesc Wrapper] 🔄 Post-processing HBC file: ${outputFile}`);
36+
37+
setTimeout(() => {
38+
processHBCFile(outputFile);
39+
}, 500);
40+
} else {
41+
process.exit(code);
42+
}
43+
});
44+
45+
function processHBCFile(hbcFilePath) {
46+
const hashFilePath = path.join(PROJECT_ROOT, 'bundle-hash.json');
47+
48+
if (!fs.existsSync(hashFilePath)) {
49+
console.warn(`[Hermesc Wrapper] ⚠️ Hash file not found: ${hashFilePath}`);
50+
console.warn(`[Hermesc Wrapper] Skipping metadata injection.`);
51+
process.exit(0);
52+
return;
53+
}
54+
55+
if (!fs.existsSync(hbcFilePath)) {
56+
console.warn(`[Hermesc Wrapper] ⚠️ HBC file not found: ${hbcFilePath}`);
57+
console.warn(`[Hermesc Wrapper] Skipping metadata injection.`);
58+
process.exit(0);
59+
return;
60+
}
61+
62+
try {
63+
const hashData = JSON.parse(fs.readFileSync(hashFilePath, 'utf8'));
64+
const { contentHash } = hashData;
65+
66+
console.log(`[Hermesc Wrapper] 📝 Injecting metadata into HBC...`);
67+
console.log(`[Hermesc Wrapper] Hash: ${contentHash.slice(0, 16)}...`);
68+
69+
const hbcBuffer = fs.readFileSync(hbcFilePath);
70+
71+
const metadata = { contentHash };
72+
const metadataJson = JSON.stringify(metadata);
73+
74+
const MAGIC = Buffer.from('RNUPDATE', 'utf8');
75+
const jsonBuffer = Buffer.from(metadataJson, 'utf8');
76+
const lengthBuffer = Buffer.alloc(4);
77+
lengthBuffer.writeUInt32LE(jsonBuffer.length);
78+
79+
const finalBuffer = Buffer.concat([
80+
hbcBuffer,
81+
MAGIC,
82+
jsonBuffer,
83+
lengthBuffer,
84+
MAGIC,
85+
]);
86+
87+
fs.writeFileSync(hbcFilePath, finalBuffer);
88+
89+
console.log(`[Hermesc Wrapper] ✅ Successfully injected metadata into: ${hbcFilePath}`);
90+
console.log(`[Hermesc Wrapper] 🧹 Cleaning up hash file...`);
91+
92+
process.exit(0);
93+
} catch (error) {
94+
console.error(`[Hermesc Wrapper] ❌ Failed to process HBC file:`, error);
95+
process.exit(1);
96+
}
97+
}

0 commit comments

Comments
 (0)