miaoda-expo-devkit 0.1.1-beta.99 → 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 +163 -111
- package/dist/babel/plugin-lucide-react-native.js +185 -185
- package/dist/cli/lint.js +317 -309
- package/dist/index.d.mts +1 -110
- package/dist/index.d.ts +1 -110
- package/dist/index.js +0 -159
- package/dist/index.mjs +0 -132
- package/dist/metro.d.mts +25 -5
- package/dist/metro.d.ts +25 -5
- package/dist/metro.js +49 -18
- package/dist/metro.mjs +50 -20
- package/dist/rules/no-bare-web-crypto.js +235 -0
- package/dist/rules/no-expo-audio-compat.js +90 -90
- package/dist/rules/no-expo-file-system-compat.js +2130 -0
- package/dist/rules/no-expo-video-compat.js +90 -90
- 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/dist/stubs/entry-inject.js +0 -2
- package/dist/stubs/expo-native-wechat-stub.js +116 -0
- package/dist/stubs/expo-router-entry-native-stub.js +4 -0
- package/dist/stubs/i18n/en.js +16 -1
- package/dist/stubs/i18n/zh.js +16 -1
- package/dist/stubs/logbox-control.js +9 -0
- package/dist/stubs/screenshot-control.js +1 -1
- package/dist/stubs/sentry-react-native-stub.js +15 -180
- package/oxlint-config.json +20 -5
- package/package.json +65 -5
- package/pnpm-patches/@expo__cli@55.0.16-55.0.22.patch +712 -0
- package/pnpm-patches/@expo__cli@55.0.16.patch +218 -0
- package/pnpm-patches/@expo__cli@55.0.17.patch +218 -0
- package/pnpm-patches/@expo__cli@55.0.18.patch +218 -0
- package/pnpm-patches/@expo__cli@55.0.19.patch +218 -0
- package/pnpm-patches/@expo__cli@55.0.20.patch +218 -0
- package/pnpm-patches/@expo__cli@55.0.21.patch +218 -0
- package/pnpm-patches/@expo__cli@55.0.22.patch +218 -0
- package/pnpm-patches/@shopify__react-native-skia@2.4.18.patch +14 -0
- package/pnpm-patches/expo-modules-core@55.0.15.patch +21 -0
- package/pnpm-patches/expo@55.0.6.patch +283 -0
- package/pnpm-patches/metro-file-map@0.83.3.patch +28 -0
- package/pnpm-patches/metro-runtime@0.83.3.patch +17 -0
- package/pnpm-patches/react-native-css-interop@0.2.6.patch +12 -0
- package/pnpm-patches/react-native-reanimated@4.2.1.patch +53 -0
- package/pnpm-patches/react-native-worklets@0.7.2.patch +91 -0
- package/pnpm-patches/react-native-worklets@0.7.4.patch +91 -0
- package/pnpm-patches/react-native@0.83.2.patch +384 -0
- package/pnpmfile.cjs +251 -0
- 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,283 @@
|
|
|
1
|
+
diff --git a/bundledNativeModules.json b/bundledNativeModules.json
|
|
2
|
+
index e2b8941..355deae 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
|
+
index c0ea71e..953cf53 100644
|
|
25
|
+
--- a/src/async-require/hmr.ts
|
|
26
|
+
+++ b/src/async-require/hmr.ts
|
|
27
|
+
@@ -17,6 +17,7 @@ import {
|
|
28
|
+
handleCompileError,
|
|
29
|
+
hideLoading,
|
|
30
|
+
resetErrorOverlay,
|
|
31
|
+
+ reload,
|
|
32
|
+
showLoading,
|
|
33
|
+
} from './hmrUtils';
|
|
34
|
+
|
|
35
|
+
@@ -29,6 +30,38 @@ let hmrClient: MetroHMRClient | null = null;
|
|
36
|
+
let hmrUnavailableReason: string | null = null;
|
|
37
|
+
const buildErrorQueue = new Set<unknown>();
|
|
38
|
+
let didConnect: boolean = false;
|
|
39
|
+
+// ---- Miaoda HMR 批量编辑控制(客户端侧)----
|
|
40
|
+
+// miaodaPaused 是 Agent 的编辑闸门,与用户自己的 Fast Refresh 开关
|
|
41
|
+
+// (fastRefreshRequested)相互独立:放行后只有用户开着 Fast Refresh 才恢复更新。
|
|
42
|
+
+let miaodaPaused = false;
|
|
43
|
+
+let fastRefreshRequested = false;
|
|
44
|
+
+// 断线兜底轮询间隔。控制消息走 /hot WebSocket,轮询只用于 socket 断开期间的
|
|
45
|
+
+// 状态同步,不需要很频繁。
|
|
46
|
+
+const MIAODA_HMR_POLL_INTERVAL_MS = 2000;
|
|
47
|
+
+type MiaodaStatusProtocol = 'http' | 'https';
|
|
48
|
+
+let miaodaStatusHost: string | null = null;
|
|
49
|
+
+// 首次探测使用的协议,取 bundle URL 的 scheme(设备怎么加载 bundle 就先试什么协议)。
|
|
50
|
+
+let miaodaStatusInitialProtocol: MiaodaStatusProtocol = 'http';
|
|
51
|
+
+// 自适应成功后记住的协议;null 表示还没有成功过,每轮都先试初始协议。
|
|
52
|
+
+let miaodaStatusProtocol: MiaodaStatusProtocol | null = null;
|
|
53
|
+
+// 收到明确 404(服务端未启用控制路由)后停止轮询;WebSocket 重连时重置,
|
|
54
|
+
+// 因为重连对面的 Metro 可能换了一个开启了控制的进程。
|
|
55
|
+
+let miaodaStatusPollingStopped = false;
|
|
56
|
+
+let miaodaPollTimer: ReturnType<typeof setTimeout> | null = null;
|
|
57
|
+
+// 轮询最近一次见到的服务端状态版本。
|
|
58
|
+
+let miaodaStateVersion: number | null = null;
|
|
59
|
+
+// 已通过 WebSocket 控制消息同步到的版本。轮询发现比它新的版本,说明 socket
|
|
60
|
+
+// 断开期间错过了控制消息,增量 revision 的连续性无法证明,必须完整 reload。
|
|
61
|
+
+let miaodaLastControlVersion: number | null = null;
|
|
62
|
+
+// 本次 JS 会话首次观察到的服务端版本(WebSocket 或轮询,谁先到用谁),此后
|
|
63
|
+
+// 不再更新。新会话的 bundle 刚从服务器取回,内容本身就对应该时刻的服务端
|
|
64
|
+
+// 状态,因此它是「当前运行代码已知完好」的锚点:之后版本号超过它且
|
|
65
|
+
+// lastMode 为 reload,才说明本会话错过了一次必须完整加载的迁移。
|
|
66
|
+
+let miaodaSessionBaseVersion: number | null = null;
|
|
67
|
+
+// 轮询曾观察到 paused=true:之后的版本变化即使 lastMode 不是 reload
|
|
68
|
+
+// 也要按 reload 处理(暂停期间错过的批量编辑不能靠增量补齐)。
|
|
69
|
+
+let miaodaObservedPaused = false;
|
|
70
|
+
+let miaodaWebSocketDisconnected = false;
|
|
71
|
+
const pendingLogs: [LogLevel, any[]][] = [];
|
|
72
|
+
|
|
73
|
+
type LogLevel =
|
|
74
|
+
@@ -42,6 +75,100 @@ type LogLevel =
|
|
75
|
+
| 'groupEnd'
|
|
76
|
+
| 'debug';
|
|
77
|
+
|
|
78
|
+
+function scheduleMiaodaStatusPoll() {
|
|
79
|
+
+ // Web 端不轮询(跨域响应会被浏览器拦截,且没有必要);无 host、已 404 停止、
|
|
80
|
+
+ // 或已有定时器在跑时都直接跳过。unref 避免定时器阻止进程退出(如 Jest 环境)。
|
|
81
|
+
+ if (process.env.EXPO_OS === 'web' || !miaodaStatusHost || miaodaStatusPollingStopped || miaodaPollTimer != null) return;
|
|
82
|
+
+ miaodaPollTimer = setTimeout(() => {
|
|
83
|
+
+ miaodaPollTimer = null;
|
|
84
|
+
+ void pollMiaodaStatus();
|
|
85
|
+
+ }, MIAODA_HMR_POLL_INTERVAL_MS);
|
|
86
|
+
+ (miaodaPollTimer as any).unref?.();
|
|
87
|
+
+}
|
|
88
|
+
+
|
|
89
|
+
+function miaodaStatusUrl(protocol: MiaodaStatusProtocol) {
|
|
90
|
+
+ return `${protocol}://${miaodaStatusHost}/innerapi/v1/sourcecode/__hmr_status`;
|
|
91
|
+
+}
|
|
92
|
+
+
|
|
93
|
+
+function alternateMiaodaStatusProtocol(protocol: MiaodaStatusProtocol): MiaodaStatusProtocol {
|
|
94
|
+
+ return protocol === 'https' ? 'http' : 'https';
|
|
95
|
+
+}
|
|
96
|
+
+
|
|
97
|
+
+async function pollMiaodaStatus() {
|
|
98
|
+
+ if (process.env.EXPO_OS === 'web' || !miaodaStatusHost || miaodaStatusPollingStopped) return;
|
|
99
|
+
+ let protocol = miaodaStatusProtocol ?? miaodaStatusInitialProtocol;
|
|
100
|
+
+ try {
|
|
101
|
+
+ let response;
|
|
102
|
+
+ try {
|
|
103
|
+
+ response = await fetch(miaodaStatusUrl(protocol), {headers: {'Cache-Control': 'no-cache'}});
|
|
104
|
+
+ } catch {
|
|
105
|
+
+ // 协议自适应:部分开发环境的 HTTPS 证书无效、设备会拒绝,需要降级 HTTP;
|
|
106
|
+
+ // 也有环境只暴露 HTTPS。只有网络/证书类失败(fetch reject)才换协议重试,
|
|
107
|
+
+ // HTTP 状态码不算——那是服务器给出的确定答复。
|
|
108
|
+
+ protocol = alternateMiaodaStatusProtocol(protocol);
|
|
109
|
+
+ response = await fetch(miaodaStatusUrl(protocol), {headers: {'Cache-Control': 'no-cache'}});
|
|
110
|
+
+ }
|
|
111
|
+
+
|
|
112
|
+
+ if (response.status === 404) {
|
|
113
|
+
+ // 404 是确定性信号:这个 Metro 实例没有挂载控制路由(未开启
|
|
114
|
+
+ // EXPO_MIAODA_HMR_CONTROL)。停止探测直到 WebSocket 重连,
|
|
115
|
+
+ // 避免对普通开发服务器永久空轮询。注意不要在 404 时换协议重试。
|
|
116
|
+
+ miaodaStatusPollingStopped = true;
|
|
117
|
+
+ if (miaodaPollTimer != null) {
|
|
118
|
+
+ clearTimeout(miaodaPollTimer);
|
|
119
|
+
+ miaodaPollTimer = null;
|
|
120
|
+
+ }
|
|
121
|
+
+ return;
|
|
122
|
+
+ }
|
|
123
|
+
+ if (!response.ok) {
|
|
124
|
+
+ // 5xx 等暂时性 HTTP 失败不停止轮询,下一轮继续重试。
|
|
125
|
+
+ return;
|
|
126
|
+
+ }
|
|
127
|
+
+
|
|
128
|
+
+ // 记住本次到达服务器的协议,后续轮询固定使用它。
|
|
129
|
+
+ miaodaStatusProtocol = protocol;
|
|
130
|
+
+ const state = (await response.json()) as {
|
|
131
|
+
+ paused?: boolean; reloadPending?: boolean; stateVersion?: number; lastMode?: string | null;
|
|
132
|
+
+ };
|
|
133
|
+
+ // Number.isInteger 不是 TS 类型守卫,先收窄 number 再判整数,
|
|
134
|
+
+ // 使 version 的类型收敛为 number | null。
|
|
135
|
+
+ const version = typeof state.stateVersion === 'number' && Number.isInteger(state.stateVersion) ? state.stateVersion : null;
|
|
136
|
+
+ const firstObservation = miaodaStateVersion === null;
|
|
137
|
+
+ miaodaStateVersion = version;
|
|
138
|
+
+ if (miaodaSessionBaseVersion === null && version !== null) miaodaSessionBaseVersion = version;
|
|
139
|
+
+ if (state.paused) {
|
|
140
|
+
+ miaodaObservedPaused = true;
|
|
141
|
+
+ miaodaPaused = true;
|
|
142
|
+
+ hmrClient?.disable();
|
|
143
|
+
+ // 把观察到的这次暂停作为本地基线版本。之后若出现没有对应 WebSocket
|
|
144
|
+
+ // 控制消息的新版本,说明错过了状态迁移,必须完整 Bundle reload。
|
|
145
|
+
+ miaodaLastControlVersion = version;
|
|
146
|
+
+ } else {
|
|
147
|
+
+ // 错过控制消息且(曾处于暂停或 socket 断连中)→ 增量连续性无法证明,
|
|
148
|
+
+ // 只能 reload。lastMode === 'reload' 则必须再满足「版本号相对本次会话
|
|
149
|
+
+ // 基线前进过」才视为未执行的 reload 指令:服务端会把 lastMode 持久化并
|
|
150
|
+
+ // 一直原样返回,若不做版本门控,已经执行过 reload 的客户端重连后会反复
|
|
151
|
+
+ // 执行同一条指令,陷入无限重载循环。
|
|
152
|
+
+ const missedControl = !firstObservation && version !== null && version !== miaodaLastControlVersion;
|
|
153
|
+
+ const missedReload = version !== null && miaodaSessionBaseVersion !== null && version > miaodaSessionBaseVersion && state.lastMode === 'reload';
|
|
154
|
+
+ const shouldReload = missedReload || (missedControl && (miaodaObservedPaused || miaodaWebSocketDisconnected));
|
|
155
|
+
+ miaodaObservedPaused = false;
|
|
156
|
+
+ miaodaPaused = false;
|
|
157
|
+
+ miaodaLastControlVersion = version;
|
|
158
|
+
+ if (shouldReload) {
|
|
159
|
+
+ reload();
|
|
160
|
+
+ return;
|
|
161
|
+
+ }
|
|
162
|
+
+ if (fastRefreshRequested) HMRClient.enable();
|
|
163
|
+
+ }
|
|
164
|
+
+ } catch {
|
|
165
|
+
+ // 网络错误、超时、响应解析失败都属于暂时故障:不解除编辑闸门,
|
|
166
|
+
+ // 也不停止轮询,下一轮继续。
|
|
167
|
+
+ } finally {
|
|
168
|
+
+ scheduleMiaodaStatusPoll();
|
|
169
|
+
+ }
|
|
170
|
+
+}
|
|
171
|
+
+
|
|
172
|
+
function assert(foo: any, msg: string): asserts foo {
|
|
173
|
+
if (!foo) throw new Error(msg);
|
|
174
|
+
}
|
|
175
|
+
@@ -52,6 +179,9 @@ function assert(foo: any, msg: string): asserts foo {
|
|
176
|
+
*/
|
|
177
|
+
const HMRClient = {
|
|
178
|
+
enable() {
|
|
179
|
+
+ fastRefreshRequested = true;
|
|
180
|
+
+ // Agent 编辑闸门优先于用户操作:暂停期间只记录意图,等 resume 再恢复。
|
|
181
|
+
+ if (miaodaPaused) return;
|
|
182
|
+
if (hmrUnavailableReason !== null) {
|
|
183
|
+
// If HMR became unavailable while you weren't using it,
|
|
184
|
+
// explain why when you try to turn it on.
|
|
185
|
+
@@ -87,6 +217,7 @@ const HMRClient = {
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
disable() {
|
|
189
|
+
+ fastRefreshRequested = false;
|
|
190
|
+
assert(hmrClient, 'Expected HMRClient.setup() call at startup.');
|
|
191
|
+
hmrClient.disable();
|
|
192
|
+
},
|
|
193
|
+
@@ -180,6 +311,13 @@ const HMRClient = {
|
|
194
|
+
const origin = `${serverScheme}://${serverHost}`;
|
|
195
|
+
const client = new MetroHMRClient(`${origin}/hot`);
|
|
196
|
+
hmrClient = client;
|
|
197
|
+
+ if (process.env.EXPO_OS !== 'web') {
|
|
198
|
+
+ // 仅 native 客户端启用状态轮询。初始协议跟随 bundle URL 的 scheme:
|
|
199
|
+
+ // 设备可能拿到证书无效的 HTTPS 开发地址,也有环境只暴露 HTTPS,
|
|
200
|
+
+ // 因此先按 bundle 协议探测,成功后记住可用的那个协议。
|
|
201
|
+
+ miaodaStatusHost = serverHost;
|
|
202
|
+
+ miaodaStatusInitialProtocol = serverScheme === 'https' || serverScheme === 'wss' ? 'https' : 'http';
|
|
203
|
+
+ }
|
|
204
|
+
|
|
205
|
+
pendingEntryPoints.push(
|
|
206
|
+
// HMRServer understands regular bundle URLs, so prefer that in case
|
|
207
|
+
@@ -193,6 +331,55 @@ const HMRClient = {
|
|
208
|
+
})
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
+ client.on('open', () => {
|
|
212
|
+
+ miaodaWebSocketDisconnected = false;
|
|
213
|
+
+ // 重连对面的 Metro 可能是另一个进程(例如这次开启了 HMR 控制),
|
|
214
|
+
+ // 重置 404 停止标志,重新探测一次。
|
|
215
|
+
+ miaodaStatusPollingStopped = false;
|
|
216
|
+
+ scheduleMiaodaStatusPoll();
|
|
217
|
+
+ });
|
|
218
|
+
+
|
|
219
|
+
+ // 服务端编辑闸门控制消息(协议 v1)。stateVersion 随每条消息捎带,
|
|
220
|
+
+ // 收到即同步本地版本基线,轮询兜底据此判断有没有丢消息。
|
|
221
|
+
+ client.on('miaoda-hmr-control', (message: {
|
|
222
|
+
+ version: number; action: string; id?: number; error?: any; stateVersion?: number;
|
|
223
|
+
+ }) => {
|
|
224
|
+
+ if (message.version !== 1) return;
|
|
225
|
+
+ if (Number.isInteger(message.stateVersion)) {
|
|
226
|
+
+ if (miaodaSessionBaseVersion === null) miaodaSessionBaseVersion = message.stateVersion!;
|
|
227
|
+
+ miaodaStateVersion = message.stateVersion!;
|
|
228
|
+
+ miaodaLastControlVersion = message.stateVersion!;
|
|
229
|
+
+ }
|
|
230
|
+
+ switch (message.action) {
|
|
231
|
+
+ case 'pause':
|
|
232
|
+
+ // 立即闸住 HMR 并隐藏刷新提示,然后回 ack——服务端要等所有客户端
|
|
233
|
+
+ // ack 后才认为暂停完成,ack 之前 Agent 不会开始写文件。
|
|
234
|
+
+ miaodaPaused = true;
|
|
235
|
+
+ client.disable();
|
|
236
|
+
+ hideLoading();
|
|
237
|
+
+ client.send(JSON.stringify({type: 'miaoda-hmr-ack', version: 1, id: message.id}));
|
|
238
|
+
+ break;
|
|
239
|
+
+ case 'resume':
|
|
240
|
+
+ // 解除 Agent 闸门;用户自己的 Fast Refresh 开关保持独立,
|
|
241
|
+
+ // 只有它开着才真正恢复更新。
|
|
242
|
+
+ miaodaPaused = false;
|
|
243
|
+
+ if (fastRefreshRequested) HMRClient.enable();
|
|
244
|
+
+ break;
|
|
245
|
+
+ case 'reload':
|
|
246
|
+
+ // 完整加载前也保持闸门,避免 reload 完成前又有增量进来。
|
|
247
|
+
+ miaodaPaused = true;
|
|
248
|
+
+ client.disable();
|
|
249
|
+
+ reload();
|
|
250
|
+
+ break;
|
|
251
|
+
+ case 'error':
|
|
252
|
+
+ // 批次最终编译失败:闸门仍然关着(服务端等 Agent 修好重试),
|
|
253
|
+
+ // 但错误本身要展示出来,因为它在设备端是可行动的。
|
|
254
|
+
+ hideLoading();
|
|
255
|
+
+ handleCompileError(message.error);
|
|
256
|
+
+ break;
|
|
257
|
+
+ }
|
|
258
|
+
+ });
|
|
259
|
+
+
|
|
260
|
+
client.on('connection-error', (e: Error) => {
|
|
261
|
+
setHMRUnavailableReason(getConnectionError(serverHost, e));
|
|
262
|
+
});
|
|
263
|
+
@@ -234,6 +421,10 @@ const HMRClient = {
|
|
264
|
+
client.on('error', (data) => this._onMetroError(data));
|
|
265
|
+
|
|
266
|
+
client.on('close', (closeEvent?: { code: number; reason: string }) => {
|
|
267
|
+
+ // 控制通道断开:之后的版本变化都无法通过 socket 同步,
|
|
268
|
+
+ // 启动轮询兜底直到重连。
|
|
269
|
+
+ miaodaWebSocketDisconnected = true;
|
|
270
|
+
+ scheduleMiaodaStatusPoll();
|
|
271
|
+
hideLoading();
|
|
272
|
+
const reason = closeEvent?.reason;
|
|
273
|
+
const code = closeEvent?.code || 1000;
|
|
274
|
+
@@ -326,6 +517,9 @@ function registerBundleEntryPoints(client: MetroHMRClient | null) {
|
|
275
|
+
client?.send(
|
|
276
|
+
JSON.stringify({
|
|
277
|
+
type: 'register-entrypoints',
|
|
278
|
+
+ // Miaoda 控制协议 v1 的协商标记:带此标记的客户端才会收到
|
|
279
|
+
+ // miaoda-hmr-control 消息;不带标记的老客户端会触发服务端 409。
|
|
280
|
+
+ miaodaHmr: 1,
|
|
281
|
+
entryPoints: pendingEntryPoints,
|
|
282
|
+
})
|
|
283
|
+
);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
diff --git a/src/index.js b/src/index.js
|
|
2
|
+
index ec21a55..b8a8f68 100644
|
|
3
|
+
--- a/src/index.js
|
|
4
|
+
+++ b/src/index.js
|
|
5
|
+
@@ -670,6 +670,23 @@ class FileMap extends _events.default {
|
|
6
|
+
);
|
|
7
|
+
});
|
|
8
|
+
};
|
|
9
|
+
+ // Miaoda 批量编辑栅栏:resume 时用来证明「watcher 已经看到了 Agent 的
|
|
10
|
+
+ // 最后一次写入」。checkHealth 的 cookie 沿 watcher 通道走一遍,返回即代表
|
|
11
|
+
+ // 此前所有文件事件都已投递到本进程;再把 changeQueue 排空(处理中的事件
|
|
12
|
+
+ // 可能继续入队新任务,所以循环到队列不再变化);最后 emitChange() 立即
|
|
13
|
+
+ // 发出累积的变更,让 DeltaCalculator 不等 CHANGE_INTERVAL 就能看到它们。
|
|
14
|
+
+ this.miaodaFlushChanges = async (timeout = 5000) => {
|
|
15
|
+
+ const result = await this._watcher.checkHealth(timeout);
|
|
16
|
+
+ if (result.type !== "success") {
|
|
17
|
+
+ throw new Error("Metro watcher barrier failed: " + result.type);
|
|
18
|
+
+ }
|
|
19
|
+
+ let pending;
|
|
20
|
+
+ do {
|
|
21
|
+
+ pending = changeQueue;
|
|
22
|
+
+ await pending;
|
|
23
|
+
+ } while (pending !== changeQueue);
|
|
24
|
+
+ emitChange();
|
|
25
|
+
+ };
|
|
26
|
+
this._changeInterval = setInterval(emitChange, CHANGE_INTERVAL);
|
|
27
|
+
(0, _invariant.default)(
|
|
28
|
+
this._watcher != null,
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
diff --git a/src/modules/HMRClient.js b/src/modules/HMRClient.js
|
|
2
|
+
index 3e2652d..8225714 100644
|
|
3
|
+
--- a/src/modules/HMRClient.js
|
|
4
|
+
+++ b/src/modules/HMRClient.js
|
|
5
|
+
@@ -35,6 +35,12 @@ class HMRClient extends EventEmitter {
|
|
6
|
+
this._ws.onmessage = (message) => {
|
|
7
|
+
const data = JSON.parse(String(message.data));
|
|
8
|
+
switch (data.type) {
|
|
9
|
+
+ // Miaoda HMR 批量编辑控制:服务端复用这条 /hot socket 下发
|
|
10
|
+
+ // 控制消息。HMRClient 只做透传转发,具体处理在 expo 包的
|
|
11
|
+
+ // async-require/hmr.ts(该补丁同时加了版本协商,version !== 1 直接忽略)。
|
|
12
|
+
+ case "miaoda-hmr-control":
|
|
13
|
+
+ if (data.version === 1) this.emit("miaoda-hmr-control", data);
|
|
14
|
+
+ break;
|
|
15
|
+
case "bundle-registered":
|
|
16
|
+
this.emit("bundle-registered");
|
|
17
|
+
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
|
+
}
|