miaoda-expo-devkit 0.1.1-beta.30 → 0.1.1-beta.32
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 +63 -7
- package/dist/metro.d.mts +19 -1
- package/dist/metro.d.ts +19 -1
- package/dist/metro.js +26 -3
- package/dist/metro.mjs +25 -3
- package/dist/stubs/expo-media-library-stub.js +108 -0
- package/dist/stubs/expo-notifications-stub.js +10 -1
- package/dist/stubs/web-stub-dialog.js +156 -0
- package/package.json +2 -1
- package/pnpm-config.json +1 -6
package/README.md
CHANGED
|
@@ -7,6 +7,8 @@ Expo / React Native 开发环境工具集,通过 Metro 构建层注入以下
|
|
|
7
7
|
- **Bundle 首部注入** — 在 expo-router 初始化之前执行自定义脚本
|
|
8
8
|
- **HMR postMessage 控制** — 通过 `window.postMessage` 在运行时启动或停止 Fast Refresh
|
|
9
9
|
- **LogBox 屏蔽** — web 平台禁用 Expo 全屏错误遮罩
|
|
10
|
+
- **expo-notifications stub** — Expo Go(Android)中提供 no-op 实现,核心 API 调用时弹出带参数校验的调试 Alert,Dev Build 透传真实模块
|
|
11
|
+
- **expo-media-library stub** — Expo Go / Web 中提供 no-op 实现,`saveToLibraryAsync`、`createAssetAsync`、权限请求等 API 调用时弹出 Alert 提示,Dev Build 原生环境透传真实模块
|
|
10
12
|
|
|
11
13
|
## 安装
|
|
12
14
|
|
|
@@ -32,16 +34,16 @@ pnpm install
|
|
|
32
34
|
```js
|
|
33
35
|
// metro.config.js
|
|
34
36
|
const { getDefaultConfig } = require('expo/metro-config');
|
|
35
|
-
const {
|
|
37
|
+
const { withDevkit } = require('miaoda-expo-devkit/metro');
|
|
36
38
|
|
|
37
|
-
|
|
38
|
-
module.exports = withEntryInjection(withDevStubs(config));
|
|
39
|
+
module.exports = withDevkit(getDefaultConfig(__dirname));
|
|
39
40
|
```
|
|
40
41
|
|
|
41
|
-
|
|
42
|
+
`withDevkit` 已内置所有 wrapper(含 expo-notifications、expo-media-library stub),无需手动叠加。也可单独使用各 wrapper:
|
|
42
43
|
|
|
43
44
|
```js
|
|
44
|
-
|
|
45
|
+
const { withDevStubs, withEntryInjection, withExpoMediaLibraryStub } = require('miaoda-expo-devkit/metro');
|
|
46
|
+
module.exports = withExpoMediaLibraryStub(withEntryInjection(withDevStubs(config)));
|
|
45
47
|
```
|
|
46
48
|
|
|
47
49
|
### Sentry 初始化
|
|
@@ -225,12 +227,22 @@ expect(onError).toHaveBeenCalledWith(
|
|
|
225
227
|
|
|
226
228
|
```
|
|
227
229
|
metro.config.js
|
|
228
|
-
└─
|
|
230
|
+
└─ withDevkit(config)
|
|
229
231
|
│
|
|
230
232
|
├─ withDevStubs → resolver.resolveRequest
|
|
231
233
|
│ ├─ @sentry/react-native → dist/stubs/sentry-react-native-stub.js (全平台)
|
|
232
234
|
│ └─ @expo/log-box → dist/stubs/no-op-logbox.js (仅 web)
|
|
233
235
|
│
|
|
236
|
+
├─ withExpoNotificationsStub → resolver.resolveRequest
|
|
237
|
+
│ └─ expo-notifications → dist/stubs/expo-notifications-stub.js (仅 Android)
|
|
238
|
+
│ ├─ Expo Go:no-op + 调试 Alert(含参数校验)
|
|
239
|
+
│ └─ Dev Build:透传真实 expo-notifications
|
|
240
|
+
│
|
|
241
|
+
├─ withExpoMediaLibraryStub → resolver.resolveRequest
|
|
242
|
+
│ └─ expo-media-library → dist/stubs/expo-media-library-stub.js (全平台)
|
|
243
|
+
│ ├─ Expo Go / Web:no-op + Alert 提示(不崩溃)
|
|
244
|
+
│ └─ Dev Build(原生):透传真实 expo-media-library
|
|
245
|
+
│
|
|
234
246
|
└─ withEntryInjection → resolver.resolveRequest
|
|
235
247
|
└─ expo-router/entry-classic → dist/stubs/expo-router-entry-stub.js
|
|
236
248
|
├─ require('./entry-inject') ← 注入脚本(bundle 首部执行)
|
|
@@ -262,11 +274,13 @@ sentry-react-native-stub.js
|
|
|
262
274
|
| 子路径 | 文件 | 内容 |
|
|
263
275
|
|---|---|---|
|
|
264
276
|
| `.` | `dist/index.js` | `SentryCapture`、`MetroSymbolicator`、全部类型 |
|
|
265
|
-
| `./metro` | `dist/metro.js` | `withDevStubs`、`withEntryInjection` |
|
|
277
|
+
| `./metro` | `dist/metro.js` | `withDevkit`、`withDevStubs`、`withEntryInjection`、`withExpoNotificationsStub`、`withExpoMediaLibraryStub` 等 |
|
|
266
278
|
| `./babel-plugin-jsx-source` | `dist/babel/plugin-jsx-source.js` | Babel 插件:为 JSX 注入 source 信息 |
|
|
267
279
|
| `./babel-preset` | `dist/babel/preset.js` | Babel Preset:集成 jsx-source 和 lucide 插件 |
|
|
268
280
|
| `./sentry-react-native-stub` | `dist/stubs/sentry-react-native-stub.js` | `@sentry/react-native` 模块替换 stub |
|
|
269
281
|
| `./no-op-logbox` | `dist/stubs/no-op-logbox.js` | LogBox no-op stub |
|
|
282
|
+
| `./expo-notifications-stub` | `dist/stubs/expo-notifications-stub.js` | `expo-notifications` Expo Go Android stub |
|
|
283
|
+
| `./expo-media-library-stub` | `dist/stubs/expo-media-library-stub.js` | `expo-media-library` Expo Go / Web stub |
|
|
270
284
|
|
|
271
285
|
---
|
|
272
286
|
|
|
@@ -389,6 +403,48 @@ LogBox no-op stub,用于 web 平台禁用 Expo 全屏错误遮罩。
|
|
|
389
403
|
|
|
390
404
|
---
|
|
391
405
|
|
|
406
|
+
### expo-notifications-stub.js
|
|
407
|
+
|
|
408
|
+
`expo-notifications` 模块替换 stub,由 `withExpoNotificationsStub()` 在 Metro 层注入(**仅 Android 平台**)。
|
|
409
|
+
|
|
410
|
+
**背景:** Expo SDK 53 起,`expo-notifications` 的 Android native module 已从 Expo Go 中移除,直接 import 会在 Expo Go 启动时崩溃。
|
|
411
|
+
|
|
412
|
+
**运行时行为:**
|
|
413
|
+
- **Expo Go(Android)**:提供 no-op 实现,`requestPermissionsAsync`、`setNotificationChannelAsync`、`scheduleNotificationAsync` 等核心 API 调用时弹出带参数校验的调试 Alert
|
|
414
|
+
- **Development Build(Android)**:透传真实 `expo-notifications`,功能完全正常
|
|
415
|
+
- **iOS(任意)**:不经过此 stub,直接使用真实 `expo-notifications`
|
|
416
|
+
|
|
417
|
+
**手动验证:** 在 `devkit-e2e` App 中扫码进入「Notification Stub 验证」页面,逐按钮触发并对照期望结果。
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
421
|
+
### expo-media-library-stub.js
|
|
422
|
+
|
|
423
|
+
`expo-media-library` 模块替换 stub,由 `withExpoMediaLibraryStub()` 在 Metro 层注入(**全平台**)。
|
|
424
|
+
|
|
425
|
+
**背景:** `expo-media-library` 依赖原生相册 API,在 Expo Go 和 Web 环境中不可用,调用 `saveToLibraryAsync` 等 API 会直接崩溃。
|
|
426
|
+
|
|
427
|
+
**运行时行为:**
|
|
428
|
+
- **Expo Go / Web**:提供 no-op 实现,以下 API 调用时弹出 Alert 提示(不崩溃):
|
|
429
|
+
- `usePermissions()` — 返回 `{ status: 'undetermined', granted: false }`,`requestPermission()` 弹 Alert
|
|
430
|
+
- `requestPermissionsAsync()` / `getPermissionsAsync()` — 前者弹 Alert,后者静默返回 denied
|
|
431
|
+
- `saveToLibraryAsync(uri)` — 弹 Alert 显示操作和 URI(超 60 字符自动截断)
|
|
432
|
+
- `createAssetAsync(uri)` — 弹 Alert 并返回合法的伪资产对象
|
|
433
|
+
- 其他未知 API — Proxy 兜底,静默返回 `undefined`;以 `PermissionsAsync` 结尾的 API 返回 denied 结构
|
|
434
|
+
- **Development Build(原生)**:透传真实 `expo-media-library`,功能完全正常
|
|
435
|
+
|
|
436
|
+
**Alert 消息格式:**
|
|
437
|
+
```
|
|
438
|
+
保存到相册
|
|
439
|
+
Expo Go 扫码预览不支持访问手机相册
|
|
440
|
+
操作: 图片: file:///tmp/test.jpg
|
|
441
|
+
发布为正式 App 后可正常使用
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
**手动验证:** 在 `devkit-e2e` App 中扫码进入「Media Library Stub 验证」页面,逐按钮触发并对照期望结果。
|
|
445
|
+
|
|
446
|
+
---
|
|
447
|
+
|
|
392
448
|
## Babel 插件:jsx-source
|
|
393
449
|
|
|
394
450
|
`babel-plugin-jsx-source` 为 JSX 元素注入 source 属性(文件路径、行列号),用于开发调试。通过 `dataSet` 对象注入,这是 React Web 可识别的数据通道。
|
package/dist/metro.d.mts
CHANGED
|
@@ -116,6 +116,7 @@ declare function withRouteEndpoint(config: MetroConfig, options: RouteEndpointOp
|
|
|
116
116
|
* withEsbuildMinify — 将 Metro minifier 切换为 esbuild(仅生产构建生效)
|
|
117
117
|
* withLucideResolver — 消除 lucide 子路径未在 exports 声明时的 warning
|
|
118
118
|
* withExpoNotificationsStub — Android:expo-notifications → stub(Expo Go no-op,Dev Build 透传)
|
|
119
|
+
* withExpoMediaLibraryStub — Web / Expo Go:expo-media-library → stub(弹 Alert 提示,Dev Build 透传)
|
|
119
120
|
* withEntryInjection — 在 expo-router 启动前注入脚本(仅 __DEV__)
|
|
120
121
|
* withDevStubs — 替换 Sentry DSN / 屏蔽 LogBox(仅 __DEV__)
|
|
121
122
|
* withRouteEndpoint — 添加 /__routes 端点(仅 __DEV__)
|
|
@@ -258,6 +259,23 @@ declare function withLucideResolver(config: MetroConfig, options?: WithLucideRes
|
|
|
258
259
|
*/
|
|
259
260
|
declare function withExpoNotificationsStub(config: MetroConfig): MetroConfig;
|
|
260
261
|
|
|
262
|
+
/**
|
|
263
|
+
* withExpoMediaLibraryStub — Web / Expo Go 将 expo-media-library 替换为 stub
|
|
264
|
+
*
|
|
265
|
+
* expo-media-library 依赖 native module,在 Web 和 Expo Go 中不可用。
|
|
266
|
+
* stub 在运行时通过 Platform.OS 和 isRunningInExpoGo() 判断:
|
|
267
|
+
* - Web / Expo Go:no-op(核心 API 弹 Alert 提示,不崩溃)
|
|
268
|
+
* - Development Build(原生):透传真实 expo-media-library,功能完全正常
|
|
269
|
+
*
|
|
270
|
+
* 此 wrapper 需要在开发和生产构建中均生效,因为 native module 缺失是运行时问题。
|
|
271
|
+
*/
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* @param config Metro config 对象
|
|
275
|
+
* @returns 注入 resolveRequest 后的新 Metro config
|
|
276
|
+
*/
|
|
277
|
+
declare function withExpoMediaLibraryStub(config: MetroConfig): MetroConfig;
|
|
278
|
+
|
|
261
279
|
/**
|
|
262
280
|
* 将 Metro transformer 的 minifier 切换为 esbuild。
|
|
263
281
|
*
|
|
@@ -270,4 +288,4 @@ declare function withExpoNotificationsStub(config: MetroConfig): MetroConfig;
|
|
|
270
288
|
*/
|
|
271
289
|
declare function withEsbuildMinify(config: MetroConfig): MetroConfig;
|
|
272
290
|
|
|
273
|
-
export { type DevkitOptions, type InjectOptions, type PatchNativeWindCacheOptions, type RouteEndpointOptions, patchNativeWindCachePath, withCssInterop, withDevStubs, withDevkit, withEntryInjection, withEsbuildMinify, withExpoNotificationsStub, withLucideResolver, withNativeWind, withRouteEndpoint, withWorkspaceNodeModules };
|
|
291
|
+
export { type DevkitOptions, type InjectOptions, type PatchNativeWindCacheOptions, type RouteEndpointOptions, patchNativeWindCachePath, withCssInterop, withDevStubs, withDevkit, withEntryInjection, withEsbuildMinify, withExpoMediaLibraryStub, withExpoNotificationsStub, withLucideResolver, withNativeWind, withRouteEndpoint, withWorkspaceNodeModules };
|
package/dist/metro.d.ts
CHANGED
|
@@ -116,6 +116,7 @@ declare function withRouteEndpoint(config: MetroConfig, options: RouteEndpointOp
|
|
|
116
116
|
* withEsbuildMinify — 将 Metro minifier 切换为 esbuild(仅生产构建生效)
|
|
117
117
|
* withLucideResolver — 消除 lucide 子路径未在 exports 声明时的 warning
|
|
118
118
|
* withExpoNotificationsStub — Android:expo-notifications → stub(Expo Go no-op,Dev Build 透传)
|
|
119
|
+
* withExpoMediaLibraryStub — Web / Expo Go:expo-media-library → stub(弹 Alert 提示,Dev Build 透传)
|
|
119
120
|
* withEntryInjection — 在 expo-router 启动前注入脚本(仅 __DEV__)
|
|
120
121
|
* withDevStubs — 替换 Sentry DSN / 屏蔽 LogBox(仅 __DEV__)
|
|
121
122
|
* withRouteEndpoint — 添加 /__routes 端点(仅 __DEV__)
|
|
@@ -258,6 +259,23 @@ declare function withLucideResolver(config: MetroConfig, options?: WithLucideRes
|
|
|
258
259
|
*/
|
|
259
260
|
declare function withExpoNotificationsStub(config: MetroConfig): MetroConfig;
|
|
260
261
|
|
|
262
|
+
/**
|
|
263
|
+
* withExpoMediaLibraryStub — Web / Expo Go 将 expo-media-library 替换为 stub
|
|
264
|
+
*
|
|
265
|
+
* expo-media-library 依赖 native module,在 Web 和 Expo Go 中不可用。
|
|
266
|
+
* stub 在运行时通过 Platform.OS 和 isRunningInExpoGo() 判断:
|
|
267
|
+
* - Web / Expo Go:no-op(核心 API 弹 Alert 提示,不崩溃)
|
|
268
|
+
* - Development Build(原生):透传真实 expo-media-library,功能完全正常
|
|
269
|
+
*
|
|
270
|
+
* 此 wrapper 需要在开发和生产构建中均生效,因为 native module 缺失是运行时问题。
|
|
271
|
+
*/
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* @param config Metro config 对象
|
|
275
|
+
* @returns 注入 resolveRequest 后的新 Metro config
|
|
276
|
+
*/
|
|
277
|
+
declare function withExpoMediaLibraryStub(config: MetroConfig): MetroConfig;
|
|
278
|
+
|
|
261
279
|
/**
|
|
262
280
|
* 将 Metro transformer 的 minifier 切换为 esbuild。
|
|
263
281
|
*
|
|
@@ -270,4 +288,4 @@ declare function withExpoNotificationsStub(config: MetroConfig): MetroConfig;
|
|
|
270
288
|
*/
|
|
271
289
|
declare function withEsbuildMinify(config: MetroConfig): MetroConfig;
|
|
272
290
|
|
|
273
|
-
export { type DevkitOptions, type InjectOptions, type PatchNativeWindCacheOptions, type RouteEndpointOptions, patchNativeWindCachePath, withCssInterop, withDevStubs, withDevkit, withEntryInjection, withEsbuildMinify, withExpoNotificationsStub, withLucideResolver, withNativeWind, withRouteEndpoint, withWorkspaceNodeModules };
|
|
291
|
+
export { type DevkitOptions, type InjectOptions, type PatchNativeWindCacheOptions, type RouteEndpointOptions, patchNativeWindCachePath, withCssInterop, withDevStubs, withDevkit, withEntryInjection, withEsbuildMinify, withExpoMediaLibraryStub, withExpoNotificationsStub, withLucideResolver, withNativeWind, withRouteEndpoint, withWorkspaceNodeModules };
|
package/dist/metro.js
CHANGED
|
@@ -36,6 +36,7 @@ __export(metro_exports, {
|
|
|
36
36
|
withDevkit: () => withDevkit,
|
|
37
37
|
withEntryInjection: () => withEntryInjection,
|
|
38
38
|
withEsbuildMinify: () => withEsbuildMinify,
|
|
39
|
+
withExpoMediaLibraryStub: () => withExpoMediaLibraryStub,
|
|
39
40
|
withExpoNotificationsStub: () => withExpoNotificationsStub,
|
|
40
41
|
withLucideResolver: () => withLucideResolver,
|
|
41
42
|
withNativeWind: () => withNativeWind,
|
|
@@ -283,7 +284,7 @@ function withCssInterop(config) {
|
|
|
283
284
|
}
|
|
284
285
|
|
|
285
286
|
// src/metro/withDevkit.ts
|
|
286
|
-
var
|
|
287
|
+
var import_path10 = __toESM(require("path"));
|
|
287
288
|
|
|
288
289
|
// src/metro/withEsbuildMinify.ts
|
|
289
290
|
function withEsbuildMinify(config) {
|
|
@@ -353,7 +354,7 @@ var EXPO_NOTIFICATIONS_STUB_PATH = import_path8.default.resolve(__dirname, "stub
|
|
|
353
354
|
function withExpoNotificationsStub(config) {
|
|
354
355
|
const upstream = config.resolver?.resolveRequest ?? null;
|
|
355
356
|
const resolveRequest = (context, moduleName, platform) => {
|
|
356
|
-
if (platform === "android" && moduleName === "expo-notifications" && !context.originModulePath.includes(EXPO_NOTIFICATIONS_STUB_FILENAME)) {
|
|
357
|
+
if ((platform === "android" || platform === "web") && moduleName === "expo-notifications" && !context.originModulePath.includes(EXPO_NOTIFICATIONS_STUB_FILENAME)) {
|
|
357
358
|
return { filePath: EXPO_NOTIFICATIONS_STUB_PATH, type: "sourceFile" };
|
|
358
359
|
}
|
|
359
360
|
if (upstream) return upstream(context, moduleName, platform);
|
|
@@ -362,6 +363,26 @@ function withExpoNotificationsStub(config) {
|
|
|
362
363
|
return { ...config, resolver: { ...config.resolver, resolveRequest } };
|
|
363
364
|
}
|
|
364
365
|
|
|
366
|
+
// src/metro/withExpoMediaLibraryStub.ts
|
|
367
|
+
var import_path9 = __toESM(require("path"));
|
|
368
|
+
var EXPO_MEDIA_LIBRARY_STUB_FILENAME = "expo-media-library-stub.js";
|
|
369
|
+
var EXPO_MEDIA_LIBRARY_STUB_PATH = import_path9.default.resolve(
|
|
370
|
+
__dirname,
|
|
371
|
+
"stubs",
|
|
372
|
+
EXPO_MEDIA_LIBRARY_STUB_FILENAME
|
|
373
|
+
);
|
|
374
|
+
function withExpoMediaLibraryStub(config) {
|
|
375
|
+
const upstream = config.resolver?.resolveRequest ?? null;
|
|
376
|
+
const resolveRequest = (context, moduleName, platform) => {
|
|
377
|
+
if (moduleName === "expo-media-library" && !context.originModulePath.includes(EXPO_MEDIA_LIBRARY_STUB_FILENAME)) {
|
|
378
|
+
return { filePath: EXPO_MEDIA_LIBRARY_STUB_PATH, type: "sourceFile" };
|
|
379
|
+
}
|
|
380
|
+
if (upstream) return upstream(context, moduleName, platform);
|
|
381
|
+
return context.resolveRequest(context, moduleName, platform);
|
|
382
|
+
};
|
|
383
|
+
return { ...config, resolver: { ...config.resolver, resolveRequest } };
|
|
384
|
+
}
|
|
385
|
+
|
|
365
386
|
// src/metro/withDevkit.ts
|
|
366
387
|
function withDevkit(config, options = {}) {
|
|
367
388
|
const projectRoot = config.projectRoot ?? process.cwd();
|
|
@@ -371,10 +392,11 @@ function withDevkit(config, options = {}) {
|
|
|
371
392
|
config = withEsbuildMinify(config);
|
|
372
393
|
config = withLucideResolver(config);
|
|
373
394
|
config = withExpoNotificationsStub(config);
|
|
395
|
+
config = withExpoMediaLibraryStub(config);
|
|
374
396
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
375
397
|
config = withEntryInjection(config);
|
|
376
398
|
config = withDevStubs(config);
|
|
377
|
-
config = withRouteEndpoint(config, { appDir:
|
|
399
|
+
config = withRouteEndpoint(config, { appDir: import_path10.default.join(projectRoot, "src", "app") });
|
|
378
400
|
}
|
|
379
401
|
return withNativeWind(config, { input, inlineRem: 16 });
|
|
380
402
|
}
|
|
@@ -394,6 +416,7 @@ try {
|
|
|
394
416
|
withDevkit,
|
|
395
417
|
withEntryInjection,
|
|
396
418
|
withEsbuildMinify,
|
|
419
|
+
withExpoMediaLibraryStub,
|
|
397
420
|
withExpoNotificationsStub,
|
|
398
421
|
withLucideResolver,
|
|
399
422
|
withNativeWind,
|
package/dist/metro.mjs
CHANGED
|
@@ -244,7 +244,7 @@ function withCssInterop(config) {
|
|
|
244
244
|
}
|
|
245
245
|
|
|
246
246
|
// src/metro/withDevkit.ts
|
|
247
|
-
import
|
|
247
|
+
import path10 from "path";
|
|
248
248
|
|
|
249
249
|
// src/metro/withEsbuildMinify.ts
|
|
250
250
|
function withEsbuildMinify(config) {
|
|
@@ -314,7 +314,7 @@ var EXPO_NOTIFICATIONS_STUB_PATH = path8.resolve(__dirname, "stubs", EXPO_NOTIFI
|
|
|
314
314
|
function withExpoNotificationsStub(config) {
|
|
315
315
|
const upstream = config.resolver?.resolveRequest ?? null;
|
|
316
316
|
const resolveRequest = (context, moduleName, platform) => {
|
|
317
|
-
if (platform === "android" && moduleName === "expo-notifications" && !context.originModulePath.includes(EXPO_NOTIFICATIONS_STUB_FILENAME)) {
|
|
317
|
+
if ((platform === "android" || platform === "web") && moduleName === "expo-notifications" && !context.originModulePath.includes(EXPO_NOTIFICATIONS_STUB_FILENAME)) {
|
|
318
318
|
return { filePath: EXPO_NOTIFICATIONS_STUB_PATH, type: "sourceFile" };
|
|
319
319
|
}
|
|
320
320
|
if (upstream) return upstream(context, moduleName, platform);
|
|
@@ -323,6 +323,26 @@ function withExpoNotificationsStub(config) {
|
|
|
323
323
|
return { ...config, resolver: { ...config.resolver, resolveRequest } };
|
|
324
324
|
}
|
|
325
325
|
|
|
326
|
+
// src/metro/withExpoMediaLibraryStub.ts
|
|
327
|
+
import path9 from "path";
|
|
328
|
+
var EXPO_MEDIA_LIBRARY_STUB_FILENAME = "expo-media-library-stub.js";
|
|
329
|
+
var EXPO_MEDIA_LIBRARY_STUB_PATH = path9.resolve(
|
|
330
|
+
__dirname,
|
|
331
|
+
"stubs",
|
|
332
|
+
EXPO_MEDIA_LIBRARY_STUB_FILENAME
|
|
333
|
+
);
|
|
334
|
+
function withExpoMediaLibraryStub(config) {
|
|
335
|
+
const upstream = config.resolver?.resolveRequest ?? null;
|
|
336
|
+
const resolveRequest = (context, moduleName, platform) => {
|
|
337
|
+
if (moduleName === "expo-media-library" && !context.originModulePath.includes(EXPO_MEDIA_LIBRARY_STUB_FILENAME)) {
|
|
338
|
+
return { filePath: EXPO_MEDIA_LIBRARY_STUB_PATH, type: "sourceFile" };
|
|
339
|
+
}
|
|
340
|
+
if (upstream) return upstream(context, moduleName, platform);
|
|
341
|
+
return context.resolveRequest(context, moduleName, platform);
|
|
342
|
+
};
|
|
343
|
+
return { ...config, resolver: { ...config.resolver, resolveRequest } };
|
|
344
|
+
}
|
|
345
|
+
|
|
326
346
|
// src/metro/withDevkit.ts
|
|
327
347
|
function withDevkit(config, options = {}) {
|
|
328
348
|
const projectRoot = config.projectRoot ?? process.cwd();
|
|
@@ -332,10 +352,11 @@ function withDevkit(config, options = {}) {
|
|
|
332
352
|
config = withEsbuildMinify(config);
|
|
333
353
|
config = withLucideResolver(config);
|
|
334
354
|
config = withExpoNotificationsStub(config);
|
|
355
|
+
config = withExpoMediaLibraryStub(config);
|
|
335
356
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
336
357
|
config = withEntryInjection(config);
|
|
337
358
|
config = withDevStubs(config);
|
|
338
|
-
config = withRouteEndpoint(config, { appDir:
|
|
359
|
+
config = withRouteEndpoint(config, { appDir: path10.join(projectRoot, "src", "app") });
|
|
339
360
|
}
|
|
340
361
|
return withNativeWind(config, { input, inlineRem: 16 });
|
|
341
362
|
}
|
|
@@ -354,6 +375,7 @@ export {
|
|
|
354
375
|
withDevkit,
|
|
355
376
|
withEntryInjection,
|
|
356
377
|
withEsbuildMinify,
|
|
378
|
+
withExpoMediaLibraryStub,
|
|
357
379
|
withExpoNotificationsStub,
|
|
358
380
|
withLucideResolver,
|
|
359
381
|
withNativeWind,
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var import_react_native = require("react-native");
|
|
3
|
+
var import_expo = require("expo");
|
|
4
|
+
var import_web_stub_dialog = require("./web-stub-dialog");
|
|
5
|
+
if (import_react_native.Platform.OS !== "web" && !(0, import_expo.isRunningInExpoGo)()) {
|
|
6
|
+
module.exports = require("expo-media-library");
|
|
7
|
+
} else {
|
|
8
|
+
let showStubAlert = function(title, detail) {
|
|
9
|
+
if (import_react_native.Platform.OS === "web") {
|
|
10
|
+
(0, import_web_stub_dialog.showWebStubDialog)({ title, details: [detail] });
|
|
11
|
+
} else {
|
|
12
|
+
import_react_native.Alert.alert(
|
|
13
|
+
title,
|
|
14
|
+
["\u79D2\u54D2\u626B\u7801\u9884\u89C8\u4E0D\u652F\u6301\u8BBF\u95EE\u624B\u673A\u76F8\u518C", `\u64CD\u4F5C: ${detail}`, "", "\u53D1\u5E03\u4E3A\u6B63\u5F0F App \u540E\u53EF\u6B63\u5E38\u4F7F\u7528"].join("\n"),
|
|
15
|
+
[{ text: "\u77E5\u9053\u4E86" }]
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
}, usePermissions = function() {
|
|
19
|
+
const [permission, setPermission] = useState(UNDETERMINED_PERMISSION);
|
|
20
|
+
const requestPermission = useCallback(async () => {
|
|
21
|
+
showStubAlert("\u8BF7\u6C42\u76F8\u518C\u6743\u9650", "\u8BFB\u5199\u624B\u673A\u76F8\u518C");
|
|
22
|
+
setPermission(DENIED_PERMISSION);
|
|
23
|
+
return DENIED_PERMISSION;
|
|
24
|
+
}, []);
|
|
25
|
+
const getPermission = useCallback(async () => permission, [permission]);
|
|
26
|
+
return [permission, requestPermission, getPermission];
|
|
27
|
+
};
|
|
28
|
+
var showStubAlert2 = showStubAlert, usePermissions2 = usePermissions;
|
|
29
|
+
const UNDETERMINED_PERMISSION = {
|
|
30
|
+
status: "undetermined",
|
|
31
|
+
granted: false,
|
|
32
|
+
canAskAgain: true,
|
|
33
|
+
expires: "never"
|
|
34
|
+
};
|
|
35
|
+
const DENIED_PERMISSION = {
|
|
36
|
+
status: "denied",
|
|
37
|
+
granted: false,
|
|
38
|
+
canAskAgain: false,
|
|
39
|
+
expires: "never"
|
|
40
|
+
};
|
|
41
|
+
const { useState, useCallback } = require("react");
|
|
42
|
+
const saveToLibraryAsync = async (uri) => {
|
|
43
|
+
const uriStr = typeof uri === "string" ? uri.length > 60 ? uri.slice(0, 60) + "\u2026" : uri : String(uri);
|
|
44
|
+
showStubAlert("\u4FDD\u5B58\u5230\u76F8\u518C", `\u56FE\u7247: ${uriStr}`);
|
|
45
|
+
};
|
|
46
|
+
const createAssetAsync = async (uri) => {
|
|
47
|
+
const uriStr = typeof uri === "string" ? uri.slice(0, 60) : String(uri);
|
|
48
|
+
showStubAlert("\u521B\u5EFA\u5A92\u4F53\u8D44\u4EA7", `\u6587\u4EF6: ${uriStr}`);
|
|
49
|
+
return {
|
|
50
|
+
id: "stub",
|
|
51
|
+
filename: "stub.jpg",
|
|
52
|
+
uri: String(uri),
|
|
53
|
+
mediaType: "photo",
|
|
54
|
+
mediaSubtypes: [],
|
|
55
|
+
width: 0,
|
|
56
|
+
height: 0,
|
|
57
|
+
creationTime: 0,
|
|
58
|
+
modificationTime: 0,
|
|
59
|
+
duration: 0
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
const requestPermissionsAsync = async () => {
|
|
63
|
+
showStubAlert("\u8BF7\u6C42\u76F8\u518C\u6743\u9650", "\u8BFB\u5199\u624B\u673A\u76F8\u518C");
|
|
64
|
+
return DENIED_PERMISSION;
|
|
65
|
+
};
|
|
66
|
+
const getPermissionsAsync = async () => DENIED_PERMISSION;
|
|
67
|
+
const enums = {
|
|
68
|
+
PermissionStatus: {
|
|
69
|
+
GRANTED: "granted",
|
|
70
|
+
DENIED: "denied",
|
|
71
|
+
UNDETERMINED: "undetermined",
|
|
72
|
+
LIMITED: "limited"
|
|
73
|
+
},
|
|
74
|
+
MediaType: {
|
|
75
|
+
photo: "photo",
|
|
76
|
+
video: "video",
|
|
77
|
+
audio: "audio",
|
|
78
|
+
unknown: "unknown"
|
|
79
|
+
},
|
|
80
|
+
SortBy: {
|
|
81
|
+
default: "default",
|
|
82
|
+
creationTime: "creationTime",
|
|
83
|
+
modificationTime: "modificationTime",
|
|
84
|
+
mediaType: "mediaType",
|
|
85
|
+
width: "width",
|
|
86
|
+
height: "height",
|
|
87
|
+
duration: "duration"
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
const coreHandlers = {
|
|
91
|
+
usePermissions,
|
|
92
|
+
saveToLibraryAsync,
|
|
93
|
+
createAssetAsync,
|
|
94
|
+
requestPermissionsAsync,
|
|
95
|
+
getPermissionsAsync
|
|
96
|
+
};
|
|
97
|
+
const noopPermission = async () => DENIED_PERMISSION;
|
|
98
|
+
const noop = async () => void 0;
|
|
99
|
+
module.exports = new Proxy(enums, {
|
|
100
|
+
get(target, key) {
|
|
101
|
+
if (key in target) return target[key];
|
|
102
|
+
if (key in coreHandlers) return coreHandlers[key];
|
|
103
|
+
if (key.endsWith("PermissionsAsync")) return noopPermission;
|
|
104
|
+
return noop;
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=expo-media-library-stub.js.map
|
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
var import_expo = require("expo");
|
|
3
3
|
var import_react_native = require("react-native");
|
|
4
|
-
|
|
4
|
+
var import_web_stub_dialog = require("./web-stub-dialog");
|
|
5
|
+
if (import_react_native.Platform.OS !== "web" && !(0, import_expo.isRunningInExpoGo)()) {
|
|
5
6
|
module.exports = require("expo-notifications");
|
|
6
7
|
} else {
|
|
7
8
|
let showDetailedAlert = function(apiName, lines, isValid, errors) {
|
|
9
|
+
if (import_react_native.Platform.OS === "web") {
|
|
10
|
+
(0, import_web_stub_dialog.showWebStubDialog)({
|
|
11
|
+
title: apiName,
|
|
12
|
+
details: lines,
|
|
13
|
+
errors: !isValid && errors && errors.length > 0 ? errors : void 0
|
|
14
|
+
});
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
8
17
|
const statusLine = isValid ? "\u79D2\u54D2\u901A\u77E5\u60A8 \u6D88\u606F\u63A8\u9001\u9A8C\u8BC1\u901A\u8FC7" : "\u9A8C\u8BC1\u672A\u901A\u8FC7";
|
|
9
18
|
const parts = [statusLine, "", ...lines];
|
|
10
19
|
if (!isValid && errors && errors.length > 0) {
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var web_stub_dialog_exports = {};
|
|
20
|
+
__export(web_stub_dialog_exports, {
|
|
21
|
+
showWebStubDialog: () => showWebStubDialog
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(web_stub_dialog_exports);
|
|
24
|
+
const DIALOG_ID = "__devkit_stub_dialog__";
|
|
25
|
+
const STYLES = {
|
|
26
|
+
backdrop: `
|
|
27
|
+
position: fixed;
|
|
28
|
+
inset: 0;
|
|
29
|
+
display: flex;
|
|
30
|
+
align-items: center;
|
|
31
|
+
justify-content: center;
|
|
32
|
+
background: rgba(0,0,0,0.55);
|
|
33
|
+
z-index: 99999;
|
|
34
|
+
padding: 24px;
|
|
35
|
+
box-sizing: border-box;
|
|
36
|
+
`,
|
|
37
|
+
card: `
|
|
38
|
+
background: #1e1e1e;
|
|
39
|
+
border-radius: 14px;
|
|
40
|
+
padding: 20px 24px 16px;
|
|
41
|
+
max-width: 380px;
|
|
42
|
+
width: 100%;
|
|
43
|
+
box-shadow: 0 8px 32px rgba(0,0,0,0.5);
|
|
44
|
+
display: flex;
|
|
45
|
+
flex-direction: column;
|
|
46
|
+
gap: 0;
|
|
47
|
+
`,
|
|
48
|
+
badge: `
|
|
49
|
+
display: inline-block;
|
|
50
|
+
font-size: 11px;
|
|
51
|
+
font-weight: 600;
|
|
52
|
+
letter-spacing: 0.5px;
|
|
53
|
+
color: #00E676;
|
|
54
|
+
background: rgba(0,230,118,0.12);
|
|
55
|
+
border-radius: 6px;
|
|
56
|
+
padding: 2px 8px;
|
|
57
|
+
margin-bottom: 10px;
|
|
58
|
+
font-family: system-ui, sans-serif;
|
|
59
|
+
`,
|
|
60
|
+
title: `
|
|
61
|
+
font-size: 16px;
|
|
62
|
+
font-weight: 700;
|
|
63
|
+
color: #ffffff;
|
|
64
|
+
margin: 0 0 12px;
|
|
65
|
+
font-family: system-ui, sans-serif;
|
|
66
|
+
line-height: 1.3;
|
|
67
|
+
`,
|
|
68
|
+
body: `
|
|
69
|
+
font-size: 13px;
|
|
70
|
+
color: #aaaaaa;
|
|
71
|
+
font-family: system-ui, sans-serif;
|
|
72
|
+
line-height: 1.6;
|
|
73
|
+
margin: 0 0 16px;
|
|
74
|
+
white-space: pre-wrap;
|
|
75
|
+
`,
|
|
76
|
+
highlight: `
|
|
77
|
+
color: #e0e0e0;
|
|
78
|
+
font-family: ui-monospace, monospace;
|
|
79
|
+
font-size: 12px;
|
|
80
|
+
background: rgba(255,255,255,0.06);
|
|
81
|
+
border-radius: 6px;
|
|
82
|
+
padding: 8px 10px;
|
|
83
|
+
margin: 0 0 16px;
|
|
84
|
+
white-space: pre-wrap;
|
|
85
|
+
word-break: break-all;
|
|
86
|
+
`,
|
|
87
|
+
footer: `
|
|
88
|
+
font-size: 12px;
|
|
89
|
+
color: #666;
|
|
90
|
+
font-family: system-ui, sans-serif;
|
|
91
|
+
margin: 0 0 16px;
|
|
92
|
+
`,
|
|
93
|
+
button: `
|
|
94
|
+
align-self: flex-end;
|
|
95
|
+
background: #00E676;
|
|
96
|
+
color: #121212;
|
|
97
|
+
border: none;
|
|
98
|
+
border-radius: 8px;
|
|
99
|
+
padding: 8px 20px;
|
|
100
|
+
font-size: 14px;
|
|
101
|
+
font-weight: 700;
|
|
102
|
+
font-family: system-ui, sans-serif;
|
|
103
|
+
cursor: pointer;
|
|
104
|
+
transition: opacity 0.15s;
|
|
105
|
+
`
|
|
106
|
+
};
|
|
107
|
+
function showWebStubDialog(opts) {
|
|
108
|
+
if (typeof document === "undefined") return;
|
|
109
|
+
document.getElementById(DIALOG_ID)?.remove();
|
|
110
|
+
const backdrop = document.createElement("div");
|
|
111
|
+
backdrop.id = DIALOG_ID;
|
|
112
|
+
backdrop.setAttribute("style", STYLES.backdrop);
|
|
113
|
+
const card = document.createElement("div");
|
|
114
|
+
card.setAttribute("style", STYLES.card);
|
|
115
|
+
const badge = document.createElement("span");
|
|
116
|
+
badge.setAttribute("style", STYLES.badge);
|
|
117
|
+
badge.textContent = "\u79D2\u54D2\u9884\u89C8\u6A21\u5F0F";
|
|
118
|
+
const titleEl = document.createElement("p");
|
|
119
|
+
titleEl.setAttribute("style", STYLES.title);
|
|
120
|
+
titleEl.textContent = opts.title;
|
|
121
|
+
const bodyEl = document.createElement("p");
|
|
122
|
+
bodyEl.setAttribute("style", STYLES.body);
|
|
123
|
+
bodyEl.textContent = "\u6B64\u529F\u80FD\u5728\u79D2\u54D2\u9884\u89C8\u4E2D\u4E0D\u53EF\u7528\uFF0C\u53D1\u5E03\u4E3A\u6B63\u5F0F App \u540E\u53EF\u6B63\u5E38\u4F7F\u7528\u3002";
|
|
124
|
+
const detailEl = document.createElement("div");
|
|
125
|
+
detailEl.setAttribute("style", STYLES.highlight);
|
|
126
|
+
detailEl.textContent = opts.details.join("\n");
|
|
127
|
+
card.appendChild(badge);
|
|
128
|
+
card.appendChild(titleEl);
|
|
129
|
+
card.appendChild(bodyEl);
|
|
130
|
+
card.appendChild(detailEl);
|
|
131
|
+
if (opts.errors && opts.errors.length > 0) {
|
|
132
|
+
const errEl = document.createElement("p");
|
|
133
|
+
errEl.setAttribute(
|
|
134
|
+
"style",
|
|
135
|
+
STYLES.footer + "color: #ff5252;"
|
|
136
|
+
);
|
|
137
|
+
errEl.textContent = "\u53C2\u6570\u95EE\u9898: " + opts.errors.join(" / ");
|
|
138
|
+
card.appendChild(errEl);
|
|
139
|
+
}
|
|
140
|
+
const btn = document.createElement("button");
|
|
141
|
+
btn.setAttribute("style", STYLES.button);
|
|
142
|
+
btn.textContent = "\u77E5\u9053\u4E86";
|
|
143
|
+
const close = () => backdrop.remove();
|
|
144
|
+
btn.addEventListener("click", close);
|
|
145
|
+
backdrop.addEventListener("click", (e) => {
|
|
146
|
+
if (e.target === backdrop) close();
|
|
147
|
+
});
|
|
148
|
+
card.appendChild(btn);
|
|
149
|
+
backdrop.appendChild(card);
|
|
150
|
+
document.body.appendChild(backdrop);
|
|
151
|
+
}
|
|
152
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
153
|
+
0 && (module.exports = {
|
|
154
|
+
showWebStubDialog
|
|
155
|
+
});
|
|
156
|
+
//# sourceMappingURL=web-stub-dialog.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "miaoda-expo-devkit",
|
|
3
|
-
"version": "0.1.1-beta.
|
|
3
|
+
"version": "0.1.1-beta.32",
|
|
4
4
|
"description": "Expo 应用开发工具集:Sentry DSN 替换 stub、错误/网络捕获、Metro 符号化",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
"./sentry-react-native-stub": "./dist/stubs/sentry-react-native-stub.js",
|
|
46
46
|
"./no-op-logbox": "./dist/stubs/no-op-logbox.js",
|
|
47
47
|
"./expo-notifications-stub": "./dist/stubs/expo-notifications-stub.js",
|
|
48
|
+
"./expo-media-library-stub": "./dist/stubs/expo-media-library-stub.js",
|
|
48
49
|
"./rules/no-undeclared-expo-plugin": "./dist/rules/no-undeclared-expo-plugin.js",
|
|
49
50
|
"./rules/no-unused-expo-plugin": "./dist/rules/no-unused-expo-plugin.js",
|
|
50
51
|
"./rules/no-unstable-expo-router": "./dist/rules/no-unstable-expo-router.js",
|
package/pnpm-config.json
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
1
|
{
|
|
2
|
-
"blockedPackages": [
|
|
3
|
-
{
|
|
4
|
-
"name": "expo-media-library",
|
|
5
|
-
"reason": "Use \"expo-image-picker\" instead — it covers most image/video picking needs with simpler permissions. Only use expo-media-library if you need to save files to the album or browse the full library."
|
|
6
|
-
}
|
|
7
|
-
]
|
|
2
|
+
"blockedPackages": []
|
|
8
3
|
}
|