miaoda-expo-devkit 0.1.1-dev.development-build.4 → 0.1.1-dev.development-build.6

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 CHANGED
@@ -180,7 +180,9 @@ Web 注入内容包括:
180
180
  Bundle,避免使用失效的增量 revision。最终编译失败会返回 `422` 并继续保持暂停。
181
181
 
182
182
  普通恢复执行 Fast Refresh;`/_hmr/reload` 执行完整 Bundle 重载,若当前暂停则延迟到
183
- 恢复时执行。过渡期内旧路径 `/innerapi/v1/sourcecode/__hmr_*` 作为别名同样可用,
183
+ 恢复时执行。暂停期间 iOS/Android 客户端展示全屏「应用正在更新中」遮罩,阻断用户
184
+ 对旧界面的交互,恢复后自动消失(Web 端不展示)。过渡期内旧路径
185
+ `/innerapi/v1/sourcecode/__hmr_*` 作为别名同样可用,
184
186
  供平台网关 sourcecode 透传通道不改路由接入。协议、返回值、版本要求和加载边界见
185
187
  [Metro HMR 批量编辑控制](../../docs/metro-hmr-control.md)。
186
188
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "miaoda-expo-devkit",
3
- "version": "0.1.1-dev.development-build.4",
3
+ "version": "0.1.1-dev.development-build.6",
4
4
  "description": "Expo 应用开发工具集:Sentry DSN 替换 stub、错误/网络捕获、Metro 符号化",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.js",
@@ -21,7 +21,7 @@ index e2b8941..355deae 100644
21
21
  "jest-expo": "~55.0.9",
22
22
  "lottie-react-native": "~7.3.4",
23
23
  diff --git a/src/async-require/hmr.ts b/src/async-require/hmr.ts
24
- index c0ea71e..be9cb40 100644
24
+ index c0ea71e..9250e76 100644
25
25
  --- a/src/async-require/hmr.ts
26
26
  +++ b/src/async-require/hmr.ts
27
27
  @@ -17,6 +17,7 @@ import {
@@ -32,7 +32,7 @@ index c0ea71e..be9cb40 100644
32
32
  showLoading,
33
33
  } from './hmrUtils';
34
34
 
35
- @@ -29,6 +30,38 @@ let hmrClient: MetroHMRClient | null = null;
35
+ @@ -29,6 +30,66 @@ let hmrClient: MetroHMRClient | null = null;
36
36
  let hmrUnavailableReason: string | null = null;
37
37
  const buildErrorQueue = new Set<unknown>();
38
38
  let didConnect: boolean = false;
@@ -40,6 +40,34 @@ index c0ea71e..be9cb40 100644
40
40
  +// miaodaPaused 是 Agent 的编辑闸门,与用户自己的 Fast Refresh 开关
41
41
  +// (fastRefreshRequested)相互独立:放行后只有用户开着 Fast Refresh 才恢复更新。
42
42
  +let miaodaPaused = false;
43
+ +// 暂停状态订阅者:launch/MiaodaHmrOverlay 的全屏遮罩据此在 Agent 批量编辑
44
+ +// 期间盖住应用、阻断交互。遮罩挂在 dev 模式的根组件包装里,纯 JS 实现。
45
+ +const miaodaPausedListeners = new Set<(paused: boolean) => void>();
46
+ +
47
+ +// miaodaPaused 的唯一写入入口:值真正变化时才广播,订阅者异常不能
48
+ +// 反过来打断 HMR 控制链路(ack、reload 等时序敏感操作)。
49
+ +function setMiaodaPaused(paused: boolean) {
50
+ + if (miaodaPaused === paused) return;
51
+ + miaodaPaused = paused;
52
+ + for (const listener of miaodaPausedListeners) {
53
+ + try {
54
+ + listener(paused);
55
+ + } catch {}
56
+ + }
57
+ +}
58
+ +
59
+ +// 订阅暂停状态变化,返回取消订阅函数(供 React useEffect 使用)。
60
+ +export function subscribeMiaodaPaused(listener: (paused: boolean) => void): () => void {
61
+ + miaodaPausedListeners.add(listener);
62
+ + return () => {
63
+ + miaodaPausedListeners.delete(listener);
64
+ + };
65
+ +}
66
+ +
67
+ +// 遮罩挂载时读取当前值,避免错过订阅注册之前发生的迁移。
68
+ +export function isMiaodaPaused(): boolean {
69
+ + return miaodaPaused;
70
+ +}
43
71
  +let fastRefreshRequested = false;
44
72
  +// 断线兜底轮询间隔。控制消息走 /hot WebSocket,轮询只用于 socket 断开期间的
45
73
  +// 状态同步,不需要很频繁。
@@ -71,7 +99,7 @@ index c0ea71e..be9cb40 100644
71
99
  const pendingLogs: [LogLevel, any[]][] = [];
72
100
 
73
101
  type LogLevel =
74
- @@ -42,6 +75,102 @@ type LogLevel =
102
+ @@ -42,6 +103,102 @@ type LogLevel =
75
103
  | 'groupEnd'
76
104
  | 'debug';
77
105
 
@@ -140,7 +168,7 @@ index c0ea71e..be9cb40 100644
140
168
  + if (miaodaSessionBaseVersion === null && version !== null) miaodaSessionBaseVersion = version;
141
169
  + if (state.paused) {
142
170
  + miaodaObservedPaused = true;
143
- + miaodaPaused = true;
171
+ + setMiaodaPaused(true);
144
172
  + hmrClient?.disable();
145
173
  + // 把观察到的这次暂停作为本地基线版本。之后若出现没有对应 WebSocket
146
174
  + // 控制消息的新版本,说明错过了状态迁移,必须完整 Bundle reload。
@@ -155,7 +183,7 @@ index c0ea71e..be9cb40 100644
155
183
  + const missedReload = version !== null && miaodaSessionBaseVersion !== null && version > miaodaSessionBaseVersion && state.lastMode === 'reload';
156
184
  + const shouldReload = missedReload || (missedControl && (miaodaObservedPaused || miaodaWebSocketDisconnected));
157
185
  + miaodaObservedPaused = false;
158
- + miaodaPaused = false;
186
+ + setMiaodaPaused(false);
159
187
  + miaodaLastControlVersion = version;
160
188
  + if (shouldReload) {
161
189
  + reload();
@@ -174,7 +202,7 @@ index c0ea71e..be9cb40 100644
174
202
  function assert(foo: any, msg: string): asserts foo {
175
203
  if (!foo) throw new Error(msg);
176
204
  }
177
- @@ -52,6 +181,9 @@ function assert(foo: any, msg: string): asserts foo {
205
+ @@ -52,6 +209,9 @@ function assert(foo: any, msg: string): asserts foo {
178
206
  */
179
207
  const HMRClient = {
180
208
  enable() {
@@ -184,7 +212,7 @@ index c0ea71e..be9cb40 100644
184
212
  if (hmrUnavailableReason !== null) {
185
213
  // If HMR became unavailable while you weren't using it,
186
214
  // explain why when you try to turn it on.
187
- @@ -87,6 +219,7 @@ const HMRClient = {
215
+ @@ -87,6 +247,7 @@ const HMRClient = {
188
216
  },
189
217
 
190
218
  disable() {
@@ -192,7 +220,7 @@ index c0ea71e..be9cb40 100644
192
220
  assert(hmrClient, 'Expected HMRClient.setup() call at startup.');
193
221
  hmrClient.disable();
194
222
  },
195
- @@ -180,6 +313,13 @@ const HMRClient = {
223
+ @@ -180,6 +341,13 @@ const HMRClient = {
196
224
  const origin = `${serverScheme}://${serverHost}`;
197
225
  const client = new MetroHMRClient(`${origin}/hot`);
198
226
  hmrClient = client;
@@ -206,7 +234,7 @@ index c0ea71e..be9cb40 100644
206
234
 
207
235
  pendingEntryPoints.push(
208
236
  // HMRServer understands regular bundle URLs, so prefer that in case
209
- @@ -193,6 +333,55 @@ const HMRClient = {
237
+ @@ -193,6 +361,55 @@ const HMRClient = {
210
238
  })
211
239
  );
212
240
 
@@ -233,7 +261,7 @@ index c0ea71e..be9cb40 100644
233
261
  + case 'pause':
234
262
  + // 立即闸住 HMR 并隐藏刷新提示,然后回 ack——服务端要等所有客户端
235
263
  + // ack 后才认为暂停完成,ack 之前 Agent 不会开始写文件。
236
- + miaodaPaused = true;
264
+ + setMiaodaPaused(true);
237
265
  + client.disable();
238
266
  + hideLoading();
239
267
  + client.send(JSON.stringify({type: 'miaoda-hmr-ack', version: 1, id: message.id}));
@@ -241,12 +269,12 @@ index c0ea71e..be9cb40 100644
241
269
  + case 'resume':
242
270
  + // 解除 Agent 闸门;用户自己的 Fast Refresh 开关保持独立,
243
271
  + // 只有它开着才真正恢复更新。
244
- + miaodaPaused = false;
272
+ + setMiaodaPaused(false);
245
273
  + if (fastRefreshRequested) HMRClient.enable();
246
274
  + break;
247
275
  + case 'reload':
248
276
  + // 完整加载前也保持闸门,避免 reload 完成前又有增量进来。
249
- + miaodaPaused = true;
277
+ + setMiaodaPaused(true);
250
278
  + client.disable();
251
279
  + reload();
252
280
  + break;
@@ -262,7 +290,7 @@ index c0ea71e..be9cb40 100644
262
290
  client.on('connection-error', (e: Error) => {
263
291
  setHMRUnavailableReason(getConnectionError(serverHost, e));
264
292
  });
265
- @@ -234,6 +423,10 @@ const HMRClient = {
293
+ @@ -234,6 +451,10 @@ const HMRClient = {
266
294
  client.on('error', (data) => this._onMetroError(data));
267
295
 
268
296
  client.on('close', (closeEvent?: { code: number; reason: string }) => {
@@ -273,7 +301,7 @@ index c0ea71e..be9cb40 100644
273
301
  hideLoading();
274
302
  const reason = closeEvent?.reason;
275
303
  const code = closeEvent?.code || 1000;
276
- @@ -326,6 +519,9 @@ function registerBundleEntryPoints(client: MetroHMRClient | null) {
304
+ @@ -326,6 +547,9 @@ function registerBundleEntryPoints(client: MetroHMRClient | null) {
277
305
  client?.send(
278
306
  JSON.stringify({
279
307
  type: 'register-entrypoints',
@@ -283,3 +311,125 @@ index c0ea71e..be9cb40 100644
283
311
  entryPoints: pendingEntryPoints,
284
312
  })
285
313
  );
314
+ diff --git a/src/launch/MiaodaHmrOverlay.tsx b/src/launch/MiaodaHmrOverlay.tsx
315
+ new file mode 100644
316
+ index 0000000..2db61b9
317
+ --- /dev/null
318
+ +++ b/src/launch/MiaodaHmrOverlay.tsx
319
+ @@ -0,0 +1,58 @@
320
+ +import * as React from 'react';
321
+ +import { ActivityIndicator, StyleSheet, Text, View } from 'react-native';
322
+ +
323
+ +import { isMiaodaPaused, subscribeMiaodaPaused } from '../async-require/hmr';
324
+ +
325
+ +// Miaoda HMR 批量编辑期间的「更新中」全屏遮罩。
326
+ +//
327
+ +// Agent 通过 `/_hmr/off` 暂停 HMR 后会连续改写工程文件,这期间设备仍在运行
328
+ +// 上一份完好的 JS,但用户看到的是一个随时会被替换掉的界面,继续操作会产生
329
+ +// 基于旧代码的困惑交互。遮罩在暂停期间盖住整个应用并吃掉所有触摸,
330
+ +// resume(或 reload 导致的整树卸载)后自动消失。
331
+ +//
332
+ +// 状态源是 async-require/hmr 里的 miaodaPaused 广播:WebSocket 控制帧到达时
333
+ +// 同步触发,2 秒轮询兜底路径同样走该广播,因此两种通道下遮罩行为一致。
334
+ +// 纯 JS 实现,挂在 withDevTools 里,只在开发模式出现,不需要原生改动。
335
+ +export default function MiaodaHmrOverlay(): React.ReactElement | null {
336
+ + // 挂载时用当前值初始化,避免错过订阅注册之前就已经开始的暂停。
337
+ + const [paused, setPaused] = React.useState(isMiaodaPaused);
338
+ +
339
+ + React.useEffect(() => subscribeMiaodaPaused(setPaused), []);
340
+ +
341
+ + if (!paused) return null;
342
+ +
343
+ + // absoluteFill + 默认的 pointerEvents="auto":作为根组件的最后一个兄弟节点
344
+ + // 渲染在用户界面之上,拦截全部触摸,直到暂停解除。
345
+ + return (
346
+ + <View style={styles.container}>
347
+ + <ActivityIndicator color="#ffffff" size="large" />
348
+ + <Text style={styles.title}>应用正在更新中</Text>
349
+ + <Text style={styles.subtitle}>暂时无法使用,更新完成后将自动恢复</Text>
350
+ + </View>
351
+ + );
352
+ +}
353
+ +
354
+ +const styles = StyleSheet.create({
355
+ + container: {
356
+ + // 不展开 StyleSheet.absoluteFillObject(RN 新版本中已标记废弃),直接写全。
357
+ + position: 'absolute',
358
+ + top: 0,
359
+ + left: 0,
360
+ + right: 0,
361
+ + bottom: 0,
362
+ + backgroundColor: 'rgba(0, 0, 0, 0.65)',
363
+ + alignItems: 'center',
364
+ + justifyContent: 'center',
365
+ + },
366
+ + title: {
367
+ + color: '#ffffff',
368
+ + fontSize: 17,
369
+ + fontWeight: '600',
370
+ + marginTop: 16,
371
+ + },
372
+ + subtitle: {
373
+ + color: 'rgba(255, 255, 255, 0.8)',
374
+ + fontSize: 13,
375
+ + marginTop: 8,
376
+ + },
377
+ +});
378
+ diff --git a/src/launch/withDevTools.ios.tsx b/src/launch/withDevTools.ios.tsx
379
+ index f1f686c..0029bfa 100644
380
+ --- a/src/launch/withDevTools.ios.tsx
381
+ +++ b/src/launch/withDevTools.ios.tsx
382
+ @@ -3,6 +3,8 @@ import * as React from 'react';
383
+ import DevLoadingView from '../environment/DevLoadingView';
384
+ import { isRunningInExpoGo } from '../environment/ExpoGo';
385
+
386
+ +import MiaodaHmrOverlay from './MiaodaHmrOverlay';
387
+ +
388
+ /**
389
+ * Append the Expo Fast Refresh view and optionally
390
+ * keep the screen awake if `expo-keep-awake` is installed.
391
+ @@ -31,11 +33,17 @@ export function withDevTools<TComponent extends React.ComponentType<any>>(
392
+ <>
393
+ <AppRootComponent {...props} />
394
+ <DevLoadingView />
395
+ + <MiaodaHmrOverlay />
396
+ </>
397
+ );
398
+ }
399
+
400
+ - return <AppRootComponent {...props} />;
401
+ + return (
402
+ + <>
403
+ + <AppRootComponent {...props} />
404
+ + <MiaodaHmrOverlay />
405
+ + </>
406
+ + );
407
+ }
408
+
409
+ if (process.env.NODE_ENV !== 'production') {
410
+ diff --git a/src/launch/withDevTools.tsx b/src/launch/withDevTools.tsx
411
+ index 5a98062..6a6c3b7 100644
412
+ --- a/src/launch/withDevTools.tsx
413
+ +++ b/src/launch/withDevTools.tsx
414
+ @@ -1,5 +1,7 @@
415
+ import * as React from 'react';
416
+
417
+ +import MiaodaHmrOverlay from './MiaodaHmrOverlay';
418
+ +
419
+ // Keep the screen awake on Android, we don't use the Fast Refresh overlay here.
420
+ // The default behavior here is to skip the custom Fast Refresh indicator.
421
+ export function withDevTools<TComponent extends React.ComponentType<any>>(
422
+ @@ -18,7 +20,12 @@ export function withDevTools<TComponent extends React.ComponentType<any>>(
423
+
424
+ function WithDevTools(props: React.ComponentProps<TComponent>) {
425
+ useOptionalKeepAwake();
426
+ - return <AppRootComponent {...props} />;
427
+ + return (
428
+ + <>
429
+ + <AppRootComponent {...props} />
430
+ + <MiaodaHmrOverlay />
431
+ + </>
432
+ + );
433
+ }
434
+
435
+ if (process.env.NODE_ENV !== 'production') {
@@ -1,3 +1,30 @@
1
+ diff --git a/Libraries/LogBox/Data/LogBoxData.js b/Libraries/LogBox/Data/LogBoxData.js
2
+ index 2ce92e0..56eb4f8 100644
3
+ --- a/Libraries/LogBox/Data/LogBoxData.js
4
+ +++ b/Libraries/LogBox/Data/LogBoxData.js
5
+ @@ -149,9 +149,17 @@ function appendNewLog(newLog: LogBoxLog) {
6
+ - // If the next log has the same category as the previous one
7
+ - // then roll it up into the last log in the list by incrementing
8
+ - // the count (similar to how Chrome does it).
9
+ + // Only merge logs with the same severity. A fatal exception must retain
10
+ + // its own stack and open the inspector even after a matching console error.
11
+ const lastLog = Array.from(logs).pop();
12
+ - if (lastLog && lastLog.category === newLog.category) {
13
+ + if (
14
+ + lastLog &&
15
+ + lastLog.category === newLog.category &&
16
+ + lastLog.level === newLog.level
17
+ + ) {
18
+ lastLog.incrementCount();
19
+ - handleUpdate();
20
+ + // A repeated uncaught exception must also reopen a hidden inspector.
21
+ + if (newLog.level === 'fatal' && _selectedIndex < 0) {
22
+ + setSelectedLog(logs.size - 1);
23
+ + } else {
24
+ + handleUpdate();
25
+ + }
26
+ return;
27
+ }
1
28
  diff --git a/Libraries/LogBox/UI/LogBoxInspector.js b/Libraries/LogBox/UI/LogBoxInspector.js
2
29
  --- a/Libraries/LogBox/UI/LogBoxInspector.js
3
30
  +++ b/Libraries/LogBox/UI/LogBoxInspector.js
package/pnpmfile.cjs CHANGED
@@ -51,6 +51,7 @@ const managedPatches = {
51
51
  'expo@55.0.6': {
52
52
  filename: 'expo@55.0.6.patch',
53
53
  legacyHashes: new Set([
54
+ '3eca6fa71eb9640bccf2075dc37c5c8ed2fa060db77b36bb2c67fba902a9a9c5',
54
55
  '1df9b522728e93daf64f9e2298ab835ce52d55b4aa2de92a68b947e7ed08f40a',
55
56
  '8ac63998a9852ddafda7af96deb4d7ac4c3a5da5e65e865af32be9d4fd966e02',
56
57
  '12efbb4c64bc67744a838cc5d9d88604ad2a99cdd578a5cfab31b8d1c2be1144',