miaoda-expo-devkit 0.1.1-beta.99 → 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 +90 -90
  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 +9 -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,218 @@
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,7 @@
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 _runServerfork = require("./runServer-fork");
10
+ const _withMetroMultiPlatform = require("./withMetroMultiPlatform");
11
+ const _events = require("../../../events");
12
+ @@ -274,6 +275,7 @@
13
+ const { middleware, messagesSocket, eventsSocket, websocketEndpoints } = (0, _createMetroMiddleware.createMetroMiddleware)(metroConfig, {
14
+ getMetroBundler
15
+ });
16
+ + const aiErrorCollector = !isExporting ? (0, _aiErrorCollector.createAiErrorCollector)(projectRoot) : null;
17
+ // Get local URL to Metro bundler server (typically configured as 127.0.0.1:8081)
18
+ const serverBaseUrl = metroBundler.getUrlCreator().constructUrl({
19
+ scheme: 'http',
20
+ @@ -348,7 +350,10 @@
21
+ }
22
+ }), fileBuffer);
23
+ };
24
+ - setEventReporter(eventsSocket.reportMetroEvent);
25
+ + setEventReporter((event)=>{
26
+ + eventsSocket.reportMetroEvent(event);
27
+ + aiErrorCollector == null ? void 0 : aiErrorCollector.handleMetroEvent(event);
28
+ + });
29
+ // This function ensures that modules in source maps are sorted in the same
30
+ // order as in a plain JS bundle.
31
+ metro._getSortedModules = function(graph) {
32
+ @@ -493,1 +498,1 @@
33
+ -//# sourceMappingURL=instantiateMetro.js.map
34
+
35
+ +//# sourceMappingURL=instantiateMetro.js.map
36
+ diff --git a/build/src/start/server/metro/aiErrorCollector.js b/build/src/start/server/metro/aiErrorCollector.js
37
+ new file mode 100644
38
+ --- /dev/null
39
+ +++ b/build/src/start/server/metro/aiErrorCollector.js
40
+ @@ -0,0 +1,178 @@
41
+ +"use strict";
42
+ +Object.defineProperty(exports, "__esModule", {
43
+ + value: true
44
+ +});
45
+ +Object.defineProperty(exports, "createAiErrorCollector", {
46
+ + enumerable: true,
47
+ + get: function() {
48
+ + return createAiErrorCollector;
49
+ + }
50
+ +});
51
+ +const fs = require("fs");
52
+ +const os = require("os");
53
+ +const path = require("path");
54
+ +
55
+ +const MAX_VALUE_DEPTH = 4;
56
+ +const MAX_STRING_LENGTH = 20000;
57
+ +
58
+ +function isEnabled() {
59
+ + return process.env.EXPO_AI_ERROR_COLLECTOR === '1';
60
+ +}
61
+ +
62
+ +function getOutputPath(projectRoot) {
63
+ + const configured = process.env.EXPO_AI_ERROR_FILE;
64
+ + if (!configured) return path.join(projectRoot, 'node_modules', '.miaoda-expo', 'ai-error.json');
65
+ + return path.isAbsolute(configured) ? configured : path.resolve(projectRoot, configured);
66
+ +}
67
+ +
68
+ +function toSerializable(value, depth = 0, seen = new Set()) {
69
+ + if (value == null || typeof value === 'boolean' || typeof value === 'number') return value;
70
+ + if (typeof value === 'string') return value.length > MAX_STRING_LENGTH ? value.slice(0, MAX_STRING_LENGTH) + '...[truncated]' : value;
71
+ + if (depth >= MAX_VALUE_DEPTH) return '[truncated]';
72
+ + if (typeof value === 'function' || typeof value === 'symbol') return String(value);
73
+ + if (seen.has(value)) return '[circular]';
74
+ + seen.add(value);
75
+ + if (value instanceof Error) {
76
+ + const error = {
77
+ + name: value.name,
78
+ + message: value.message,
79
+ + stack: value.stack
80
+ + };
81
+ + for (const key of Object.keys(value)) error[key] = toSerializable(value[key], depth + 1, seen);
82
+ + seen.delete(value);
83
+ + return error;
84
+ + }
85
+ + if (Array.isArray(value)) {
86
+ + const result = value.map((item)=>toSerializable(item, depth + 1, seen));
87
+ + seen.delete(value);
88
+ + return result;
89
+ + }
90
+ + if (typeof value === 'object') {
91
+ + const result = {};
92
+ + for (const key of Object.keys(value)) result[key] = toSerializable(value[key], depth + 1, seen);
93
+ + seen.delete(value);
94
+ + return result;
95
+ + }
96
+ + return String(value);
97
+ +}
98
+ +
99
+ +function normalizeError(error, projectRoot, buildID) {
100
+ + const raw = error && typeof error === 'object' ? error : {
101
+ + message: String(error ?? 'Metro bundling failed')
102
+ + };
103
+ + const message = String(raw.message ?? raw.reason ?? error ?? 'Metro bundling failed');
104
+ + const record = {
105
+ + schemaVersion: 1,
106
+ + id: `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`,
107
+ + timestamp: new Date().toISOString(),
108
+ + source: 'metro',
109
+ + eventType: 'bundling_error',
110
+ + buildID: buildID ?? undefined,
111
+ + projectRoot,
112
+ + type: String(raw.type ?? raw.name ?? 'MetroError'),
113
+ + name: raw.name == null ? undefined : String(raw.name),
114
+ + message,
115
+ + filename: raw.filename == null ? undefined : String(raw.filename),
116
+ + lineNumber: typeof raw.lineNumber === 'number' ? raw.lineNumber : undefined,
117
+ + column: typeof raw.column === 'number' ? raw.column : undefined,
118
+ + errors: Array.isArray(raw.errors) ? toSerializable(raw.errors) : [],
119
+ + stack: raw.stack == null ? undefined : String(raw.stack),
120
+ + cause: raw.cause == null ? undefined : toSerializable(raw.cause)
121
+ + };
122
+ + const extra = {};
123
+ + for (const key of Object.keys(raw)) {
124
+ + if (!['type', 'name', 'message', 'filename', 'lineNumber', 'column', 'errors', 'stack', 'cause'].includes(key)) {
125
+ + extra[key] = toSerializable(raw[key]);
126
+ + }
127
+ + }
128
+ + if (Object.keys(extra).length) record.extra = extra;
129
+ + return omitUndefined(record);
130
+ +}
131
+ +
132
+ +function omitUndefined(value) {
133
+ + if (!value || typeof value !== 'object') return value;
134
+ + if (Array.isArray(value)) return value.map(omitUndefined);
135
+ + const result = {};
136
+ + for (const [key, item] of Object.entries(value)) {
137
+ + if (item !== undefined) result[key] = omitUndefined(item);
138
+ + }
139
+ + return result;
140
+ +}
141
+ +
142
+ +function createAiErrorCollector(projectRoot) {
143
+ + if (!isEnabled()) return null;
144
+ + const outputFile = getOutputPath(projectRoot);
145
+ + let activeBuildID = null;
146
+ + let lastFailedBuildID = null;
147
+ + let state = {
148
+ + schemaVersion: 1,
149
+ + status: 'compiling',
150
+ + buildID: null,
151
+ + updatedAt: new Date().toISOString(),
152
+ + error: null
153
+ + };
154
+ + let warned = false;
155
+ + const publish = (nextState)=>{
156
+ + state = omitUndefined({ ...nextState, updatedAt: new Date().toISOString() });
157
+ + let temporaryFile;
158
+ + try {
159
+ + fs.mkdirSync(path.dirname(outputFile), { recursive: true });
160
+ + temporaryFile = `${outputFile}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;
161
+ + fs.writeFileSync(temporaryFile, JSON.stringify(state, null, 2) + os.EOL, 'utf8');
162
+ + fs.renameSync(temporaryFile, outputFile);
163
+ + } catch (error) {
164
+ + try {
165
+ + if (temporaryFile) fs.rmSync(temporaryFile, { force: true });
166
+ + } catch {}
167
+ + if (!warned) {
168
+ + warned = true;
169
+ + console.warn(`[expo-ai-error] Could not write ${outputFile}: ${error.message}`);
170
+ + }
171
+ + }
172
+ + };
173
+ + const isCurrentBuild = (buildID, allowMissing = false)=>buildID == null ? allowMissing || activeBuildID == null : activeBuildID == null || buildID === activeBuildID;
174
+ + return {
175
+ + getState() {
176
+ + return state;
177
+ + },
178
+ + handleMetroEvent(event) {
179
+ + if (!event || typeof event !== 'object') return;
180
+ + const type = event.type;
181
+ + const buildID = event.buildID ?? event.bundleDetails?.buildID ?? null;
182
+ + if (type === 'bundle_build_started') {
183
+ + activeBuildID = buildID;
184
+ + lastFailedBuildID = null;
185
+ + publish({ schemaVersion: 1, status: 'compiling', buildID, error: null });
186
+ + return;
187
+ + }
188
+ + if (type === 'bundle_build_failed') {
189
+ + if (!isCurrentBuild(buildID)) return;
190
+ + lastFailedBuildID = buildID ?? activeBuildID;
191
+ + publish({
192
+ + schemaVersion: 1,
193
+ + status: 'failed',
194
+ + buildID: lastFailedBuildID,
195
+ + error: normalizeError(event.error ?? { type: 'MetroBundleError', message: 'Metro bundling failed' }, projectRoot, lastFailedBuildID)
196
+ + });
197
+ + return;
198
+ + }
199
+ + if (type === 'bundling_error') {
200
+ + const errorBuildID = buildID ?? lastFailedBuildID ?? activeBuildID;
201
+ + if (!isCurrentBuild(errorBuildID, true)) return;
202
+ + publish({
203
+ + schemaVersion: 1,
204
+ + status: 'failed',
205
+ + buildID: errorBuildID,
206
+ + error: normalizeError(event.error, projectRoot, errorBuildID)
207
+ + });
208
+ + return;
209
+ + }
210
+ + if (type === 'bundle_build_done') {
211
+ + if (!isCurrentBuild(buildID)) return;
212
+ + publish({ schemaVersion: 1, status: 'succeeded', buildID: buildID ?? activeBuildID, error: null });
213
+ + }
214
+ + }
215
+ + };
216
+ +}
217
+ +
218
+ +//# sourceMappingURL=aiErrorCollector.js.map
@@ -0,0 +1,14 @@
1
+ diff --git a/scripts/install-skia.mjs b/scripts/install-skia.mjs
2
+ index ac4e945f9acb611c43f60a38aaada1ca2b695bb2..d502637ac0e09d8016b2b174c08265237bf4085f 100644
3
+ --- a/scripts/install-skia.mjs
4
+ +++ b/scripts/install-skia.mjs
5
+ @@ -484,7 +484,8 @@ const main = async () => {
6
+ for (const artifactName of names) {
7
+ const assetName = `${artifactName}-${releaseTag}.tar.gz`;
8
+ const extractDir = path.join(artifactsDir, artifactName);
9
+ - const downloadUrl = `https://github.com/${repo}/releases/download/${releaseTag}/${assetName}`;
10
+ + const baseUrl = process.env.SKIA_DOWNLOAD_BASE_URL || `https://github.com/${repo}/releases/download`;
11
+ + const downloadUrl = `${baseUrl}/${releaseTag}/${assetName}`;
12
+
13
+ console.log(` Downloading ${assetName}...`);
14
+ try {
@@ -0,0 +1,21 @@
1
+ --- a/android/cmake/main.cmake
2
+ +++ b/android/cmake/main.cmake
3
+ @@ -73,15 +73,17 @@
4
+
5
+ if (${CMAKE_BUILD_TYPE} MATCHES "Debug")
6
+ set(BUILD_TYPE "debug")
7
+ + set(MERGE_NATIVE_LIBS_DIR "mergeDebugNativeLibs")
8
+ else ()
9
+ set(BUILD_TYPE "release")
10
+ + set(MERGE_NATIVE_LIBS_DIR "mergeReleaseNativeLibs")
11
+ endif ()
12
+
13
+ set_target_properties(
14
+ worklets
15
+ PROPERTIES
16
+ IMPORTED_LOCATION
17
+ - "${REACT_NATIVE_WORKLETS_DIR}/android/build/intermediates/cmake/${BUILD_TYPE}/obj/${ANDROID_ABI}/libworklets.so"
18
+ + "${REACT_NATIVE_WORKLETS_DIR}/android/build/intermediates/merged_native_libs/${BUILD_TYPE}/${MERGE_NATIVE_LIBS_DIR}/out/lib/${ANDROID_ABI}/libworklets.so"
19
+ )
20
+
21
+ target_link_libraries(
@@ -0,0 +1,235 @@
1
+ diff --git a/bundledNativeModules.json b/bundledNativeModules.json
2
+ index e2b8941923b274b1c5c9bda2269d28cf574aff7f..355deae878b654e1e1d9db114ee88c1e7e504276 100644
3
+ --- a/bundledNativeModules.json
4
+ +++ b/bundledNativeModules.json
5
+ @@ -19,7 +19,7 @@
6
+ "expo-apple-authentication": "~55.0.8",
7
+ "expo-application": "~55.0.9",
8
+ "expo-asset": "~55.0.8",
9
+ - "expo-audio": "~55.0.8",
10
+ + "expo-audio": "55.0.6",
11
+ "expo-auth-session": "~55.0.8",
12
+ "expo-background-fetch": "~55.0.9",
13
+ "expo-background-task": "~55.0.9",
14
+ @@ -90,7 +90,7 @@
15
+ "expo-tracking-transparency": "~55.0.8",
16
+ "expo-updates": "~55.0.13",
17
+ "expo-video-thumbnails": "~55.0.10",
18
+ - "expo-video": "~55.0.10",
19
+ + "expo-video": "55.0.5",
20
+ "expo-web-browser": "~55.0.9",
21
+ "jest-expo": "~55.0.9",
22
+ "lottie-react-native": "~7.3.4",
23
+ diff --git a/src/async-require/hmr.ts b/src/async-require/hmr.ts
24
+ --- a/src/async-require/hmr.ts
25
+ +++ b/src/async-require/hmr.ts
26
+ @@ -17,6 +17,7 @@
27
+ handleCompileError,
28
+ hideLoading,
29
+ resetErrorOverlay,
30
+ + reload,
31
+ showLoading,
32
+ } from './hmrUtils';
33
+
34
+ @@ -29,6 +30,20 @@
35
+ let hmrUnavailableReason: string | null = null;
36
+ const buildErrorQueue = new Set<unknown>();
37
+ let didConnect: boolean = false;
38
+ +// Agent pause is separate from the user's native Fast Refresh preference.
39
+ +let miaodaPaused = false;
40
+ +let fastRefreshRequested = false;
41
+ +const MIAODA_HMR_POLL_INTERVAL_MS = 2000;
42
+ +type MiaodaStatusProtocol = 'http' | 'https';
43
+ +let miaodaStatusHost: string | null = null;
44
+ +let miaodaStatusInitialProtocol: MiaodaStatusProtocol = 'http';
45
+ +let miaodaStatusProtocol: MiaodaStatusProtocol | null = null;
46
+ +let miaodaStatusPollingStopped = false;
47
+ +let miaodaPollTimer: ReturnType<typeof setTimeout> | null = null;
48
+ +let miaodaStateVersion: number | null = null;
49
+ +let miaodaLastControlVersion: number | null = null;
50
+ +let miaodaObservedPaused = false;
51
+ +let miaodaWebSocketDisconnected = false;
52
+ const pendingLogs: [LogLevel, any[]][] = [];
53
+
54
+ type LogLevel =
55
+ @@ -42,6 +57,86 @@
56
+ | 'groupEnd'
57
+ | 'debug';
58
+
59
+ +function scheduleMiaodaStatusPoll() {
60
+ + if (process.env.EXPO_OS === 'web' || !miaodaStatusHost || miaodaStatusPollingStopped || miaodaPollTimer != null) return;
61
+ + miaodaPollTimer = setTimeout(() => {
62
+ + miaodaPollTimer = null;
63
+ + void pollMiaodaStatus();
64
+ + }, MIAODA_HMR_POLL_INTERVAL_MS);
65
+ + (miaodaPollTimer as any).unref?.();
66
+ +}
67
+ +
68
+ +function miaodaStatusUrl(protocol: MiaodaStatusProtocol) {
69
+ + return `${protocol}://${miaodaStatusHost}/innerapi/v1/sourcecode/__hmr_status`;
70
+ +}
71
+ +
72
+ +function alternateMiaodaStatusProtocol(protocol: MiaodaStatusProtocol): MiaodaStatusProtocol {
73
+ + return protocol === 'https' ? 'http' : 'https';
74
+ +}
75
+ +
76
+ +async function pollMiaodaStatus() {
77
+ + if (process.env.EXPO_OS === 'web' || !miaodaStatusHost || miaodaStatusPollingStopped) return;
78
+ + let protocol = miaodaStatusProtocol ?? miaodaStatusInitialProtocol;
79
+ + try {
80
+ + let response;
81
+ + try {
82
+ + response = await fetch(miaodaStatusUrl(protocol), {headers: {'Cache-Control': 'no-cache'}});
83
+ + } catch {
84
+ + // Devices may reject development certificates and need HTTP, while other
85
+ + // environments expose HTTPS only. Switch only after a network/SSL failure.
86
+ + protocol = alternateMiaodaStatusProtocol(protocol);
87
+ + response = await fetch(miaodaStatusUrl(protocol), {headers: {'Cache-Control': 'no-cache'}});
88
+ + }
89
+ +
90
+ + if (response.status === 404) {
91
+ + // A 404 is a definitive signal that this Metro instance has no control route.
92
+ + // Stop probing until the WebSocket reconnects, which may point at a new server.
93
+ + miaodaStatusPollingStopped = true;
94
+ + if (miaodaPollTimer != null) {
95
+ + clearTimeout(miaodaPollTimer);
96
+ + miaodaPollTimer = null;
97
+ + }
98
+ + return;
99
+ + }
100
+ + if (!response.ok) {
101
+ + // 5xx and other temporary HTTP failures are retried on the next interval.
102
+ + return;
103
+ + }
104
+ +
105
+ + // Remember the protocol that reached the server for subsequent polls.
106
+ + miaodaStatusProtocol = protocol;
107
+ + const state = (await response.json()) as {
108
+ + paused?: boolean; reloadPending?: boolean; stateVersion?: number; lastMode?: string | null;
109
+ + };
110
+ + const version = Number.isInteger(state.stateVersion) ? state.stateVersion : null;
111
+ + const firstObservation = miaodaStateVersion === null;
112
+ + miaodaStateVersion = version;
113
+ + if (state.paused) {
114
+ + miaodaObservedPaused = true;
115
+ + miaodaPaused = true;
116
+ + hmrClient?.disable();
117
+ + // The observed pause is now our local baseline. A later version without a
118
+ + // matching WebSocket message requires a full Bundle reload.
119
+ + miaodaLastControlVersion = version;
120
+ + } else {
121
+ + const missedControl = !firstObservation && version !== null && version !== miaodaLastControlVersion;
122
+ + const shouldReload = !firstObservation && (state.lastMode === 'reload' || (missedControl && (miaodaObservedPaused || miaodaWebSocketDisconnected)));
123
+ + miaodaObservedPaused = false;
124
+ + miaodaPaused = false;
125
+ + miaodaLastControlVersion = version;
126
+ + if (shouldReload) {
127
+ + reload();
128
+ + return;
129
+ + }
130
+ + if (fastRefreshRequested) HMRClient.enable();
131
+ + }
132
+ + } catch {
133
+ + // Network, timeout and invalid-response failures must not release the edit gate.
134
+ + } finally {
135
+ + scheduleMiaodaStatusPoll();
136
+ + }
137
+ +}
138
+ +
139
+ function assert(foo: any, msg: string): asserts foo {
140
+ if (!foo) throw new Error(msg);
141
+ }
142
+ @@ -52,6 +147,8 @@
143
+ */
144
+ const HMRClient = {
145
+ enable() {
146
+ + fastRefreshRequested = true;
147
+ + if (miaodaPaused) return;
148
+ if (hmrUnavailableReason !== null) {
149
+ // If HMR became unavailable while you weren't using it,
150
+ // explain why when you try to turn it on.
151
+ @@ -87,6 +184,7 @@
152
+ },
153
+
154
+ disable() {
155
+ + fastRefreshRequested = false;
156
+ assert(hmrClient, 'Expected HMRClient.setup() call at startup.');
157
+ hmrClient.disable();
158
+ },
159
+ @@ -180,6 +278,13 @@
160
+ const origin = `${serverScheme}://${serverHost}`;
161
+ const client = new MetroHMRClient(`${origin}/hot`);
162
+ hmrClient = client;
163
+ + if (process.env.EXPO_OS !== 'web') {
164
+ + // Native clients may receive HTTPS bundle URLs with invalid development certificates,
165
+ + // while some deployments expose only HTTPS. Probe the bundle protocol first, then
166
+ + // remember the protocol that succeeds for subsequent polls.
167
+ + miaodaStatusHost = serverHost;
168
+ + miaodaStatusInitialProtocol = serverScheme === 'https' || serverScheme === 'wss' ? 'https' : 'http';
169
+ + }
170
+
171
+ pendingEntryPoints.push(
172
+ // HMRServer understands regular bundle URLs, so prefer that in case
173
+ @@ -192,6 +297,45 @@
174
+ platform: typeof platformOrOptions === 'string' ? platformOrOptions : undefined,
175
+ })
176
+ );
177
+ +
178
+ + client.on('open', () => {
179
+ + miaodaWebSocketDisconnected = false;
180
+ + // A reconnect may target a Metro process with HMR control enabled.
181
+ + miaodaStatusPollingStopped = false;
182
+ + scheduleMiaodaStatusPoll();
183
+ + });
184
+ +
185
+ + client.on('miaoda-hmr-control', (message: {
186
+ + version: number; action: string; id?: number; error?: any; stateVersion?: number;
187
+ + }) => {
188
+ + if (message.version !== 1) return;
189
+ + if (Number.isInteger(message.stateVersion)) {
190
+ + miaodaStateVersion = message.stateVersion!;
191
+ + miaodaLastControlVersion = message.stateVersion!;
192
+ + }
193
+ + switch (message.action) {
194
+ + case 'pause':
195
+ + miaodaPaused = true;
196
+ + client.disable();
197
+ + hideLoading();
198
+ + client.send(JSON.stringify({type: 'miaoda-hmr-ack', version: 1, id: message.id}));
199
+ + break;
200
+ + case 'resume':
201
+ + miaodaPaused = false;
202
+ + if (fastRefreshRequested) HMRClient.enable();
203
+ + break;
204
+ + case 'reload':
205
+ + miaodaPaused = true;
206
+ + client.disable();
207
+ + reload();
208
+ + break;
209
+ + case 'error':
210
+ + // Final compilation failure is actionable even while the edit gate stays closed.
211
+ + hideLoading();
212
+ + handleCompileError(message.error);
213
+ + break;
214
+ + }
215
+ + });
216
+
217
+ client.on('connection-error', (e: Error) => {
218
+ setHMRUnavailableReason(getConnectionError(serverHost, e));
219
+ @@ -234,6 +378,8 @@
220
+ client.on('error', (data) => this._onMetroError(data));
221
+
222
+ client.on('close', (closeEvent?: { code: number; reason: string }) => {
223
+ + miaodaWebSocketDisconnected = true;
224
+ + scheduleMiaodaStatusPoll();
225
+ hideLoading();
226
+ const reason = closeEvent?.reason;
227
+ const code = closeEvent?.code || 1000;
228
+ @@ -326,6 +472,7 @@
229
+ client?.send(
230
+ JSON.stringify({
231
+ type: 'register-entrypoints',
232
+ + miaodaHmr: 1,
233
+ entryPoints: pendingEntryPoints,
234
+ })
235
+ );
@@ -0,0 +1,24 @@
1
+ diff --git a/src/index.js b/src/index.js
2
+ --- a/src/index.js
3
+ +++ b/src/index.js
4
+ @@ -670,6 +670,20 @@
5
+ );
6
+ });
7
+ };
8
+ + // Miaoda edit-batch barrier. The cookie fences watcher delivery; drain file
9
+ + // processing before emitting the changes used by DeltaCalculator.
10
+ + this.miaodaFlushChanges = async (timeout = 5000) => {
11
+ + const result = await this._watcher.checkHealth(timeout);
12
+ + if (result.type !== "success") {
13
+ + throw new Error("Metro watcher barrier failed: " + result.type);
14
+ + }
15
+ + let pending;
16
+ + do {
17
+ + pending = changeQueue;
18
+ + await pending;
19
+ + } while (pending !== changeQueue);
20
+ + emitChange();
21
+ + };
22
+ this._changeInterval = setInterval(emitChange, CHANGE_INTERVAL);
23
+ (0, _invariant.default)(
24
+ this._watcher != null,
@@ -0,0 +1,13 @@
1
+ diff --git a/src/modules/HMRClient.js b/src/modules/HMRClient.js
2
+ --- a/src/modules/HMRClient.js
3
+ +++ b/src/modules/HMRClient.js
4
+ @@ -35,6 +35,9 @@
5
+ this._ws.onmessage = (message) => {
6
+ const data = JSON.parse(String(message.data));
7
+ switch (data.type) {
8
+ + case "miaoda-hmr-control":
9
+ + if (data.version === 1) this.emit("miaoda-hmr-control", data);
10
+ + break;
11
+ case "bundle-registered":
12
+ this.emit("bundle-registered");
13
+ break;
@@ -0,0 +1,12 @@
1
+ diff --git a/dist/css-to-rn/parseDeclaration.js b/dist/css-to-rn/parseDeclaration.js
2
+ index 59ea66e459dbf6922bcd80a6ac014408ca13c2af..088149d6f92dfb8f48bb979f5239c2a420f0dc2d 100644
3
+ --- a/dist/css-to-rn/parseDeclaration.js
4
+ +++ b/dist/css-to-rn/parseDeclaration.js
5
+ @@ -918,6 +918,7 @@ function parseDeclaration(declaration, options) {
6
+ return addStyleProp(declaration.property, parseTextAlign(declaration.value, parseOptions));
7
+ case "box-shadow": {
8
+ parseBoxShadow(declaration.value, parseOptions);
9
+ + return;
10
+ }
11
+ case "aspect-ratio": {
12
+ return addStyleProp(declaration.property, parseAspectRatio(declaration.value));
@@ -0,0 +1,53 @@
1
+ --- a/android/CMakeLists.txt
2
+ +++ b/android/CMakeLists.txt
3
+ @@ -76,8 +76,10 @@
4
+
5
+ if(${CMAKE_BUILD_TYPE} MATCHES "Debug")
6
+ set(BUILD_TYPE "debug")
7
+ + set(MERGE_NATIVE_LIBS_DIR "mergeDebugNativeLibs")
8
+ else()
9
+ set(BUILD_TYPE "release")
10
+ + set(MERGE_NATIVE_LIBS_DIR "mergeReleaseNativeLibs")
11
+ endif()
12
+
13
+ add_library(worklets SHARED IMPORTED)
14
+ @@ -86,7 +88,7 @@
15
+ worklets
16
+ PROPERTIES
17
+ IMPORTED_LOCATION
18
+ - "${REACT_NATIVE_WORKLETS_DIR}/android/build/intermediates/cmake/${BUILD_TYPE}/obj/${ANDROID_ABI}/libworklets.so"
19
+ + "${REACT_NATIVE_WORKLETS_DIR}/android/build/intermediates/merged_native_libs/${BUILD_TYPE}/${MERGE_NATIVE_LIBS_DIR}/out/lib/${ANDROID_ABI}/libworklets.so"
20
+ )
21
+
22
+ set_target_properties(reanimated PROPERTIES LINKER_LANGUAGE CXX)
23
+ --- a/android/build.gradle
24
+ +++ b/android/build.gradle
25
+ @@ -350,7 +350,26 @@
26
+ evaluationDependsOn(":react-native-worklets")
27
+
28
+ afterEvaluate {
29
+ - tasks.getByName("externalNativeBuildDebug").dependsOn(findProject(":react-native-worklets").tasks.getByName("externalNativeBuildDebug"))
30
+ - tasks.getByName("externalNativeBuildRelease").dependsOn(findProject(":react-native-worklets").tasks.getByName("externalNativeBuildRelease"))
31
+ + def workletsTasks = findProject(":react-native-worklets").tasks
32
+ + def workletsDebugNativeLibs = workletsTasks.getByName("mergeDebugNativeLibs")
33
+ + def workletsReleaseNativeLibs = workletsTasks.getByName("mergeReleaseNativeLibs")
34
+ +
35
+ + // CMakeLists.txt imports libworklets.so from the `merged_native_libs` output of
36
+ + // `:react-native-worklets:merge<Variant>NativeLibs`. ninja resolves that file while the
37
+ + // per-ABI `buildCMake<NativeBuildType>[<abi>][<target>]` tasks run, and those tasks are
38
+ + // *upstream* of the `externalNativeBuild<Variant>` umbrella task, so ordering declared on
39
+ + // the umbrella task alone lands too late and the link fails with
40
+ + // "libworklets.so ... missing and no known rule to make it".
41
+ + // Release variants build with CMAKE_BUILD_TYPE=RelWithDebInfo, so their task name is
42
+ + // `buildCMakeRelWithDebInfo[...]`; match on "Debug" and treat every other build type as
43
+ + // release, mirroring the CMAKE_BUILD_TYPE check in CMakeLists.txt.
44
+ + tasks.matching { it.name.startsWith("buildCMake") }.configureEach { nativeBuildTask ->
45
+ + nativeBuildTask.dependsOn(
46
+ + nativeBuildTask.name.contains("Debug") ? workletsDebugNativeLibs : workletsReleaseNativeLibs
47
+ + )
48
+ + }
49
+ +
50
+ + tasks.getByName("externalNativeBuildDebug").dependsOn(workletsDebugNativeLibs)
51
+ + tasks.getByName("externalNativeBuildRelease").dependsOn(workletsReleaseNativeLibs)
52
+ }
53
+ }