react-native-update 10.37.0 → 10.37.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/android/src/main/java/cn/reactnative/modules/update/UpdateModuleImpl.java +18 -32
- package/android/src/newarch/cn/reactnative/modules/update/UpdateModule.java +5 -5
- package/harmony/pushy/hvigor-plugin.ts +22 -27
- package/package.json +1 -1
- package/scripts/bundle-metadata-plugin.js +184 -0
- package/scripts/hermesc-wrapper.js +97 -0
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
### 优势
|
|
12
12
|
|
|
13
13
|
1. 对中国用户使用阿里云高速 CDN 分发,对比其他服务器在国外的热更新服务,分发更稳定,更新成功率极高。对国外用户则智能分流至 cloudflare,同样享受高速更新服务。
|
|
14
|
-
2. 基于 bsdiff/hdiff
|
|
14
|
+
2. 基于 bsdiff/hdiff 算法创建的**超小更新包**,通常版本迭代后在几十至几百 KB 级别(其他全量热更新服务所需流量通常在几十 MB 级别)。
|
|
15
15
|
3. 始终跟进 RN 最新正式版本,第一时间提供支持。支持 hermes 字节码格式。支持新架构(注:安卓 0.73.0 ~ 0.76.0 的新架构因官方 bug 不支持,0.73 以下或 0.76.1 以上的新架构可用)。
|
|
16
16
|
4. 跨越多个版本进行更新时,只需要下载**一个更新包**,不需要逐版本依次更新。
|
|
17
17
|
5. 命令行工具 & 网页双端管理,版本发布过程简单便捷,完全可以集成 CI。
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
| 对比维度 | react-native-update | expo-update | react-native-code-push |
|
|
25
25
|
|---------|---------------------|-------------|------------------------|
|
|
26
26
|
| **价格/成本** | 提供免费额度,多级梯度付费(最低约 66 元/月),流量不单独计费 | 提供免费额度,多级梯度付费(最低约 136 元/月),超出流量额外计费 | ❌ **已停运**(Microsoft App Center 已于 2025 年 3 月 31 日停止服务) |
|
|
27
|
-
| **更新包大小** | ⭐⭐⭐⭐⭐
|
|
27
|
+
| **更新包大小** | ⭐⭐⭐⭐⭐ 几十至几百 KB(增量更新) | ⭐⭐⭐ 全量更新(通常几十 MB) | ❌ **已停运** |
|
|
28
28
|
| **中国地区访问速度** | ⭐⭐⭐⭐⭐ 阿里云 CDN,速度极快 | ⭐⭐ 国外服务器,可能较慢 | ❌ **已停运** |
|
|
29
29
|
| **iOS 支持** | ✅ 支持 | ✅ 支持 | ❌ **已停运** |
|
|
30
30
|
| **Android 支持** | ✅ 支持 | ✅ 支持 | ❌ **已停运** |
|
|
@@ -134,13 +134,21 @@ public class UpdateModuleImpl {
|
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
-
public static void reloadUpdate(final UpdateContext updateContext,final ReactApplicationContext mContext, final ReadableMap options, final Promise promise) {
|
|
137
|
+
public static void reloadUpdate(final UpdateContext updateContext, final ReactApplicationContext mContext, final ReadableMap options, final Promise promise) {
|
|
138
138
|
final String hash = options.getString("hash");
|
|
139
|
+
restartApp(updateContext, mContext, hash, promise);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
public static void restartApp(final UpdateContext updateContext, final ReactApplicationContext mContext, final String hash, final Promise promise) {
|
|
139
144
|
UiThreadUtil.runOnUiThread(new Runnable() {
|
|
140
145
|
@Override
|
|
141
146
|
public void run() {
|
|
142
|
-
|
|
143
|
-
|
|
147
|
+
// 如果提供了 hash,则切换版本
|
|
148
|
+
if (hash != null && updateContext != null) {
|
|
149
|
+
updateContext.switchVersion(hash);
|
|
150
|
+
}
|
|
151
|
+
|
|
144
152
|
final Context application = mContext.getApplicationContext();
|
|
145
153
|
JSBundleLoader loader = JSBundleLoader.createFileLoader(UpdateContext.getBundleUrl(application));
|
|
146
154
|
try {
|
|
@@ -166,6 +174,7 @@ public class UpdateModuleImpl {
|
|
|
166
174
|
} catch (Throwable err) {
|
|
167
175
|
final Activity currentActivity = mContext.getCurrentActivity();
|
|
168
176
|
if (currentActivity == null) {
|
|
177
|
+
promise.reject(err);
|
|
169
178
|
return;
|
|
170
179
|
}
|
|
171
180
|
try {
|
|
@@ -203,6 +212,7 @@ public class UpdateModuleImpl {
|
|
|
203
212
|
|
|
204
213
|
// Invoke the reload method with a reason
|
|
205
214
|
reloadMethod.invoke(reactHost, "react-native-update");
|
|
215
|
+
promise.resolve(true);
|
|
206
216
|
} catch (Throwable e) {
|
|
207
217
|
currentActivity.runOnUiThread(new Runnable() {
|
|
208
218
|
@Override
|
|
@@ -210,42 +220,17 @@ public class UpdateModuleImpl {
|
|
|
210
220
|
currentActivity.recreate();
|
|
211
221
|
}
|
|
212
222
|
});
|
|
223
|
+
promise.resolve(true);
|
|
213
224
|
}
|
|
214
225
|
}
|
|
215
|
-
promise.resolve(true);
|
|
216
226
|
}
|
|
217
227
|
});
|
|
218
228
|
}
|
|
219
229
|
|
|
230
|
+
|
|
220
231
|
public static void restartApp(final ReactApplicationContext mContext, final Promise promise) {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
public void run() {
|
|
224
|
-
try {
|
|
225
|
-
final Context application = mContext.getApplicationContext();
|
|
226
|
-
ReactInstanceManager instanceManager = ((ReactApplication) application).getReactNativeHost().getReactInstanceManager();
|
|
227
|
-
|
|
228
|
-
instanceManager.recreateReactContextInBackground();
|
|
229
|
-
promise.resolve(true);
|
|
230
|
-
|
|
231
|
-
} catch (Throwable err) {
|
|
232
|
-
promise.reject("restartApp failed: "+err.getMessage());
|
|
233
|
-
Log.e("pushy", "restartApp failed", err);
|
|
234
|
-
|
|
235
|
-
final Activity currentActivity = mContext.getCurrentActivity();
|
|
236
|
-
if (currentActivity == null) {
|
|
237
|
-
return;
|
|
238
|
-
}
|
|
239
|
-
currentActivity.runOnUiThread(new Runnable() {
|
|
240
|
-
@Override
|
|
241
|
-
public void run() {
|
|
242
|
-
currentActivity.recreate();
|
|
243
|
-
}
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
});
|
|
248
|
-
}
|
|
232
|
+
restartApp(null, mContext, null, promise);
|
|
233
|
+
}
|
|
249
234
|
|
|
250
235
|
public static void setNeedUpdate(final UpdateContext updateContext, final ReadableMap options, final Promise promise) {
|
|
251
236
|
final String hash = options.getString("hash");
|
|
@@ -320,3 +305,4 @@ public class UpdateModuleImpl {
|
|
|
320
305
|
}
|
|
321
306
|
|
|
322
307
|
}
|
|
308
|
+
|
|
@@ -104,27 +104,27 @@ public class UpdateModule extends NativePushySpec {
|
|
|
104
104
|
|
|
105
105
|
@Override
|
|
106
106
|
public void downloadPatchFromPpk(ReadableMap options, final Promise promise) {
|
|
107
|
-
UpdateModuleImpl.downloadPatchFromPpk(updateContext,options,promise);
|
|
107
|
+
UpdateModuleImpl.downloadPatchFromPpk(updateContext, options, promise);
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
@Override
|
|
111
111
|
public void reloadUpdate(ReadableMap options,Promise promise) {
|
|
112
|
-
UpdateModuleImpl.reloadUpdate(updateContext, mContext, options,promise);
|
|
112
|
+
UpdateModuleImpl.reloadUpdate(updateContext, mContext, options, promise);
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
@Override
|
|
116
116
|
public void restartApp(Promise promise) {
|
|
117
|
-
UpdateModuleImpl.restartApp(mContext, promise);
|
|
117
|
+
UpdateModuleImpl.restartApp(updateContext, mContext, null, promise);
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
@Override
|
|
121
121
|
public void setNeedUpdate(ReadableMap options,Promise promise) {
|
|
122
|
-
UpdateModuleImpl.setNeedUpdate(updateContext, options,promise);
|
|
122
|
+
UpdateModuleImpl.setNeedUpdate(updateContext, options, promise);
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
@Override
|
|
126
126
|
public void markSuccess(Promise promise) {
|
|
127
|
-
UpdateModuleImpl.markSuccess(updateContext,promise);
|
|
127
|
+
UpdateModuleImpl.markSuccess(updateContext, promise);
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
@Override
|
|
@@ -4,36 +4,31 @@ import path from 'path';
|
|
|
4
4
|
export function reactNativeUpdatePlugin() {
|
|
5
5
|
return {
|
|
6
6
|
pluginId: 'reactNativeUpdatePlugin',
|
|
7
|
-
apply(
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
'entry/src/main/resources/rawfile/meta.json',
|
|
15
|
-
);
|
|
16
|
-
fs.mkdirSync(path.dirname(metaFilePath), { recursive: true });
|
|
7
|
+
apply(_node) {
|
|
8
|
+
const cwd = process.cwd();
|
|
9
|
+
const metaFilePath = path.resolve(
|
|
10
|
+
cwd,
|
|
11
|
+
'entry/src/main/resources/rawfile/meta.json',
|
|
12
|
+
);
|
|
13
|
+
fs.mkdirSync(path.dirname(metaFilePath), { recursive: true });
|
|
17
14
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
15
|
+
const moduleJsonPath = path.resolve(cwd, 'AppScope/app.json5');
|
|
16
|
+
let versionName = '';
|
|
17
|
+
if (fs.existsSync(moduleJsonPath)) {
|
|
18
|
+
const content = fs.readFileSync(moduleJsonPath, 'utf-8');
|
|
19
|
+
const match = content.match(
|
|
20
|
+
/(?:"versionName"|versionName):\s*["']([^"']+)["']/,
|
|
21
|
+
);
|
|
22
|
+
versionName = match?.[1] || '';
|
|
23
|
+
}
|
|
27
24
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
25
|
+
const metaContent = {
|
|
26
|
+
pushy_build_time: Date.now(),
|
|
27
|
+
versionName,
|
|
28
|
+
};
|
|
32
29
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
},
|
|
36
|
-
});
|
|
30
|
+
fs.writeFileSync(metaFilePath, JSON.stringify(metaContent, null, 2));
|
|
31
|
+
console.log(`Build time written to ${metaFilePath}`);
|
|
37
32
|
},
|
|
38
33
|
};
|
|
39
34
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,184 @@
|
|
|
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
|
+
|
|
131
|
+
const hermescLocations = [
|
|
132
|
+
{
|
|
133
|
+
path: path.join(PROJECT_ROOT, 'node_modules/react-native/sdks/hermesc/osx-bin/hermesc'),
|
|
134
|
+
name: 'Node Modules'
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
path: path.join(PROJECT_ROOT, 'ios/Pods/hermes-engine/destroot/bin/hermesc'),
|
|
138
|
+
name: 'iOS Pods'
|
|
139
|
+
}
|
|
140
|
+
];
|
|
141
|
+
|
|
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
|
+
setupHermescWrapper();
|
|
158
|
+
const baseJSBundle = require('metro/src/DeltaBundler/Serializers/baseJSBundle');
|
|
159
|
+
const bundleToString = require('metro/src/lib/bundleToString');
|
|
160
|
+
const bundle = baseJSBundle(entryPoint, preModules, graph, options);
|
|
161
|
+
const { code: bundleCode } = bundleToString(bundle);
|
|
162
|
+
const contentHash = calculateContentHash(bundleCode);
|
|
163
|
+
const metadataInjection = generateMetadataInjection(contentHash);
|
|
164
|
+
const metadataComment = generateMetadataComment(contentHash);
|
|
165
|
+
const hashFilePath = path.join(PROJECT_ROOT, 'bundle-hash.json');
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
const hashData = {
|
|
169
|
+
contentHash,
|
|
170
|
+
timestamp: new Date().toISOString(),
|
|
171
|
+
};
|
|
172
|
+
fs.writeFileSync(hashFilePath, JSON.stringify(hashData, null, 2));
|
|
173
|
+
console.log(`✅ [Metro] Saved hash to: ${hashFilePath}`);
|
|
174
|
+
console.log(`🔐 [Metro] Hash: ${contentHash.slice(0, 16)}...`);
|
|
175
|
+
} catch (error) {
|
|
176
|
+
console.error('❌ [Metro] Failed to save hash file:', error);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return bundleCode + metadataInjection + metadataComment;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
module.exports = {
|
|
183
|
+
metadataSerializer,
|
|
184
|
+
};
|
|
@@ -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
|
+
}
|