react-native-update 10.37.13 → 10.37.15

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/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "react-native-update",
3
- "version": "10.37.13",
3
+ "version": "10.37.15",
4
4
  "description": "react-native hot update",
5
5
  "main": "src/index",
6
6
  "scripts": {
7
+ "prepublishOnly": "NODE_ENV=production bun scripts/prepublish.ts",
7
8
  "postinstall": "node scripts/check-expo-version.js",
8
9
  "prepack": "bun submodule && bun lint",
9
10
  "lint": "eslint \"src/*.@(ts|tsx|js|jsx)\" && tsc --noEmit",
@@ -12,12 +13,12 @@
12
13
  "build:so": "bun submodule && $ANDROID_HOME/ndk/28.2.13676358/ndk-build NDK_PROJECT_PATH=android APP_BUILD_SCRIPT=android/jni/Android.mk NDK_APPLICATION_MK=android/jni/Application.mk NDK_LIBS_OUT=android/lib",
13
14
  "build:ios-debug": "cd Example/testHotUpdate && bun && detox build --configuration ios.sim.debug",
14
15
  "build:ios-release": "cd Example/testHotUpdate && bun && detox build --configuration ios.sim.release",
15
- "test:ios-debug": "cd Example/testHotUpdate && detox test --configuration ios.sim.debug",
16
- "test:ios-release": "cd Example/testHotUpdate && bun detox test --configuration ios.sim.release",
16
+ "test:ios-debug": "cd Example/testHotUpdate && E2E_PLATFORM=ios detox test --configuration ios.sim.debug",
17
+ "test:ios-release": "cd Example/testHotUpdate && E2E_PLATFORM=ios bun detox test --configuration ios.sim.release",
17
18
  "build:android-debug": "cd Example/testHotUpdate && bun && detox build --configuration android.emu.debug",
18
19
  "build:android-release": "cd Example/testHotUpdate && bun && detox build --configuration android.emu.release",
19
- "test:android-release": "cd Example/testHotUpdate && bun detox test --configuration android.emu.release --headless --record-logs all",
20
- "test:android-debug": "cd Example/testHotUpdate && detox test --configuration android.emu.debug --headless --record-logs all",
20
+ "test:android-release": "cd Example/testHotUpdate && E2E_PLATFORM=android bun detox test --configuration android.emu.release --headless --record-logs all",
21
+ "test:android-debug": "cd Example/testHotUpdate && E2E_PLATFORM=android detox test --configuration android.emu.debug --headless --record-logs all",
21
22
  "e2e:ios": "bun build:ios-release && bun test:ios-release",
22
23
  "e2e:android": "bun build:android-release && bun test:android-release",
23
24
  "tests:emulator:prepare": "cd .github/workflows/scripts/functions && bun && bun build",
@@ -59,6 +60,7 @@
59
60
  "@react-native/babel-preset": "^0.73.21",
60
61
  "@react-native/eslint-config": "0.79.1",
61
62
  "@react-native/typescript-config": "0.79.1",
63
+ "@types/bun": "^1.3.7",
62
64
  "@types/jest": "^29.5.14",
63
65
  "@types/node": "^22.15.2",
64
66
  "@types/react": "^18.3.11",
@@ -73,4 +75,4 @@
73
75
  "ts-jest": "^29.3.2",
74
76
  "typescript": "^5.6.3"
75
77
  }
76
- }
78
+ }
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { access, readFile, writeFile } from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import { $ } from 'bun';
6
+
7
+ async function modifyPackageJson({
8
+ version,
9
+ }: {
10
+ version: string;
11
+ }): Promise<void> {
12
+ const packageJsonPath = path.join(__dirname, '..', 'package.json');
13
+
14
+ try {
15
+ await access(packageJsonPath);
16
+ } catch {
17
+ throw new Error(`package.json not found at ${packageJsonPath}`);
18
+ }
19
+
20
+ console.log('Reading package.json...');
21
+ const packageJsonContent = await readFile(packageJsonPath, 'utf-8');
22
+ const packageJson = JSON.parse(packageJsonContent);
23
+
24
+ packageJson.version = version;
25
+
26
+ console.log('Writing modified package.json...');
27
+
28
+ await writeFile(
29
+ packageJsonPath,
30
+ JSON.stringify(packageJson, null, 2),
31
+ 'utf-8',
32
+ );
33
+
34
+ console.log('package.json has been modified for publishing');
35
+ }
36
+
37
+ async function main(): Promise<void> {
38
+ const version = (await $`git describe --tags --always`.text())
39
+ .replace('v', '')
40
+ .trim();
41
+ try {
42
+ await modifyPackageJson({ version });
43
+ console.log('✅ Prepublish script completed successfully');
44
+ } catch (error) {
45
+ console.error('❌ Prepublish script failed:', error);
46
+ process.exit(1);
47
+ }
48
+
49
+ }
50
+
51
+ main();
@@ -0,0 +1,188 @@
1
+ const fs = require('fs');
2
+
3
+ /**
4
+ * 从 Hermes 字节码文件中读取 metadata
5
+ * 支持两种方式:
6
+ * 1. 从文件末尾的自定义 Meta 块读取(新方式,推荐)
7
+ * 2. 从 Hermes -meta 参数注入的数据读取(旧方式,备用)
8
+ */
9
+ function readBundleMetadata(filePath) {
10
+ const buffer = fs.readFileSync(filePath);
11
+
12
+ // 优先尝试从文件末尾的自定义 Meta 块读取
13
+ const metaFromFooter = readMetadataFromHBCFooter(buffer);
14
+ if (metaFromFooter) {
15
+ return metaFromFooter;
16
+ }
17
+
18
+ // 如果末尾没有 Meta 块,尝试从 Hermes -meta 参数注入的数据读取(备用方案)
19
+ console.log('No metadata footer found, trying to read from Hermes -meta parameter...');
20
+ return readMetadataFromHermesMeta(buffer);
21
+ }
22
+
23
+ /**
24
+ * 从 HBC 文件末尾的自定义 Meta 块读取元数据
25
+ * 格式: [原始HBC][MAGIC_START][LENGTH][JSON_DATA][MAGIC_END]
26
+ */
27
+ function readMetadataFromHBCFooter(buffer) {
28
+ const MAGIC = Buffer.from('RNUPDATE', 'utf8'); // 8 bytes
29
+ const MAGIC_SIZE = 8;
30
+ const LENGTH_SIZE = 4;
31
+
32
+ console.log(`\n[DEBUG] Reading metadata from HBC footer...`);
33
+ console.log(`[DEBUG] File size: ${buffer.length} bytes`);
34
+ console.log(`[DEBUG] Last 100 bytes (hex): ${buffer.slice(-100).toString('hex')}`);
35
+ console.log(`[DEBUG] Last 50 bytes (utf8): ${buffer.slice(-50).toString('utf8', 0, 50).replace(/[^\x20-\x7E]/g, '.')}`);
36
+
37
+ // 检查文件是否足够大以包含 meta 块
38
+ // 最小大小: MAGIC_START(8) + LENGTH(4) + JSON(至少2字节"{}") + MAGIC_END(8) = 22 bytes
39
+ if (buffer.length < 22) {
40
+ console.log(`[DEBUG] File too small: ${buffer.length} < 22`);
41
+ return null;
42
+ }
43
+
44
+ // 从文件末尾向前查找最后一个 MAGIC_END
45
+ const lastMagicIndex = buffer.lastIndexOf(MAGIC);
46
+
47
+ console.log(`[DEBUG] MAGIC string: "${MAGIC.toString('utf8')}"`);
48
+ console.log(`[DEBUG] Last MAGIC index: ${lastMagicIndex}`);
49
+
50
+ if (lastMagicIndex === -1) {
51
+ console.log(`[DEBUG] MAGIC not found in file`);
52
+ return null;
53
+ }
54
+
55
+ // MAGIC_END 应该在文件末尾
56
+ console.log(`[DEBUG] Expected MAGIC_END at: ${buffer.length - MAGIC_SIZE}`);
57
+ console.log(`[DEBUG] Found MAGIC_END at: ${lastMagicIndex}`);
58
+
59
+ if (lastMagicIndex + MAGIC_SIZE !== buffer.length) {
60
+ console.warn(`⚠️ Found MAGIC but not at file end (expected ${buffer.length - MAGIC_SIZE}, found ${lastMagicIndex})`);
61
+ return null;
62
+ }
63
+
64
+ // 计算 MAGIC_START 的位置
65
+ // 从 MAGIC_END 向前:MAGIC_END(8) + JSON(?) + LENGTH(4) + MAGIC_START(8)
66
+ const magicEndStart = lastMagicIndex;
67
+
68
+ // 读取长度字段(在 MAGIC_END 之前的 4 字节)
69
+ const lengthStart = magicEndStart - LENGTH_SIZE;
70
+ if (lengthStart < MAGIC_SIZE) {
71
+ console.log(`[DEBUG] lengthStart too small: ${lengthStart} < ${MAGIC_SIZE}`);
72
+ return null; // 文件太小
73
+ }
74
+
75
+ const jsonLength = buffer.readUInt32LE(lengthStart);
76
+ console.log(`[DEBUG] JSON length from buffer: ${jsonLength}`);
77
+
78
+ // 验证长度是否合理(JSON 数据应该小于 10KB)
79
+ if (jsonLength > 10240 || jsonLength < 2) {
80
+ console.warn(`⚠️ Invalid JSON length: ${jsonLength}`);
81
+ return null;
82
+ }
83
+
84
+ // 计算 JSON 数据的起始位置
85
+ const jsonStart = lengthStart - jsonLength;
86
+ console.log(`[DEBUG] JSON start position: ${jsonStart}`);
87
+ if (jsonStart < MAGIC_SIZE) {
88
+ console.log(`[DEBUG] jsonStart too small: ${jsonStart} < ${MAGIC_SIZE}`);
89
+ return null;
90
+ }
91
+
92
+ // 验证 MAGIC_START
93
+ const magicStartPos = jsonStart - MAGIC_SIZE;
94
+ console.log(`[DEBUG] MAGIC_START position: ${magicStartPos}`);
95
+ const magicStart = buffer.slice(magicStartPos, jsonStart);
96
+ console.log(`[DEBUG] MAGIC_START bytes: ${magicStart.toString('hex')}`);
97
+ console.log(`[DEBUG] Expected MAGIC bytes: ${MAGIC.toString('hex')}`);
98
+
99
+ if (!magicStart.equals(MAGIC)) {
100
+ console.warn('⚠️ MAGIC_START mismatch');
101
+ console.warn(` Expected: ${MAGIC.toString('hex')}`);
102
+ console.warn(` Got: ${magicStart.toString('hex')}`);
103
+ return null;
104
+ }
105
+
106
+ // 读取 JSON 数据
107
+ try {
108
+ const jsonBuffer = buffer.slice(jsonStart, lengthStart);
109
+ const jsonString = jsonBuffer.toString('utf8');
110
+ console.log(`[DEBUG] JSON string: ${jsonString}`);
111
+ const metadata = JSON.parse(jsonString);
112
+
113
+ console.log(`✅ Found metadata in HBC footer: contentHash=${metadata.contentHash?.slice(0, 16)}...`);
114
+ console.log(` Metadata: ${JSON.stringify(metadata, null, 2)}`);
115
+
116
+ return metadata;
117
+ } catch (error) {
118
+ console.error('❌ Failed to parse metadata JSON:', error);
119
+ return null;
120
+ }
121
+ }
122
+
123
+ /**
124
+ * 从 Hermes -meta 参数注入的数据读取(备用方案)
125
+ */
126
+ function readMetadataFromHermesMeta(buffer) {
127
+ const metadata = {};
128
+ const searchPattern = Buffer.from('contentHash=', 'utf8');
129
+
130
+ // 在整个文件中查找模式
131
+ let index = buffer.indexOf(searchPattern);
132
+
133
+ if (index !== -1) {
134
+ // 找到了 "contentHash=",读取后面的 hash 值
135
+ const hashStart = index + searchPattern.length;
136
+
137
+ // Hash 应该是 64 个十六进制字符 (SHA256)
138
+ let hashEnd = hashStart;
139
+ while (hashEnd < buffer.length && hashEnd < hashStart + 64) {
140
+ const byte = buffer[hashEnd];
141
+ // 检查是否是有效的十六进制字符 (0-9, a-f, A-F)
142
+ if ((byte >= 48 && byte <= 57) || // 0-9
143
+ (byte >= 97 && byte <= 102) || // a-f
144
+ (byte >= 65 && byte <= 70)) { // A-F
145
+ hashEnd++;
146
+ } else {
147
+ break;
148
+ }
149
+ }
150
+
151
+ if (hashEnd > hashStart) {
152
+ metadata.contentHash = buffer.toString('utf8', hashStart, hashEnd);
153
+ console.log(`✅ Found contentHash in Hermes -meta: ${metadata.contentHash.slice(0, 16)}...`);
154
+ }
155
+ } else {
156
+ console.warn('⚠️ contentHash not found in Hermes -meta parameter');
157
+ }
158
+
159
+ return metadata;
160
+ }
161
+
162
+ /**
163
+ * 从普通 JS bundle 的注释中读取 metadata
164
+ */
165
+ function readMetadataFromJSBundle(buffer) {
166
+ const content = buffer.toString('utf8');
167
+ const metadata = {};
168
+
169
+ // 查找 //# BUNDLE_METADATA {...} 注释
170
+ const metaMatch = content.match(/\/\/# BUNDLE_METADATA\s+(\{[^}]+\})/);
171
+ if (metaMatch) {
172
+ try {
173
+ const metaObj = JSON.parse(metaMatch[1]);
174
+ metadata.contentHash = metaObj.contentHash;
175
+ console.log(`✅ Found contentHash in JS bundle comment: ${metadata.contentHash.slice(0, 16)}...`);
176
+ } catch (e) {
177
+ console.error('Failed to parse BUNDLE_METADATA comment:', e);
178
+ }
179
+ } else {
180
+ console.warn('⚠️ BUNDLE_METADATA comment not found in JS bundle');
181
+ }
182
+
183
+ return metadata;
184
+ }
185
+
186
+ // 测试读取
187
+ const metadata = readBundleMetadata('./index.bundlejs');
188
+ console.log('Final metadata:', metadata);
package/src/client.ts CHANGED
@@ -323,9 +323,10 @@ export class Pushy {
323
323
  this.throwIfEnabled(Error('errorChecking: ' + errorMessage));
324
324
  return this.lastRespJson ? await this.lastRespJson : emptyObj;
325
325
  }
326
- this.lastRespJson = resp.json();
326
+ const respJsonPromise = resp.json() as Promise<CheckResult>;
327
+ this.lastRespJson = respJsonPromise;
327
328
 
328
- const result: CheckResult = await this.lastRespJson;
329
+ const result: CheckResult = await respJsonPromise;
329
330
 
330
331
  log('checking result:', result);
331
332