miaoda-expo-devkit 0.1.1-dev.development-build.1 → 0.1.1-dev.development-build.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 +15 -7
- package/dist/babel/plugin-lucide-react-native.js +1 -1
- package/dist/rules/no-bare-web-crypto.js +235 -0
- package/dist/rules/no-expo-file-system-compat.js +2130 -0
- package/dist/rules/no-lucide-star-import.js +78 -0
- package/dist/rules/no-sentry-default-import.js +83 -0
- package/dist/rules/no-typeof-window-check.js +85 -0
- package/oxlint-config.json +11 -1
- package/package.json +6 -1
- package/pnpm-patches/@expo__cli@55.0.16-55.0.22.patch +230 -145
- package/pnpm-patches/expo@55.0.6.patch +78 -30
- package/pnpm-patches/metro-file-map@0.83.3.patch +7 -3
- package/pnpm-patches/metro-runtime@0.83.3.patch +5 -1
- package/pnpm-patches/react-native-worklets@0.7.4.patch +91 -0
- package/pnpmfile.cjs +85 -2
|
@@ -1,118 +1,34 @@
|
|
|
1
|
-
diff --git a/build/src/
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
const _createDebugMiddleware = require("./debugging/createDebugMiddleware");
|
|
7
|
-
const _createMetroMiddleware = require("./dev-server/createMetroMiddleware");
|
|
8
|
-
+const _aiErrorCollector = require("./aiErrorCollector");
|
|
9
|
-
+const _miaodaHmrControl = require("./miaodaHmrControl");
|
|
10
|
-
const _runServerfork = require("./runServer-fork");
|
|
11
|
-
const _withMetroMultiPlatform = require("./withMetroMultiPlatform");
|
|
12
|
-
const _events = require("../../../events");
|
|
13
|
-
@@ -273,6 +275,7 @@
|
|
14
|
-
const { middleware, messagesSocket, eventsSocket, websocketEndpoints } = (0, _createMetroMiddleware.createMetroMiddleware)(metroConfig, {
|
|
15
|
-
getMetroBundler
|
|
16
|
-
});
|
|
17
|
-
+ const aiErrorCollector = !isExporting ? (0, _aiErrorCollector.createAiErrorCollector)(projectRoot) : null;
|
|
18
|
-
// Get local URL to Metro bundler server (typically configured as 127.0.0.1:8081)
|
|
19
|
-
const serverBaseUrl = metroBundler.getUrlCreator().constructUrl({
|
|
20
|
-
scheme: 'http',
|
|
21
|
-
@@ -347,7 +350,10 @@
|
|
22
|
-
}
|
|
23
|
-
}), fileBuffer);
|
|
24
|
-
};
|
|
25
|
-
- setEventReporter(eventsSocket.reportMetroEvent);
|
|
26
|
-
+ setEventReporter((event)=>{
|
|
27
|
-
+ eventsSocket.reportMetroEvent(event);
|
|
28
|
-
+ aiErrorCollector == null ? void 0 : aiErrorCollector.handleMetroEvent(event);
|
|
29
|
-
+ });
|
|
30
|
-
// This function ensures that modules in source maps are sorted in the same
|
|
31
|
-
// order as in a plain JS bundle.
|
|
32
|
-
metro._getSortedModules = function(graph) {
|
|
33
|
-
@@ -381,13 +387,24 @@
|
|
34
|
-
// Fork of https://github.com/facebook/metro/blob/3b3e0aaf725cfa6907bf2c8b5fbc0da352d29efe/packages/metro/src/HmrServer.js#L327-L393
|
|
35
|
-
// with patch for `_createModuleId`.
|
|
36
|
-
const logger = !options.isInitialUpdate ? changeEvent == null ? void 0 : changeEvent.logger : null;
|
|
37
|
-
+ const hmrChangeID = `${changeEvent == null ? 'initial' : changeEvent.changeId}:${group.revisionId}`;
|
|
38
|
-
+ aiErrorCollector == null ? void 0 : aiErrorCollector.handleMetroEvent({
|
|
39
|
-
+ type: 'hmr_update_started',
|
|
40
|
-
+ changeID: hmrChangeID
|
|
41
|
-
+ });
|
|
42
|
-
try {
|
|
43
|
-
var _revision_graph_transformOptions_customTransformOptions;
|
|
44
|
-
const revPromise = this._bundler.getRevision(group.revisionId);
|
|
45
|
-
if (!revPromise) {
|
|
46
|
-
+ const revisionError = new (_RevisionNotFoundError()).default(group.revisionId);
|
|
47
|
-
+ aiErrorCollector == null ? void 0 : aiErrorCollector.handleMetroEvent({
|
|
48
|
-
+ type: 'hmr_update_failed',
|
|
49
|
-
+ changeID: hmrChangeID,
|
|
50
|
-
+ error: revisionError
|
|
51
|
-
+ });
|
|
52
|
-
return {
|
|
53
|
-
type: 'error',
|
|
54
|
-
- body: (0, _formatBundlingError().default)(new (_RevisionNotFoundError()).default(group.revisionId))
|
|
55
|
-
+ body: (0, _formatBundlingError().default)(revisionError)
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
logger == null ? void 0 : logger.point('updateGraph_start');
|
|
59
|
-
@@ -418,6 +435,10 @@
|
|
60
|
-
serverRoot: this._config.server.unstable_serverRoot ?? this._config.projectRoot
|
|
61
|
-
});
|
|
62
|
-
logger == null ? void 0 : logger.point('serialize_end');
|
|
63
|
-
+ aiErrorCollector == null ? void 0 : aiErrorCollector.handleMetroEvent({
|
|
64
|
-
+ type: 'hmr_update_done',
|
|
65
|
-
+ changeID: hmrChangeID
|
|
66
|
-
+ });
|
|
67
|
-
return {
|
|
68
|
-
type: 'update',
|
|
69
|
-
body: {
|
|
70
|
-
@@ -430,7 +451,9 @@
|
|
71
|
-
const formattedError = (0, _formatBundlingError().default)(error);
|
|
72
|
-
this._config.reporter.update({
|
|
73
|
-
type: 'bundling_error',
|
|
74
|
-
- error
|
|
75
|
-
+ error,
|
|
76
|
-
+ isHmr: true,
|
|
77
|
-
+ changeID: hmrChangeID
|
|
78
|
-
});
|
|
79
|
-
return {
|
|
80
|
-
type: 'error',
|
|
81
|
-
@@ -438,6 +461,11 @@
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
+ }
|
|
86
|
-
+ if (hmrServer && process.env.EXPO_MIAODA_HMR_CONTROL === '1') {
|
|
87
|
-
+ hmrServer._miaodaAttach({
|
|
88
|
-
+ stateFile: process.env.EXPO_MIAODA_HMR_STATE_FILE
|
|
89
|
-
+ });
|
|
1
|
+
diff --git a/build/src/install/index.js b/build/src/install/index.js
|
|
2
|
+
index a374fca..d1229c1 100644
|
|
3
|
+
--- a/build/src/install/index.js
|
|
4
|
+
+++ b/build/src/install/index.js
|
|
5
|
+
@@ -55,10 +55,20 @@ const expoInstall = async (argv)=>{
|
|
90
6
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
+
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
+
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
websocketServer: hmrServer
|
|
7
|
+
// Load modules after the help prompt so `npx expo install -h` shows as fast as possible.
|
|
8
|
+
const { installAsync } = require('./installAsync');
|
|
9
|
+
- const { logCmdError } = require('../utils/errors');
|
|
10
|
+
+ const { logCmdError, SilentError } = require('../utils/errors');
|
|
11
|
+
const { resolveArgsAsync } = require('./resolveOptions');
|
|
12
|
+
const { variadic, options, extras } = await resolveArgsAsync(process.argv.slice(3)).catch(logCmdError);
|
|
13
|
+
- return installAsync(variadic, options, extras).catch(logCmdError);
|
|
14
|
+
+ return installAsync(variadic, options, extras).catch((error)=>{
|
|
15
|
+
+ if (
|
|
16
|
+
+ error &&
|
|
17
|
+
+ typeof error === 'object' &&
|
|
18
|
+
+ typeof error.status === 'number' &&
|
|
19
|
+
+ Array.isArray(error.output)
|
|
20
|
+
+ ) {
|
|
21
|
+
+ return logCmdError(new SilentError(error));
|
|
22
|
+
+ }
|
|
23
|
+
+ return logCmdError(error);
|
|
24
|
+
+ });
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
//# sourceMappingURL=index.js.map
|
|
28
|
+
|
|
114
29
|
diff --git a/build/src/start/server/metro/aiErrorCollector.js b/build/src/start/server/metro/aiErrorCollector.js
|
|
115
30
|
new file mode 100644
|
|
31
|
+
index 0000000..febccaf
|
|
116
32
|
--- /dev/null
|
|
117
33
|
+++ b/build/src/start/server/metro/aiErrorCollector.js
|
|
118
34
|
@@ -0,0 +1,222 @@
|
|
@@ -338,38 +254,135 @@ new file mode 100644
|
|
|
338
254
|
+}
|
|
339
255
|
+
|
|
340
256
|
+//# sourceMappingURL=aiErrorCollector.js.map
|
|
341
|
-
diff --git a/build/src/
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
+
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
+
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
+
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
+
|
|
257
|
+
diff --git a/build/src/start/server/metro/instantiateMetro.js b/build/src/start/server/metro/instantiateMetro.js
|
|
258
|
+
index bd1d604..02db474 100644
|
|
259
|
+
--- a/build/src/start/server/metro/instantiateMetro.js
|
|
260
|
+
+++ b/build/src/start/server/metro/instantiateMetro.js
|
|
261
|
+
@@ -90,6 +90,8 @@ const _MetroTerminalReporter = require("./MetroTerminalReporter");
|
|
262
|
+
const _attachAtlas = require("./debugging/attachAtlas");
|
|
263
|
+
const _createDebugMiddleware = require("./debugging/createDebugMiddleware");
|
|
264
|
+
const _createMetroMiddleware = require("./dev-server/createMetroMiddleware");
|
|
265
|
+
+const _aiErrorCollector = require("./aiErrorCollector");
|
|
266
|
+
+const _miaodaHmrControl = require("./miaodaHmrControl");
|
|
267
|
+
const _runServerfork = require("./runServer-fork");
|
|
268
|
+
const _withMetroMultiPlatform = require("./withMetroMultiPlatform");
|
|
269
|
+
const _events = require("../../../events");
|
|
270
|
+
@@ -273,6 +275,7 @@ async function instantiateMetroAsync(metroBundler, options, { isExporting, exp =
|
|
271
|
+
const { middleware, messagesSocket, eventsSocket, websocketEndpoints } = (0, _createMetroMiddleware.createMetroMiddleware)(metroConfig, {
|
|
272
|
+
getMetroBundler
|
|
273
|
+
});
|
|
274
|
+
+ const aiErrorCollector = !isExporting ? (0, _aiErrorCollector.createAiErrorCollector)(projectRoot) : null;
|
|
275
|
+
// Get local URL to Metro bundler server (typically configured as 127.0.0.1:8081)
|
|
276
|
+
const serverBaseUrl = metroBundler.getUrlCreator().constructUrl({
|
|
277
|
+
scheme: 'http',
|
|
278
|
+
@@ -347,7 +350,10 @@ async function instantiateMetroAsync(metroBundler, options, { isExporting, exp =
|
|
279
|
+
}
|
|
280
|
+
}), fileBuffer);
|
|
281
|
+
};
|
|
282
|
+
- setEventReporter(eventsSocket.reportMetroEvent);
|
|
283
|
+
+ setEventReporter((event)=>{
|
|
284
|
+
+ eventsSocket.reportMetroEvent(event);
|
|
285
|
+
+ aiErrorCollector == null ? void 0 : aiErrorCollector.handleMetroEvent(event);
|
|
363
286
|
+ });
|
|
364
|
-
|
|
287
|
+
// This function ensures that modules in source maps are sorted in the same
|
|
288
|
+
// order as in a plain JS bundle.
|
|
289
|
+
metro._getSortedModules = function(graph) {
|
|
290
|
+
@@ -381,13 +387,24 @@ async function instantiateMetroAsync(metroBundler, options, { isExporting, exp =
|
|
291
|
+
// Fork of https://github.com/facebook/metro/blob/3b3e0aaf725cfa6907bf2c8b5fbc0da352d29efe/packages/metro/src/HmrServer.js#L327-L393
|
|
292
|
+
// with patch for `_createModuleId`.
|
|
293
|
+
const logger = !options.isInitialUpdate ? changeEvent == null ? void 0 : changeEvent.logger : null;
|
|
294
|
+
+ const hmrChangeID = `${changeEvent == null ? 'initial' : changeEvent.changeId}:${group.revisionId}`;
|
|
295
|
+
+ aiErrorCollector == null ? void 0 : aiErrorCollector.handleMetroEvent({
|
|
296
|
+
+ type: 'hmr_update_started',
|
|
297
|
+
+ changeID: hmrChangeID
|
|
298
|
+
+ });
|
|
299
|
+
try {
|
|
300
|
+
var _revision_graph_transformOptions_customTransformOptions;
|
|
301
|
+
const revPromise = this._bundler.getRevision(group.revisionId);
|
|
302
|
+
if (!revPromise) {
|
|
303
|
+
+ const revisionError = new (_RevisionNotFoundError()).default(group.revisionId);
|
|
304
|
+
+ aiErrorCollector == null ? void 0 : aiErrorCollector.handleMetroEvent({
|
|
305
|
+
+ type: 'hmr_update_failed',
|
|
306
|
+
+ changeID: hmrChangeID,
|
|
307
|
+
+ error: revisionError
|
|
308
|
+
+ });
|
|
309
|
+
return {
|
|
310
|
+
type: 'error',
|
|
311
|
+
- body: (0, _formatBundlingError().default)(new (_RevisionNotFoundError()).default(group.revisionId))
|
|
312
|
+
+ body: (0, _formatBundlingError().default)(revisionError)
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
logger == null ? void 0 : logger.point('updateGraph_start');
|
|
316
|
+
@@ -418,6 +435,10 @@ async function instantiateMetroAsync(metroBundler, options, { isExporting, exp =
|
|
317
|
+
serverRoot: this._config.server.unstable_serverRoot ?? this._config.projectRoot
|
|
318
|
+
});
|
|
319
|
+
logger == null ? void 0 : logger.point('serialize_end');
|
|
320
|
+
+ aiErrorCollector == null ? void 0 : aiErrorCollector.handleMetroEvent({
|
|
321
|
+
+ type: 'hmr_update_done',
|
|
322
|
+
+ changeID: hmrChangeID
|
|
323
|
+
+ });
|
|
324
|
+
return {
|
|
325
|
+
type: 'update',
|
|
326
|
+
body: {
|
|
327
|
+
@@ -430,7 +451,9 @@ async function instantiateMetroAsync(metroBundler, options, { isExporting, exp =
|
|
328
|
+
const formattedError = (0, _formatBundlingError().default)(error);
|
|
329
|
+
this._config.reporter.update({
|
|
330
|
+
type: 'bundling_error',
|
|
331
|
+
- error
|
|
332
|
+
+ error,
|
|
333
|
+
+ isHmr: true,
|
|
334
|
+
+ changeID: hmrChangeID
|
|
335
|
+
});
|
|
336
|
+
return {
|
|
337
|
+
type: 'error',
|
|
338
|
+
@@ -439,6 +462,16 @@ async function instantiateMetroAsync(metroBundler, options, { isExporting, exp =
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
+ // Miaoda HMR 批量编辑控制的环境变量开关:只有显式设置
|
|
343
|
+
+ // EXPO_MIAODA_HMR_CONTROL=1 才挂载控制端点与消息闸门,普通本地开发
|
|
344
|
+
+ // 不受影响。stateFile 指定暂停状态的持久化位置,使编辑批次可以跨越
|
|
345
|
+
+ // Metro 重启。注意此处位于 Expo 替换 HMR serializer 之后,attach 包装到
|
|
346
|
+
+ // 的才是最终生效的 _prepareMessage。
|
|
347
|
+
+ if (hmrServer && process.env.EXPO_MIAODA_HMR_CONTROL === '1') {
|
|
348
|
+
+ hmrServer._miaodaAttach({
|
|
349
|
+
+ stateFile: process.env.EXPO_MIAODA_HMR_STATE_FILE
|
|
350
|
+
+ });
|
|
351
|
+
+ }
|
|
352
|
+
return {
|
|
353
|
+
metro,
|
|
354
|
+
hmrServer,
|
|
365
355
|
diff --git a/build/src/start/server/metro/miaodaHmrControl.js b/build/src/start/server/metro/miaodaHmrControl.js
|
|
366
356
|
new file mode 100644
|
|
357
|
+
index 0000000..8d5d129
|
|
367
358
|
--- /dev/null
|
|
368
359
|
+++ b/build/src/start/server/metro/miaodaHmrControl.js
|
|
369
|
-
@@ -0,0 +1,
|
|
360
|
+
@@ -0,0 +1,326 @@
|
|
370
361
|
+"use strict";
|
|
371
362
|
+
|
|
372
|
-
+// SDK 55 / Metro 0.83.3
|
|
363
|
+
+// Miaoda HMR 批量编辑控制,适配 SDK 55 / Metro 0.83.3。
|
|
364
|
+
+//
|
|
365
|
+
+// Agent 批量改写工程文件期间,本模块暂停已连接 Development Build 的 HMR 通道,
|
|
366
|
+
+// 让设备继续运行上一份完好的 JS,而不是执行中间态文件或展示其编译错误。
|
|
367
|
+
+// 批次结束后,一次 resume 只应用一份最终增量。
|
|
368
|
+
+//
|
|
369
|
+
+// 两条传输通道协作:
|
|
370
|
+
+// - 控制消息复用 Metro 已有的 `/hot` WebSocket,以
|
|
371
|
+
+// {type: 'miaoda-hmr-control', version: 1, action} 帧传输(pause / resume /
|
|
372
|
+
+// reload / error)。pause 携带 `id`,客户端在运行时停止应用更新后回复
|
|
373
|
+
+// {type: 'miaoda-hmr-ack', version: 1, id},因此 pause 成功即意味着
|
|
374
|
+
+// 不会再有在途更新落到设备上。
|
|
375
|
+
+// - 客户端每 2 秒轮询 GET __hmr_status,作为 WebSocket 消息丢失时的兜底。
|
|
376
|
+
+// 每次状态变化都会递增 `stateVersion`;轮询方若发现一个从未通过 socket
|
|
377
|
+
+// 收到的版本号,则执行完整 Bundle reload 而不是 resume,因为此时已无法
|
|
378
|
+
+// 证明增量 revision 的连续性。
|
|
379
|
+
+//
|
|
380
|
+
+// 本模块整体遵循 fail-closed:ack 超时(504)、未协商协议的老客户端(409)、
|
|
381
|
+
+// watcher 栅栏缺失或持久化失败(503)、最终编译错误(422)都会让闸门保持
|
|
382
|
+
+// 暂停,任何一次调用失败都不会把写了一半的代码放到设备上。
|
|
383
|
+
+//
|
|
384
|
+
+// 必须在 Expo 替换 HMR serializer 之后再 attach(见 instantiateMetro.js);
|
|
385
|
+
+// 提前包装会包住错误的 _prepareMessage。
|
|
373
386
|
+const fs = require('node:fs');
|
|
374
387
|
+const path = require('node:path');
|
|
375
388
|
+const CONTROL = 'miaoda-hmr-control';
|
|
@@ -385,6 +398,8 @@ new file mode 100644
|
|
|
385
398
|
+ let reloadPending = false;
|
|
386
399
|
+ let stateVersion = 0;
|
|
387
400
|
+ let lastMode = null;
|
|
401
|
+
+ // 一次编辑批次可能跨越 Metro 重启:状态文件让新进程恢复暂停状态,
|
|
402
|
+
+ // 继续闸门重连上来的客户端。
|
|
388
403
|
+ if (stateFile && fs.existsSync(stateFile)) {
|
|
389
404
|
+ try {
|
|
390
405
|
+ const state = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
|
|
@@ -393,10 +408,13 @@ new file mode 100644
|
|
|
393
408
|
+ stateVersion = Number.isInteger(state.stateVersion) ? state.stateVersion : 0;
|
|
394
409
|
+ lastMode = typeof state.lastMode === 'string' ? state.lastMode : null;
|
|
395
410
|
+ } catch {
|
|
396
|
-
+ //
|
|
411
|
+
+ // 损坏的状态文件绝不能放行一个未完成的编辑批次。
|
|
397
412
|
+ paused = true;
|
|
398
413
|
+ }
|
|
399
414
|
+ }
|
|
415
|
+
+ // client -> {send, version, registered, buffer, ack}。`send` 是原始 socket
|
|
416
|
+
+ // 写入器;HMR 输出走 onClientConnect 里安装的受控包装。`version` 在客户端
|
|
417
|
+
+ // 通过 register-entrypoints 协商控制协议后为 1,未协商的老客户端为 null。
|
|
400
418
|
+ const clients = new Map();
|
|
401
419
|
+ const groupTasks = new WeakMap();
|
|
402
420
|
+ const results = new WeakMap();
|
|
@@ -405,6 +423,8 @@ new file mode 100644
|
|
|
405
423
|
+ let controlId = 0;
|
|
406
424
|
+ let restoring = null;
|
|
407
425
|
+
|
|
426
|
+
+ // 崩溃安全的原子写:临时文件 + rename。调用方依赖「持久化状态绝不比
|
|
427
|
+
+ // 已告知客户端的内容更新」这一不变量(见 run())。
|
|
408
428
|
+ function persist() {
|
|
409
429
|
+ if (!stateFile) return;
|
|
410
430
|
+ fs.mkdirSync(path.dirname(stateFile), {recursive: true});
|
|
@@ -412,6 +432,8 @@ new file mode 100644
|
|
|
412
432
|
+ fs.writeFileSync(temp, JSON.stringify({version: 1, paused, reloadPending, stateVersion, lastMode}));
|
|
413
433
|
+ fs.renameSync(temp, stateFile);
|
|
414
434
|
+ }
|
|
435
|
+
+ // 控制帧只发给已协商的客户端——未协商的客户端不认识这些帧。
|
|
436
|
+
+ // stateVersion 随帧捎带,供轮询兜底检测是否有丢失的帧。
|
|
415
437
|
+ function control(info, action, extra = {}) {
|
|
416
438
|
+ if (info.version === 1) info.send(JSON.stringify({type: CONTROL, version: 1, action, stateVersion, ...extra}));
|
|
417
439
|
+ }
|
|
@@ -422,6 +444,8 @@ new file mode 100644
|
|
|
422
444
|
+ function status() {
|
|
423
445
|
+ return {status: 0, paused, reloadPending, stateVersion, lastMode, protocolVersion: 1};
|
|
424
446
|
+ }
|
|
447
|
+
+ // 单客户端暂停握手:客户端必须在运行时停止应用更新后 ack 这个 id,
|
|
448
|
+
+ // 以此证明不会再有缓冲的更新落到设备上。
|
|
425
449
|
+ function pauseClient(info) {
|
|
426
450
|
+ if (info.version !== 1 || info.ack) return;
|
|
427
451
|
+ const id = ++controlId;
|
|
@@ -435,6 +459,8 @@ new file mode 100644
|
|
|
435
459
|
+ error.status = status;
|
|
436
460
|
+ throw error;
|
|
437
461
|
+ }
|
|
462
|
+
+ // 只有当所有已协商客户端都确认后,暂停才算完成。
|
|
463
|
+
+ // Agent 必须等它 resolve 之后才能开始写文件。
|
|
438
464
|
+ async function awaitPaused() {
|
|
439
465
|
+ if ([...clients.values()].some(info => info.registered && info.version !== 1)) {
|
|
440
466
|
+ fail(409, 'Connected client lacks Miaoda HMR protocol v1; update its JS dependencies and reload.');
|
|
@@ -455,7 +481,10 @@ new file mode 100644
|
|
|
455
481
|
+ } finally { clearTimeout(timer); }
|
|
456
482
|
+ }
|
|
457
483
|
+
|
|
458
|
-
+ //
|
|
484
|
+
+ // 立即闸门输出,包括 pause 到达时已在编译中的更新:paused 期间 HMR 帧
|
|
485
|
+
+ // (update-start/update/update-done/error)按客户端缓冲而不发送。
|
|
486
|
+
+ // 控制帧和日志不受闸门限制;缓冲在 resume 时回放,客户端仍能收到基于
|
|
487
|
+
+ // 其最后应用 revision 计算的最终增量。
|
|
459
488
|
+ const connect = hmr.onClientConnect.bind(hmr);
|
|
460
489
|
+ hmr.onClientConnect = async (url, send) => {
|
|
461
490
|
+ const info = {send, version: null, registered: false, buffer: [], ack: null};
|
|
@@ -478,14 +507,18 @@ new file mode 100644
|
|
|
478
507
|
+ return;
|
|
479
508
|
+ }
|
|
480
509
|
+ if (data?.type === 'register-entrypoints' && info) {
|
|
481
|
-
+ //
|
|
510
|
+
+ // resume 进行中注册的客户端,绝不能先于 resume 提交状态迁移就看到
|
|
511
|
+
+ // 已释放的缓冲。
|
|
482
512
|
+ if (restoring) await restoring;
|
|
483
513
|
+ info.registered = true;
|
|
484
514
|
+ info.version = data.miaodaHmr === 1 ? 1 : null;
|
|
515
|
+
+ // 在暂停批次中(重)连入的客户端立即被暂停;连入空闲服务端的客户端
|
|
516
|
+
+ // 会收到一条 resume,用来清除上一次会话遗留的本地闸门。
|
|
485
517
|
+ if (paused) pauseClient(info);
|
|
486
518
|
+ else control(info, 'resume');
|
|
487
519
|
+ }
|
|
488
|
-
+ //
|
|
520
|
+
+ // 注册错误与图更新一样走受控发送器。
|
|
521
|
+
+ // 跟踪注册任务,resume 才能先等新连入客户端的初始图构建完成,再计算最终增量。
|
|
489
522
|
+ const task = onMessage(client, message, client.sendFn);
|
|
490
523
|
+ if (data?.type !== 'register-entrypoints') return task;
|
|
491
524
|
+ registrations.add(task);
|
|
@@ -493,11 +526,15 @@ new file mode 100644
|
|
|
493
526
|
+ };
|
|
494
527
|
+ const disconnect = hmr.onClientDisconnect.bind(hmr);
|
|
495
528
|
+ hmr.onClientDisconnect = client => {
|
|
529
|
+
+ // 已断开的客户端不可能再应用更新,直接 resolve 它的挂起 ack,
|
|
530
|
+
+ // 避免 pause 干等到 504 超时。
|
|
496
531
|
+ clients.get(client)?.ack?.resolve();
|
|
497
532
|
+ clients.delete(client);
|
|
498
533
|
+ return disconnect(client);
|
|
499
534
|
+ };
|
|
500
535
|
+
|
|
536
|
+
+ // 记录每个分组的构建结果,resume 据此检查最终编译结果,
|
|
537
|
+
+ // 在 fast-refresh、reload 与 422 之间做决策。
|
|
501
538
|
+ const prepare = hmr._prepareMessage.bind(hmr);
|
|
502
539
|
+ hmr._prepareMessage = async (...args) => {
|
|
503
540
|
+ const result = await prepare(...args);
|
|
@@ -505,10 +542,13 @@ new file mode 100644
|
|
|
505
542
|
+ return result;
|
|
506
543
|
+ };
|
|
507
544
|
+ const handle = hmr._handleFileChange.bind(hmr);
|
|
545
|
+
+ // 文件变更按分组严格串行处理。paused 时跳过 handler,但这只闸门「投递」:
|
|
546
|
+
+ // 各分组的 DeltaCalculator 内部仍在持续累积文件事件,客户端运行时也会合并
|
|
547
|
+
+ // disable 期间收到的更新,因此不会丢失任何中间态。
|
|
508
548
|
+ function enqueue(group, options, event, force = false) {
|
|
509
549
|
+ const previous = groupTasks.get(group) || Promise.resolve();
|
|
510
550
|
+ const task = previous.catch(() => {}).then(async () => {
|
|
511
|
-
+ //
|
|
551
|
+
+ // 保持 DeltaCalculator 的累积不变。绝不丢弃已构建的 revision。
|
|
512
552
|
+ if (paused && !force) return;
|
|
513
553
|
+ await handle(group, options, event);
|
|
514
554
|
+ });
|
|
@@ -525,6 +565,8 @@ new file mode 100644
|
|
|
525
565
|
+ await awaitPaused();
|
|
526
566
|
+ return {status: 0, paused, mode: 'paused'};
|
|
527
567
|
+ }
|
|
568
|
+
+ // 批次中途请求的 reload 会被推迟:暂停的意义就是不让中间态代码执行,
|
|
569
|
+
+ // 而 reload 加载的恰恰就是这些中间态。
|
|
528
570
|
+ if (action === 'reload' && paused) {
|
|
529
571
|
+ reloadPending = true;
|
|
530
572
|
+ transition('reload-deferred');
|
|
@@ -533,6 +575,8 @@ new file mode 100644
|
|
|
533
575
|
+ }
|
|
534
576
|
+ if (action === 'status') return status();
|
|
535
577
|
+ if (action === 'resume' && !paused) return {...status(), mode: 'unchanged'};
|
|
578
|
+
+ // resume 与 reload 共用前置的暂停握手:必须先把客户端静默下来再计算
|
|
579
|
+
+ // 最终增量,否则仍可能放出一个基于写了一半的目录树编译的更新。
|
|
536
580
|
+ paused = true;
|
|
537
581
|
+ if (action === 'reload') reloadPending = true;
|
|
538
582
|
+ transition(action === 'reload' ? 'reload-requested' : 'paused');
|
|
@@ -547,14 +591,22 @@ new file mode 100644
|
|
|
547
591
|
+ if (typeof fileMap.miaodaFlushChanges !== 'function') {
|
|
548
592
|
+ fail(503, 'Metro file-map barrier patch is missing; HMR remains paused.');
|
|
549
593
|
+ }
|
|
550
|
-
+ //
|
|
594
|
+
+ // Agent 通过调用 resume 表示「所有写入已完成」。栅栏让这一点对 Metro
|
|
595
|
+
+ // 同样成立:watcher cookie -> file-map 处理队列 -> change 事件,
|
|
596
|
+
+ // 保证下面的最终构建一定包含最后写入的字节。
|
|
551
597
|
+ await fileMap.miaodaFlushChanges(timeout);
|
|
552
598
|
+ const groups = [...new Set(hmr._clientGroups.values())];
|
|
599
|
+
+ // 每个分组强制构建一次,使最终增量(以及任何编译错误)在放行客户端
|
|
600
|
+
+ // 之前就算出来。
|
|
553
601
|
+ await Promise.all(groups.map(group => enqueue(group, {isInitialUpdate: false}, undefined, true)));
|
|
554
602
|
+ const errors = groups.map(group => results.get(group)).filter(result => result?.type === 'error');
|
|
603
|
+
+ // 图/revision 丢失不是代码错误:增量链已断,唯一安全的放行方式是
|
|
604
|
+
+ // 完整 Bundle reload。
|
|
555
605
|
+ const staleGraph = error => ['GraphNotFoundError', 'RevisionNotFoundError'].includes(error?.type);
|
|
556
606
|
+ const compileErrors = errors.filter(result => !staleGraph(result.body));
|
|
557
607
|
+ if (compileErrors.length) {
|
|
608
|
+
+ // 把最终错误告知客户端(设备端可以据此行动),但保持闸门关闭,
|
|
609
|
+
+ // 让 Agent 修好代码后重试。
|
|
558
610
|
+ for (const info of clients.values()) control(info, 'error', {error: compileErrors[0].body});
|
|
559
611
|
+ fail(422, 'Final code failed to compile; HMR remains paused. Fix the code and retry __hmr_on.');
|
|
560
612
|
+ }
|
|
@@ -562,12 +614,16 @@ new file mode 100644
|
|
|
562
614
|
+ const data = JSON.parse(String(message));
|
|
563
615
|
+ return data.type === 'error' && staleGraph(data.body);
|
|
564
616
|
+ }));
|
|
565
|
-
+ //
|
|
617
|
+
+ // 先持久化再放行客户端。如果进程在放行与持久化之间崩溃,重启后会读到
|
|
618
|
+
+ // 陈旧的 paused 状态,把已经 resume 的客户端再次暂停;因此持久化失败
|
|
619
|
+
+ // 必须让闸门保持关闭。
|
|
566
620
|
+ const previousReloadPending = reloadPending;
|
|
567
621
|
+ paused = false;
|
|
568
622
|
+ reloadPending = false;
|
|
569
623
|
+ transition(reload ? 'reload' : 'fast-refresh');
|
|
570
624
|
+ try { persist(); } catch (error) { paused = true; reloadPending = previousReloadPending; throw error; }
|
|
625
|
+
+ // 缓冲帧先于 resume 发出:运行时先把它们与最终增量合并,
|
|
626
|
+
+ // resume 到达时通道已安静。
|
|
571
627
|
+ for (const info of clients.values()) {
|
|
572
628
|
+ if (reload) control(info, 'reload');
|
|
573
629
|
+ else {
|
|
@@ -586,6 +642,8 @@ new file mode 100644
|
|
|
586
642
|
+ const handler = (req, res, next) => {
|
|
587
643
|
+ const pathname = new URL(req.url, 'http://localhost').pathname.replace(/\/$/, '');
|
|
588
644
|
+ const action = ROUTES.get(pathname);
|
|
645
|
+
+ // OPTIONS 直接放行:这些端点服务 native 客户端,native 不强制 CORS,
|
|
646
|
+
+ // 而 Web 端按设计不轮询。
|
|
589
647
|
+ if (!action || req.method === 'OPTIONS') return next();
|
|
590
648
|
+ if (action === 'status') {
|
|
591
649
|
+ if (req.method !== 'GET') {
|
|
@@ -603,6 +661,7 @@ new file mode 100644
|
|
|
603
661
|
+ res.setHeader('Allow', 'POST');
|
|
604
662
|
+ return res.end();
|
|
605
663
|
+ }
|
|
664
|
+
+ // 控制操作串行化:并发调用方的 pause 与 resume 一个接一个执行,绝不交错。
|
|
606
665
|
+ const task = serial.catch(() => {}).then(() => run(action));
|
|
607
666
|
+ serial = task;
|
|
608
667
|
+ task.then(body => {
|
|
@@ -615,7 +674,7 @@ new file mode 100644
|
|
|
615
674
|
+ res.end(JSON.stringify({status: 1, paused, msg: error.message}));
|
|
616
675
|
+ });
|
|
617
676
|
+ };
|
|
618
|
-
+ // Metro
|
|
677
|
+
+ // Metro 自带兜底 middleware:append 会让这些路由永远不可达。
|
|
619
678
|
+ middleware.use(handler);
|
|
620
679
|
+ middleware.stack.unshift(middleware.stack.pop());
|
|
621
680
|
+ return {run: action => {
|
|
@@ -625,3 +684,29 @@ new file mode 100644
|
|
|
625
684
|
+ }};
|
|
626
685
|
+}
|
|
627
686
|
+module.exports = {attachMiaodaHmrControl};
|
|
687
|
+
diff --git a/build/src/start/server/metro/runServer-fork.js b/build/src/start/server/metro/runServer-fork.js
|
|
688
|
+
index 652bee8..a08f26a 100644
|
|
689
|
+
--- a/build/src/start/server/metro/runServer-fork.js
|
|
690
|
+
+++ b/build/src/start/server/metro/runServer-fork.js
|
|
691
|
+
@@ -63,6 +63,7 @@ function _url() {
|
|
692
|
+
return data;
|
|
693
|
+
}
|
|
694
|
+
const _log = require("../../../log");
|
|
695
|
+
+const _miaodaHmrControl = require("./miaodaHmrControl");
|
|
696
|
+
function _interop_require_default(obj) {
|
|
697
|
+
return obj && obj.__esModule ? obj : {
|
|
698
|
+
default: obj
|
|
699
|
+
@@ -150,6 +151,13 @@ const runServer = async (_metroBundler, config, { hasReducedPerformance = false,
|
|
700
|
+
onReady(httpServer);
|
|
701
|
+
}
|
|
702
|
+
const hmrServer = new (_HmrServer()).default(metroServer.getBundler(), metroServer.getCreateModuleId(), config);
|
|
703
|
+
+ // 挂载 Miaoda HMR 控制的挂点:此处能同时拿到 hmrServer、metroServer
|
|
704
|
+
+ // 与 middleware 三元组(attach 三者都需要),但真正的挂载由
|
|
705
|
+
+ // instantiateMetro 在环境变量开启时触发,保证 attach 发生在 Expo
|
|
706
|
+
+ // 替换 HMR serializer 之后。
|
|
707
|
+
+ hmrServer._miaodaAttach = (options = {}) => (0, _miaodaHmrControl.attachMiaodaHmrControl)({
|
|
708
|
+
+ hmrServer, metro: metroServer, middleware: serverApp, ...options
|
|
709
|
+
+ });
|
|
710
|
+
Object.assign(websocketEndpoints, {
|
|
711
|
+
'/hot': (0, _createWebsocketServer().default)({
|
|
712
|
+
websocketServer: hmrServer
|