miaoda-expo-devkit 0.1.1-beta.98 → 0.1.1-dev.development-build.1

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.
Files changed (42) hide show
  1. package/README.md +154 -110
  2. package/dist/babel/plugin-lucide-react-native.js +184 -184
  3. package/dist/cli/lint.js +317 -309
  4. package/dist/index.d.mts +1 -110
  5. package/dist/index.d.ts +1 -110
  6. package/dist/index.js +0 -159
  7. package/dist/index.mjs +0 -132
  8. package/dist/metro.d.mts +25 -5
  9. package/dist/metro.d.ts +25 -5
  10. package/dist/metro.js +49 -18
  11. package/dist/metro.mjs +50 -20
  12. package/dist/rules/no-expo-audio-compat.js +2041 -0
  13. package/dist/rules/no-expo-video-compat.js +90 -90
  14. package/dist/stubs/entry-inject.js +0 -2
  15. package/dist/stubs/expo-native-wechat-stub.js +116 -0
  16. package/dist/stubs/expo-router-entry-native-stub.js +4 -0
  17. package/dist/stubs/i18n/en.js +16 -1
  18. package/dist/stubs/i18n/zh.js +16 -1
  19. package/dist/stubs/logbox-control.js +9 -0
  20. package/dist/stubs/screenshot-control.js +1 -1
  21. package/dist/stubs/sentry-react-native-stub.js +15 -180
  22. package/oxlint-config.json +11 -4
  23. package/package.json +60 -5
  24. package/pnpm-patches/@expo__cli@55.0.16-55.0.22.patch +627 -0
  25. package/pnpm-patches/@expo__cli@55.0.16.patch +218 -0
  26. package/pnpm-patches/@expo__cli@55.0.17.patch +218 -0
  27. package/pnpm-patches/@expo__cli@55.0.18.patch +218 -0
  28. package/pnpm-patches/@expo__cli@55.0.19.patch +218 -0
  29. package/pnpm-patches/@expo__cli@55.0.20.patch +218 -0
  30. package/pnpm-patches/@expo__cli@55.0.21.patch +218 -0
  31. package/pnpm-patches/@expo__cli@55.0.22.patch +218 -0
  32. package/pnpm-patches/@shopify__react-native-skia@2.4.18.patch +14 -0
  33. package/pnpm-patches/expo-modules-core@55.0.15.patch +21 -0
  34. package/pnpm-patches/expo@55.0.6.patch +235 -0
  35. package/pnpm-patches/metro-file-map@0.83.3.patch +24 -0
  36. package/pnpm-patches/metro-runtime@0.83.3.patch +13 -0
  37. package/pnpm-patches/react-native-css-interop@0.2.6.patch +12 -0
  38. package/pnpm-patches/react-native-reanimated@4.2.1.patch +53 -0
  39. package/pnpm-patches/react-native-worklets@0.7.2.patch +91 -0
  40. package/pnpm-patches/react-native@0.83.2.patch +384 -0
  41. package/pnpmfile.cjs +168 -0
  42. package/dist/stubs/preview-control.js +0 -60
@@ -0,0 +1,627 @@
1
+ diff --git a/build/src/start/server/metro/instantiateMetro.js b/build/src/start/server/metro/instantiateMetro.js
2
+ --- a/build/src/start/server/metro/instantiateMetro.js
3
+ +++ b/build/src/start/server/metro/instantiateMetro.js
4
+ @@ -90,6 +90,8 @@
5
+ const _attachAtlas = require("./debugging/attachAtlas");
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
+ + });
90
+ }
91
+ return {
92
+ metro,
93
+ diff --git a/build/src/start/server/metro/runServer-fork.js b/build/src/start/server/metro/runServer-fork.js
94
+ --- a/build/src/start/server/metro/runServer-fork.js
95
+ +++ b/build/src/start/server/metro/runServer-fork.js
96
+ @@ -63,6 +63,7 @@
97
+ return data;
98
+ }
99
+ const _log = require("../../../log");
100
+ +const _miaodaHmrControl = require("./miaodaHmrControl");
101
+ function _interop_require_default(obj) {
102
+ return obj && obj.__esModule ? obj : {
103
+ default: obj
104
+ @@ -150,6 +151,9 @@
105
+ onReady(httpServer);
106
+ }
107
+ const hmrServer = new (_HmrServer()).default(metroServer.getBundler(), metroServer.getCreateModuleId(), config);
108
+ + hmrServer._miaodaAttach = (options = {}) => (0, _miaodaHmrControl.attachMiaodaHmrControl)({
109
+ + hmrServer, metro: metroServer, middleware: serverApp, ...options
110
+ + });
111
+ Object.assign(websocketEndpoints, {
112
+ '/hot': (0, _createWebsocketServer().default)({
113
+ websocketServer: hmrServer
114
+ diff --git a/build/src/start/server/metro/aiErrorCollector.js b/build/src/start/server/metro/aiErrorCollector.js
115
+ new file mode 100644
116
+ --- /dev/null
117
+ +++ b/build/src/start/server/metro/aiErrorCollector.js
118
+ @@ -0,0 +1,222 @@
119
+ +"use strict";
120
+ +Object.defineProperty(exports, "__esModule", {
121
+ + value: true
122
+ +});
123
+ +Object.defineProperty(exports, "createAiErrorCollector", {
124
+ + enumerable: true,
125
+ + get: function() {
126
+ + return createAiErrorCollector;
127
+ + }
128
+ +});
129
+ +const fs = require("fs");
130
+ +const os = require("os");
131
+ +const path = require("path");
132
+ +
133
+ +const MAX_VALUE_DEPTH = 4;
134
+ +const MAX_STRING_LENGTH = 20000;
135
+ +
136
+ +function isEnabled() {
137
+ + return process.env.EXPO_AI_ERROR_COLLECTOR === '1';
138
+ +}
139
+ +
140
+ +function getOutputPath(projectRoot) {
141
+ + const configured = process.env.EXPO_AI_ERROR_FILE;
142
+ + if (!configured) return path.join(projectRoot, 'node_modules', '.miaoda-expo', 'ai-error.json');
143
+ + return path.isAbsolute(configured) ? configured : path.resolve(projectRoot, configured);
144
+ +}
145
+ +
146
+ +function toSerializable(value, depth = 0, seen = new Set()) {
147
+ + if (value == null || typeof value === 'boolean' || typeof value === 'number') return value;
148
+ + if (typeof value === 'string') return value.length > MAX_STRING_LENGTH ? value.slice(0, MAX_STRING_LENGTH) + '...[truncated]' : value;
149
+ + if (depth >= MAX_VALUE_DEPTH) return '[truncated]';
150
+ + if (typeof value === 'function' || typeof value === 'symbol') return String(value);
151
+ + if (seen.has(value)) return '[circular]';
152
+ + seen.add(value);
153
+ + if (value instanceof Error) {
154
+ + const error = {
155
+ + name: value.name,
156
+ + message: value.message,
157
+ + stack: value.stack
158
+ + };
159
+ + for (const key of Object.keys(value)) error[key] = toSerializable(value[key], depth + 1, seen);
160
+ + seen.delete(value);
161
+ + return error;
162
+ + }
163
+ + if (Array.isArray(value)) {
164
+ + const result = value.map((item)=>toSerializable(item, depth + 1, seen));
165
+ + seen.delete(value);
166
+ + return result;
167
+ + }
168
+ + if (typeof value === 'object') {
169
+ + const result = {};
170
+ + for (const key of Object.keys(value)) result[key] = toSerializable(value[key], depth + 1, seen);
171
+ + seen.delete(value);
172
+ + return result;
173
+ + }
174
+ + return String(value);
175
+ +}
176
+ +
177
+ +function normalizeError(error, projectRoot, buildID) {
178
+ + const raw = error && typeof error === 'object' ? error : {
179
+ + message: String(error ?? 'Metro bundling failed')
180
+ + };
181
+ + const message = String(raw.message ?? raw.reason ?? error ?? 'Metro bundling failed');
182
+ + const record = {
183
+ + schemaVersion: 1,
184
+ + id: `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`,
185
+ + timestamp: new Date().toISOString(),
186
+ + source: 'metro',
187
+ + eventType: 'bundling_error',
188
+ + buildID: buildID ?? undefined,
189
+ + projectRoot,
190
+ + type: String(raw.type ?? raw.name ?? 'MetroError'),
191
+ + name: raw.name == null ? undefined : String(raw.name),
192
+ + message,
193
+ + filename: raw.filename == null ? undefined : String(raw.filename),
194
+ + lineNumber: typeof raw.lineNumber === 'number' ? raw.lineNumber : undefined,
195
+ + column: typeof raw.column === 'number' ? raw.column : undefined,
196
+ + errors: Array.isArray(raw.errors) ? toSerializable(raw.errors) : [],
197
+ + stack: raw.stack == null ? undefined : String(raw.stack),
198
+ + cause: raw.cause == null ? undefined : toSerializable(raw.cause)
199
+ + };
200
+ + const extra = {};
201
+ + for (const key of Object.keys(raw)) {
202
+ + if (!['type', 'name', 'message', 'filename', 'lineNumber', 'column', 'errors', 'stack', 'cause'].includes(key)) {
203
+ + extra[key] = toSerializable(raw[key]);
204
+ + }
205
+ + }
206
+ + if (Object.keys(extra).length) record.extra = extra;
207
+ + return omitUndefined(record);
208
+ +}
209
+ +
210
+ +function omitUndefined(value) {
211
+ + if (!value || typeof value !== 'object') return value;
212
+ + if (Array.isArray(value)) return value.map(omitUndefined);
213
+ + const result = {};
214
+ + for (const [key, item] of Object.entries(value)) {
215
+ + if (item !== undefined) result[key] = omitUndefined(item);
216
+ + }
217
+ + return result;
218
+ +}
219
+ +
220
+ +function createAiErrorCollector(projectRoot) {
221
+ + if (!isEnabled()) return null;
222
+ + const outputFile = getOutputPath(projectRoot);
223
+ + let activeBuildID = null;
224
+ + let lastFailedBuildID = null;
225
+ + let activeHmrChangeID = null;
226
+ + let state = {
227
+ + schemaVersion: 1,
228
+ + status: 'compiling',
229
+ + buildID: null,
230
+ + updatedAt: new Date().toISOString(),
231
+ + error: null
232
+ + };
233
+ + let warned = false;
234
+ + const publish = (nextState)=>{
235
+ + state = omitUndefined({ ...nextState, updatedAt: new Date().toISOString() });
236
+ + let temporaryFile;
237
+ + try {
238
+ + fs.mkdirSync(path.dirname(outputFile), { recursive: true });
239
+ + temporaryFile = `${outputFile}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;
240
+ + fs.writeFileSync(temporaryFile, JSON.stringify(state, null, 2) + os.EOL, 'utf8');
241
+ + fs.renameSync(temporaryFile, outputFile);
242
+ + } catch (error) {
243
+ + try {
244
+ + if (temporaryFile) fs.rmSync(temporaryFile, { force: true });
245
+ + } catch {}
246
+ + if (!warned) {
247
+ + warned = true;
248
+ + console.warn(`[expo-ai-error] Could not write ${outputFile}: ${error.message}`);
249
+ + }
250
+ + }
251
+ + };
252
+ + const isCurrentBuild = (buildID, allowMissing = false)=>buildID == null ? allowMissing || activeBuildID == null : activeBuildID == null || buildID === activeBuildID;
253
+ + return {
254
+ + getState() {
255
+ + return state;
256
+ + },
257
+ + handleMetroEvent(event) {
258
+ + if (!event || typeof event !== 'object') return;
259
+ + const type = event.type;
260
+ + const buildID = event.buildID ?? event.bundleDetails?.buildID ?? null;
261
+ + if (type === 'hmr_update_started') {
262
+ + activeHmrChangeID = typeof event.changeID === 'string' ? event.changeID : null;
263
+ + lastFailedBuildID = null;
264
+ + publish({
265
+ + schemaVersion: 1,
266
+ + status: 'compiling',
267
+ + buildID: activeBuildID,
268
+ + error: null
269
+ + });
270
+ + return;
271
+ + }
272
+ + if (type === 'hmr_update_failed') {
273
+ + if (activeHmrChangeID == null || event.changeID !== activeHmrChangeID) return;
274
+ + publish({
275
+ + schemaVersion: 1,
276
+ + status: 'failed',
277
+ + buildID: activeBuildID,
278
+ + error: normalizeError(event.error, projectRoot, activeBuildID)
279
+ + });
280
+ + return;
281
+ + }
282
+ + if (type === 'hmr_update_done') {
283
+ + if (activeHmrChangeID == null || event.changeID !== activeHmrChangeID) return;
284
+ + activeHmrChangeID = null;
285
+ + publish({
286
+ + schemaVersion: 1,
287
+ + status: 'succeeded',
288
+ + buildID: activeBuildID,
289
+ + error: null
290
+ + });
291
+ + return;
292
+ + }
293
+ + if (type === 'bundle_build_started') {
294
+ + activeBuildID = buildID;
295
+ + activeHmrChangeID = null;
296
+ + lastFailedBuildID = null;
297
+ + publish({ schemaVersion: 1, status: 'compiling', buildID, error: null });
298
+ + return;
299
+ + }
300
+ + if (type === 'bundle_build_failed') {
301
+ + if (!isCurrentBuild(buildID)) return;
302
+ + lastFailedBuildID = buildID ?? activeBuildID;
303
+ + publish({
304
+ + schemaVersion: 1,
305
+ + status: 'failed',
306
+ + buildID: lastFailedBuildID,
307
+ + error: normalizeError(event.error ?? { type: 'MetroBundleError', message: 'Metro bundling failed' }, projectRoot, lastFailedBuildID)
308
+ + });
309
+ + return;
310
+ + }
311
+ + if (type === 'bundling_error') {
312
+ + const isHmrError = event.isHmr === true;
313
+ + if (isHmrError && activeHmrChangeID != null && event.changeID !== activeHmrChangeID) return;
314
+ + const errorBuildID = isHmrError ? activeBuildID : buildID ?? lastFailedBuildID ?? activeBuildID;
315
+ + if (!isCurrentBuild(errorBuildID, true)) return;
316
+ + if (isHmrError) {
317
+ + // HMR 错误不携带 Metro build ID,保留变更标识以匹配后续恢复事件。
318
+ + activeHmrChangeID = event.changeID ?? activeHmrChangeID;
319
+ + }
320
+ + lastFailedBuildID = errorBuildID;
321
+ +
322
+ + publish({
323
+ + schemaVersion: 1,
324
+ + status: 'failed',
325
+ + buildID: errorBuildID,
326
+ + error: normalizeError(event.error, projectRoot, errorBuildID)
327
+ + });
328
+ + return;
329
+ + }
330
+ + if (type === 'bundle_build_done') {
331
+ + if (!isCurrentBuild(buildID)) return;
332
+ + activeHmrChangeID = null;
333
+ + lastFailedBuildID = null;
334
+ + publish({ schemaVersion: 1, status: 'succeeded', buildID: buildID ?? activeBuildID, error: null });
335
+ + }
336
+ + }
337
+ + };
338
+ +}
339
+ +
340
+ +//# sourceMappingURL=aiErrorCollector.js.map
341
+ diff --git a/build/src/install/index.js b/build/src/install/index.js
342
+ --- a/build/src/install/index.js
343
+ +++ b/build/src/install/index.js
344
+ @@ -55,8 +55,18 @@
345
+ }
346
+ // Load modules after the help prompt so `npx expo install -h` shows as fast as possible.
347
+ const { installAsync } = require('./installAsync');
348
+ - const { logCmdError } = require('../utils/errors');
349
+ + const { logCmdError, SilentError } = require('../utils/errors');
350
+ const { resolveArgsAsync } = require('./resolveOptions');
351
+ const { variadic, options, extras } = await resolveArgsAsync(process.argv.slice(3)).catch(logCmdError);
352
+ - return installAsync(variadic, options, extras).catch(logCmdError);
353
+ + return installAsync(variadic, options, extras).catch((error)=>{
354
+ + if (
355
+ + error &&
356
+ + typeof error === 'object' &&
357
+ + typeof error.status === 'number' &&
358
+ + Array.isArray(error.output)
359
+ + ) {
360
+ + return logCmdError(new SilentError(error));
361
+ + }
362
+ + return logCmdError(error);
363
+ + });
364
+ };
365
+ diff --git a/build/src/start/server/metro/miaodaHmrControl.js b/build/src/start/server/metro/miaodaHmrControl.js
366
+ new file mode 100644
367
+ --- /dev/null
368
+ +++ b/build/src/start/server/metro/miaodaHmrControl.js
369
+ @@ -0,0 +1,258 @@
370
+ +"use strict";
371
+ +
372
+ +// SDK 55 / Metro 0.83.3. Attach after Expo replaces the HMR serializer.
373
+ +const fs = require('node:fs');
374
+ +const path = require('node:path');
375
+ +const CONTROL = 'miaoda-hmr-control';
376
+ +const ROUTES = new Map([
377
+ + ['/innerapi/v1/sourcecode/__hmr_off', 'pause'],
378
+ + ['/innerapi/v1/sourcecode/__hmr_on', 'resume'],
379
+ + ['/innerapi/v1/sourcecode/__hmr_reload', 'reload'],
380
+ + ['/innerapi/v1/sourcecode/__hmr_status', 'status'],
381
+ +]);
382
+ +
383
+ +function attachMiaodaHmrControl({hmrServer: hmr, metro, middleware, stateFile, timeout = 5000}) {
384
+ + let paused = false;
385
+ + let reloadPending = false;
386
+ + let stateVersion = 0;
387
+ + let lastMode = null;
388
+ + if (stateFile && fs.existsSync(stateFile)) {
389
+ + try {
390
+ + const state = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
391
+ + paused = state.paused === true;
392
+ + reloadPending = state.reloadPending === true;
393
+ + stateVersion = Number.isInteger(state.stateVersion) ? state.stateVersion : 0;
394
+ + lastMode = typeof state.lastMode === 'string' ? state.lastMode : null;
395
+ + } catch {
396
+ + // A damaged state file must not release an unfinished edit batch.
397
+ + paused = true;
398
+ + }
399
+ + }
400
+ + const clients = new Map();
401
+ + const groupTasks = new WeakMap();
402
+ + const results = new WeakMap();
403
+ + const registrations = new Set();
404
+ + let serial = Promise.resolve();
405
+ + let controlId = 0;
406
+ + let restoring = null;
407
+ +
408
+ + function persist() {
409
+ + if (!stateFile) return;
410
+ + fs.mkdirSync(path.dirname(stateFile), {recursive: true});
411
+ + const temp = `${stateFile}.${process.pid}.tmp`;
412
+ + fs.writeFileSync(temp, JSON.stringify({version: 1, paused, reloadPending, stateVersion, lastMode}));
413
+ + fs.renameSync(temp, stateFile);
414
+ + }
415
+ + function control(info, action, extra = {}) {
416
+ + if (info.version === 1) info.send(JSON.stringify({type: CONTROL, version: 1, action, stateVersion, ...extra}));
417
+ + }
418
+ + function transition(mode) {
419
+ + stateVersion += 1;
420
+ + lastMode = mode;
421
+ + }
422
+ + function status() {
423
+ + return {status: 0, paused, reloadPending, stateVersion, lastMode, protocolVersion: 1};
424
+ + }
425
+ + function pauseClient(info) {
426
+ + if (info.version !== 1 || info.ack) return;
427
+ + const id = ++controlId;
428
+ + let resolve;
429
+ + const promise = new Promise(done => { resolve = done; });
430
+ + info.ack = {id, promise, resolve};
431
+ + control(info, 'pause', {id});
432
+ + }
433
+ + function fail(status, message) {
434
+ + const error = new Error(message);
435
+ + error.status = status;
436
+ + throw error;
437
+ + }
438
+ + async function awaitPaused() {
439
+ + if ([...clients.values()].some(info => info.registered && info.version !== 1)) {
440
+ + fail(409, 'Connected client lacks Miaoda HMR protocol v1; update its JS dependencies and reload.');
441
+ + }
442
+ + for (const info of clients.values()) pauseClient(info);
443
+ + let timer;
444
+ + try {
445
+ + await Promise.race([
446
+ + Promise.all([...clients.values()].filter(info => info.ack).map(info => info.ack.promise)),
447
+ + new Promise((_, reject) => {
448
+ + timer = setTimeout(() => {
449
+ + const error = new Error('Timed out waiting for HMR pause acknowledgement; HMR remains paused.');
450
+ + error.status = 504;
451
+ + reject(error);
452
+ + }, timeout);
453
+ + }),
454
+ + ]);
455
+ + } finally { clearTimeout(timer); }
456
+ + }
457
+ +
458
+ + // Gate output immediately, including updates already compiling when pause arrives.
459
+ + const connect = hmr.onClientConnect.bind(hmr);
460
+ + hmr.onClientConnect = async (url, send) => {
461
+ + const info = {send, version: null, registered: false, buffer: [], ack: null};
462
+ + const client = await connect(url, message => {
463
+ + const type = JSON.parse(String(message)).type;
464
+ + if (paused && ['update-start', 'update', 'update-done', 'error'].includes(type)) {
465
+ + info.buffer.push(message);
466
+ + } else send(message);
467
+ + });
468
+ + clients.set(client, info);
469
+ + return client;
470
+ + };
471
+ + const onMessage = hmr.onClientMessage.bind(hmr);
472
+ + hmr.onClientMessage = async (client, message, send) => {
473
+ + let data;
474
+ + try { data = JSON.parse(String(message)); } catch { return onMessage(client, message, send); }
475
+ + const info = clients.get(client);
476
+ + if (data?.type === 'miaoda-hmr-ack' && info) {
477
+ + if (data.version === 1 && data.id === info.ack?.id) info.ack.resolve();
478
+ + return;
479
+ + }
480
+ + if (data?.type === 'register-entrypoints' && info) {
481
+ + // Registration during a resume joins only after that operation has committed.
482
+ + if (restoring) await restoring;
483
+ + info.registered = true;
484
+ + info.version = data.miaodaHmr === 1 ? 1 : null;
485
+ + if (paused) pauseClient(info);
486
+ + else control(info, 'resume');
487
+ + }
488
+ + // Use the gated sender for registration errors as well as graph updates.
489
+ + const task = onMessage(client, message, client.sendFn);
490
+ + if (data?.type !== 'register-entrypoints') return task;
491
+ + registrations.add(task);
492
+ + try { return await task; } finally { registrations.delete(task); }
493
+ + };
494
+ + const disconnect = hmr.onClientDisconnect.bind(hmr);
495
+ + hmr.onClientDisconnect = client => {
496
+ + clients.get(client)?.ack?.resolve();
497
+ + clients.delete(client);
498
+ + return disconnect(client);
499
+ + };
500
+ +
501
+ + const prepare = hmr._prepareMessage.bind(hmr);
502
+ + hmr._prepareMessage = async (...args) => {
503
+ + const result = await prepare(...args);
504
+ + results.set(args[0], result);
505
+ + return result;
506
+ + };
507
+ + const handle = hmr._handleFileChange.bind(hmr);
508
+ + function enqueue(group, options, event, force = false) {
509
+ + const previous = groupTasks.get(group) || Promise.resolve();
510
+ + const task = previous.catch(() => {}).then(async () => {
511
+ + // Leave DeltaCalculator's changes intact. Never drop an already-built revision.
512
+ + if (paused && !force) return;
513
+ + await handle(group, options, event);
514
+ + });
515
+ + groupTasks.set(group, task);
516
+ + return task;
517
+ + }
518
+ + hmr._handleFileChange = (group, options, event) => enqueue(group, options, event);
519
+ +
520
+ + async function run(action) {
521
+ + if (action === 'pause') {
522
+ + paused = true;
523
+ + transition('paused');
524
+ + persist();
525
+ + await awaitPaused();
526
+ + return {status: 0, paused, mode: 'paused'};
527
+ + }
528
+ + if (action === 'reload' && paused) {
529
+ + reloadPending = true;
530
+ + transition('reload-deferred');
531
+ + persist();
532
+ + return {...status(), mode: 'reload-deferred'};
533
+ + }
534
+ + if (action === 'status') return status();
535
+ + if (action === 'resume' && !paused) return {...status(), mode: 'unchanged'};
536
+ + paused = true;
537
+ + if (action === 'reload') reloadPending = true;
538
+ + transition(action === 'reload' ? 'reload-requested' : 'paused');
539
+ + persist();
540
+ + await awaitPaused();
541
+ + let release;
542
+ + restoring = new Promise(done => { release = done; });
543
+ + try {
544
+ + await Promise.all([...registrations]);
545
+ + const graph = await metro.getBundler().getBundler().getDependencyGraph();
546
+ + const fileMap = graph.getWatcher();
547
+ + if (typeof fileMap.miaodaFlushChanges !== 'function') {
548
+ + fail(503, 'Metro file-map barrier patch is missing; HMR remains paused.');
549
+ + }
550
+ + // Watcher cookie -> file-map processing queue -> change event -> final graph build.
551
+ + await fileMap.miaodaFlushChanges(timeout);
552
+ + const groups = [...new Set(hmr._clientGroups.values())];
553
+ + await Promise.all(groups.map(group => enqueue(group, {isInitialUpdate: false}, undefined, true)));
554
+ + const errors = groups.map(group => results.get(group)).filter(result => result?.type === 'error');
555
+ + const staleGraph = error => ['GraphNotFoundError', 'RevisionNotFoundError'].includes(error?.type);
556
+ + const compileErrors = errors.filter(result => !staleGraph(result.body));
557
+ + if (compileErrors.length) {
558
+ + for (const info of clients.values()) control(info, 'error', {error: compileErrors[0].body});
559
+ + fail(422, 'Final code failed to compile; HMR remains paused. Fix the code and retry __hmr_on.');
560
+ + }
561
+ + const reload = reloadPending || errors.some(result => staleGraph(result.body)) || [...clients.values()].some(info => info.buffer.some(message => {
562
+ + const data = JSON.parse(String(message));
563
+ + return data.type === 'error' && staleGraph(data.body);
564
+ + }));
565
+ + // Persist before releasing clients. A persistence failure must leave the gate closed.
566
+ + const previousReloadPending = reloadPending;
567
+ + paused = false;
568
+ + reloadPending = false;
569
+ + transition(reload ? 'reload' : 'fast-refresh');
570
+ + try { persist(); } catch (error) { paused = true; reloadPending = previousReloadPending; throw error; }
571
+ + for (const info of clients.values()) {
572
+ + if (reload) control(info, 'reload');
573
+ + else {
574
+ + for (const message of info.buffer) info.send(message);
575
+ + control(info, 'resume');
576
+ + }
577
+ + info.buffer = [];
578
+ + info.ack = null;
579
+ + }
580
+ + return {...status(), mode: reload ? 'reload' : 'fast-refresh', clients: clients.size};
581
+ + } finally {
582
+ + restoring = null;
583
+ + release();
584
+ + }
585
+ + }
586
+ + const handler = (req, res, next) => {
587
+ + const pathname = new URL(req.url, 'http://localhost').pathname.replace(/\/$/, '');
588
+ + const action = ROUTES.get(pathname);
589
+ + if (!action || req.method === 'OPTIONS') return next();
590
+ + if (action === 'status') {
591
+ + if (req.method !== 'GET') {
592
+ + res.statusCode = 405;
593
+ + res.setHeader('Allow', 'GET');
594
+ + return res.end();
595
+ + }
596
+ + res.statusCode = 200;
597
+ + res.setHeader('Cache-Control', 'no-store');
598
+ + res.setHeader('Content-Type', 'application/json');
599
+ + return res.end(JSON.stringify(status()));
600
+ + }
601
+ + if (req.method !== 'POST') {
602
+ + res.statusCode = 405;
603
+ + res.setHeader('Allow', 'POST');
604
+ + return res.end();
605
+ + }
606
+ + const task = serial.catch(() => {}).then(() => run(action));
607
+ + serial = task;
608
+ + task.then(body => {
609
+ + res.statusCode = body.mode === 'reload-deferred' ? 202 : 200;
610
+ + res.setHeader('Content-Type', 'application/json');
611
+ + res.end(JSON.stringify(body));
612
+ + }, error => {
613
+ + res.statusCode = error.status || 503;
614
+ + res.setHeader('Content-Type', 'application/json');
615
+ + res.end(JSON.stringify({status: 1, paused, msg: error.message}));
616
+ + });
617
+ + };
618
+ + // Metro has a catch-all middleware: append would make these routes unreachable.
619
+ + middleware.use(handler);
620
+ + middleware.stack.unshift(middleware.stack.pop());
621
+ + return {run: action => {
622
+ + const task = serial.catch(() => {}).then(() => run(action));
623
+ + serial = task;
624
+ + return task;
625
+ + }};
626
+ +}
627
+ +module.exports = {attachMiaodaHmrControl};