miaoda-expo-devkit 0.1.1-beta.111 → 0.1.1-beta.113

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 CHANGED
@@ -2,8 +2,7 @@
2
2
 
3
3
  Expo / React Native 开发环境工具集,通过 Metro 构建层注入以下能力:
4
4
 
5
- - **Sentry DSN 替换** — 拦截 `@sentry/react-native`,将真实 DSN 替换为无害 stub,防止开发期错误事件发送到 Sentry 服务器
6
- - **错误 / 网络捕获** — 内置 `SentryCapture`,自动调用 Metro `/symbolicate` 还原源码位置并输出到 `console.warn`
5
+ - **Sentry 初始化守卫** — Native 透传真实初始化;独立 Expo Web 使用安全 fallback;sandbox Web 由前置 polyfill 唯一初始化
7
6
  - **Bundle 首部注入** — 在 expo-router 初始化之前执行自定义脚本
8
7
  - **HMR postMessage 控制** — 通过 `window.postMessage` 在运行时启动或停止 Fast Refresh
9
8
  - **LogBox 屏蔽** — web 平台禁用 Expo 全屏错误遮罩
@@ -60,7 +59,7 @@ module.exports = withExpoCalendarStub(withExpoMediaLibraryStub(withEntryInjectio
60
59
 
61
60
  ### Sentry 初始化
62
61
 
63
- 照常调用 `Sentry.init`,stub 自动注入内置捕获器,无需额外配置:
62
+ 应用可照常调用 `Sentry.init`:
64
63
 
65
64
  ```ts
66
65
  // app/_layout.tsx
@@ -68,34 +67,17 @@ import * as Sentry from '@sentry/react-native';
68
67
 
69
68
  Sentry.init({
70
69
  dsn: 'https://your-key@sentry.io/your-project',
71
- // stub 自动拦截 beforeSend / beforeBreadcrumb,错误摘要输出到 console.warn
72
70
  });
73
71
  ```
74
72
 
75
- #### 自定义回调(可选)
73
+ stub 的行为按平台区分:
76
74
 
77
- 传入自己的 `beforeSend` / `beforeBreadcrumb`,会在内置捕获器之后串联执行:
75
+ - iOS/Android:原样调用真实 `@sentry/react-native` init,应用参数不变;
76
+ - 独立 Expo Web:用安全 fallback DSN 初始化,避免开发事件进入真实项目;
77
+ - vite-sandbox Expo Web:sandbox 生成的 Metro polyfill 已经把应用实际依赖的 `@sentry/browser` 与统一观测运行时打包,并先于应用完成唯一一次 init;stub 检测版本字符串在场标记后吞掉应用 init;
78
+ - 版本不一致:只输出 `console.error`,不向编辑器宿主发送消息。
78
79
 
79
- ```ts
80
- import * as Sentry from '@sentry/react-native';
81
- import { SentryCapture } from 'miaoda-expo-devkit';
82
- import type { ErrorCaptureInfo, NetworkCaptureInfo } from 'miaoda-expo-devkit';
83
-
84
- const capture = new SentryCapture({
85
- onError(info: ErrorCaptureInfo) {
86
- console.log('Error at:', info.symbolicatedFrames?.[0]);
87
- },
88
- onNetwork(info: NetworkCaptureInfo) {
89
- console.log('Network:', info.method, info.url, info.statusCode);
90
- },
91
- });
92
-
93
- Sentry.init({
94
- dsn: 'https://your-key@sentry.io/your-project',
95
- beforeSend: capture.beforeSend,
96
- beforeBreadcrumb: capture.beforeBreadcrumb,
97
- });
98
- ```
80
+ fetch/XHR 取数、网络与 console 事件、原始数据长度限制、Supabase 白名单、`GLOBAL_ERROR` 和 Metro `/symbolicate` 均由 vite-sandbox 的同一份运行时维护。devkit 不复制这些规则,也不再包含 capture 或 runtime bridge。
99
81
 
100
82
  ### HMR postMessage 控制
101
83
 
@@ -151,75 +133,6 @@ window.__DEVKIT_HMR_ENABLED__ // true | false
151
133
 
152
134
  ---
153
135
 
154
- ### `new SentryCapture(options)`
155
-
156
- 从 `miaoda-expo-devkit` 引入。
157
-
158
- 用于自定义捕获行为。stub 内置了一个默认实例(输出到 `console.warn`),此类用于叠加自定义处理逻辑。
159
-
160
- | 选项 | 类型 | 说明 |
161
- |---|---|---|
162
- | `onError` | `(info: ErrorCaptureInfo) => void` | 捕获到错误时触发,携带符号化后的调用栈 |
163
- | `onNetwork` | `(info: NetworkCaptureInfo) => void` | 捕获到 HTTP 请求时触发 |
164
- | `symbolicator` | `MetroSymbolicator` | 可选,单测时注入 mock 以隔离网络依赖 |
165
-
166
- 构造完成后,将绑定方法传给 `Sentry.init`:
167
-
168
- ```ts
169
- Sentry.init({
170
- beforeSend: capture.beforeSend,
171
- beforeBreadcrumb: capture.beforeBreadcrumb,
172
- });
173
- ```
174
-
175
- #### `ErrorCaptureInfo`
176
-
177
- | 字段 | 类型 | 说明 |
178
- |---|---|---|
179
- | `message` | `string` | 错误信息 |
180
- | `symbolicatedFrames` | `MetroFrame[] \| null` | 经 Metro 符号化后还原到源码位置的调用栈帧 |
181
- | `componentStack` | `string \| null` | React 组件调用栈(ErrorBoundary 捕获的错误才有值) |
182
- | `mechanismType` | `string \| null` | Sentry 捕获方式:`'onerror'`、`'generic'`、`'auto.function.react.error_boundary'` 等 |
183
-
184
- #### `NetworkCaptureInfo`
185
-
186
- | 字段 | 类型 | 说明 |
187
- |---|---|---|
188
- | `method` | `string \| null` | HTTP 方法 |
189
- | `url` | `string \| null` | 请求 URL |
190
- | `statusCode` | `number \| null` | HTTP 状态码(连接级失败为 `null`) |
191
-
192
- ---
193
-
194
- ### `new MetroSymbolicator(options?)`
195
-
196
- 从 `miaoda-expo-devkit` 引入。
197
-
198
- 通过 Metro `/symbolicate` 接口将 bundle 行列号还原为源码位置。`SentryCapture` 内部已默认使用,主要用途是单测时注入 mock。
199
-
200
- | 选项 | 类型 | 说明 |
201
- |---|---|---|
202
- | `baseUrl` | `string` | Metro server 地址。不传则从 `NativeModules.SourceCode.scriptURL` 自动推断,fallback 到 `localhost:8081`。 |
203
-
204
- **单测示例:**
205
-
206
- ```ts
207
- const mockSym = {
208
- parseErrorStack: jest.fn(() => []),
209
- symbolicate: jest.fn(async () => null),
210
- };
211
- const onError = jest.fn();
212
- const capture = new SentryCapture({ onError, symbolicator: mockSym });
213
-
214
- await capture.beforeSend(fakeEvent, { originalException: new Error('oops') });
215
-
216
- expect(onError).toHaveBeenCalledWith(
217
- expect.objectContaining({ message: 'oops' }),
218
- );
219
- ```
220
-
221
- ---
222
-
223
136
  ## 全局标记
224
137
 
225
138
  注入脚本在 bundle 执行时(早于任何 React 代码)写入以下全局变量,可用于 E2E 测试断言:
@@ -233,7 +146,7 @@ expect(onError).toHaveBeenCalledWith(
233
146
 
234
147
  | 变量 | 默认值 | 说明 |
235
148
  |---|---|---|
236
- | `SENTRY_OVERRIDE_DSN` | `https://stubPublicKey@o0.ingest.sentry.io/0` | 覆盖 Sentry DSN,可指向本地 relay 等自定义端点 |
149
+ | `INJECT_SENTRY_DSN` | 安全 stub DSN | vite-sandbox 服务端已有变量;sandbox 会自动编入生成 runtime,应用开发者无需配置 |
237
150
  | `METRO_CACHE_DIR` | `projectRoot/.metro-cache` | Metro transform 缓存目录绝对路径,优先级最高,适合容器/CI 挂载外部持久目录 |
238
151
  | `METRO_TRANSFORM_LOG` | _(未设置时不记录)_ | 构建日志输出文件的绝对路径(JSONL 格式),仅开发模式(`__DEV__`)下生效 |
239
152
  | `EXPO_AI_ERROR_COLLECTOR` | _(未设置时关闭)_ | 设为 `1` 后由 DevKit 管理的 Expo CLI patch 采集当前 Metro 编译状态 |
@@ -252,10 +165,8 @@ EXPO_AI_ERROR_COLLECTOR=1 npx expo start
252
165
  可能清理这个目录;需要持久化时请设置 `EXPO_AI_ERROR_FILE` 指向项目外部路径。
253
166
 
254
167
  状态文件只表示“当前编译”:`bundle_build_started` 会立即清除上一次错误,
255
- `bundling_error` 写入结构化错误,下一次完整编译成功或失败都会替换旧状态。
256
- Expo CLI patch 也会为 HMR 增量更新写入 `hmr_update_started`、
257
- `hmr_update_done` 状态,确保源码修复后通过 Fast Refresh 恢复时旧错误同步清除。
258
- AI 进程直接读取这个文件即可,不需要额外的 HTTP 服务或 WebSocket 客户端。
168
+ `bundling_error` 写入结构化错误,下一次编译成功或失败都会替换旧状态。AI
169
+ 进程直接读取这个文件即可,不需要额外的 HTTP 服务或 WebSocket 客户端。
259
170
 
260
171
  返回对象包含 `status`(`compiling`、`failed`、`succeeded`)和 `error`。失败时
261
172
  `error` 至少包含 `type`、`message`、`filename`、`lineNumber`、`column`、
@@ -265,6 +176,14 @@ AI 进程直接读取这个文件即可,不需要额外的 HTTP 服务或 WebS
265
176
  该范围以外的 CLI 版本不会静默套用补丁;升级时需要先检查发布产物,确认兼容后再调整
266
177
  `pnpmfile.cjs` 中的范围和对应测试。
267
178
 
179
+ 以下变量只服务于 sandbox 专项 E2E,不是应用或线上部署配置:
180
+
181
+ | 变量 | 默认值 | 设置方与用途 |
182
+ |---|---|---|
183
+ | `EXPO_OVERRIDE_METRO_CONFIG` | 无 | 线上由 vite-sandbox 自动设置;专项 E2E 由准备流程指向生成的 `node_modules/.cache/vite-sandbox/metro.config.cjs` |
184
+ | `EXPO_E2E_PORT` | `3001` | 仅在 Playwright 端口冲突时由本地或 CI 覆盖 |
185
+ | `EXPO_E2E_SANDBOX_PROJECT_ID` | `0` | 仅用于断言 Sentry envelope 使用 sandbox DSN 的 project id |
186
+
268
187
  ## 工作原理
269
188
 
270
189
  ```
@@ -362,18 +281,16 @@ setupHMRMessageControl()
362
281
 
363
282
  sentry-react-native-stub.js
364
283
  └─ init(options)
365
- └─ RealSentry.init({ ...options, dsn: OVERRIDE_DSN, beforeSend, beforeBreadcrumb })
366
- ├─ _capture.beforeSend(event, hint) ← 内置 SentryCapture(自动注入)
367
- │ ├─ MetroSymbolicator.symbolicate() → POST /symbolicate
368
- │ └─ onError({ message, frames, ... }) → console.warn(默认)
369
- └─ options.beforeSend?(event, hint) ← 调用方自定义(可选,串联执行)
284
+ ├─ Native: RealSentry.init(options)
285
+ ├─ 独立 Web: RealSentry.init({...options, dsn: FALLBACK_DSN})
286
+ └─ sandbox Web: 校验版本标记后返回,不重复初始化
370
287
  ```
371
288
 
372
289
  ## Package Exports
373
290
 
374
291
  | 子路径 | 文件 | 内容 |
375
292
  |---|---|---|
376
- | `.` | `dist/index.js` | `SentryCapture`、`MetroSymbolicator`、全部类型 |
293
+ | `.` | `dist/index.js` | 主入口(当前不导出运行时 API) |
377
294
  | `./metro` | `dist/metro.js` | `withDevkit`、`withDevStubs`、`withEntryInjection`、`withPersistentCache`、`withTransformLogger`、`withExpoNotificationsStub`、`withExpoMediaLibraryStub`、`withExpoCalendarStub` 等全部 Metro wrapper |
378
295
  | `./babel-plugin-jsx-source` | `dist/babel/plugin-jsx-source.js` | Babel 插件:为 JSX 注入 source 信息 |
379
296
  | `./babel-preset` | `dist/babel/preset.js` | Babel Preset:集成 jsx-source 和 lucide 插件 |
@@ -562,13 +479,13 @@ window.postMessage({ type: 'editor-refresh' }, '*') //
562
479
  `@sentry/react-native` 模块替换 stub,由 `withDevStubs()` 在 Metro 层自动注入。
563
480
 
564
481
  **功能:**
565
- - 将用户传入的 DSN 替换为无害的覆盖值(默认 `https://stubPublicKey@o0.ingest.sentry.io/0`)
566
- - 自动注入内置 SentryCapture,监听并符号化 Sentry 错误事件
567
- - 串联调用方传入的 `beforeSend` / `beforeBreadcrumb`(内置捕获器先执行)
568
- - 向父窗口发送 `GLOBAL_ERROR` 事件,携带错误信息
482
+ - Native 原样透传应用的 Sentry init
483
+ - 独立 Web 使用安全 fallback DSN
484
+ - sandbox Web 校验运行时版本标记并阻止重复 init
485
+ - 版本不一致时只向 console 输出诊断信息,不通知编辑器宿主
569
486
 
570
487
  **环境变量:**
571
- - `SENTRY_OVERRIDE_DSN`:自定义覆盖 DSN(如指向本地 relay)
488
+ - 应用开发者不需要新增环境变量;`INJECT_SENTRY_DSN` 由 vite-sandbox 启动链路自动处理
572
489
 
573
490
  ---
574
491
 
package/dist/index.d.mts CHANGED
@@ -1,111 +1,2 @@
1
- import { ErrorEvent, EventHint, Breadcrumb, BreadcrumbHint } from '@sentry/core';
2
1
 
3
- /**
4
- * Sentry Capture 模块
5
- *
6
- * 提供 Metro 符号化(MetroSymbolicator)和 Sentry 事件捕获(SentryCapture)。
7
- */
8
-
9
- /** Metro /symbolicate 接口的帧格式(请求与响应共用)。 */
10
- interface MetroFrame {
11
- file: string;
12
- lineNumber: number;
13
- column: number;
14
- methodName: string;
15
- }
16
- /** SentryCapture.onError 回调参数 */
17
- interface ErrorCaptureInfo {
18
- message: string;
19
- symbolicatedFrames: MetroFrame[] | null;
20
- componentStack: string | null;
21
- /** Sentry Mechanism.type,如 'onerror' / 'generic' / 'auto.function.react.error_boundary' */
22
- mechanismType: string | null;
23
- }
24
- /** SentryCapture.onNetwork 回调参数 */
25
- interface NetworkCaptureInfo {
26
- method: string | null;
27
- url: string | null;
28
- statusCode: number | null;
29
- }
30
- interface SentryCaptureOptions {
31
- onError?: (info: ErrorCaptureInfo) => void;
32
- onNetwork?: (info: NetworkCaptureInfo) => void;
33
- /** 可注入 mock 实例以在单元测试中隔离网络依赖 */
34
- symbolicator?: MetroSymbolicator;
35
- }
36
- /**
37
- * Metro 符号化器。
38
- * 将 bundle 行列号还原为源码位置(通过 Metro /symbolicate 接口)。
39
- *
40
- * @example
41
- * const sym = new MetroSymbolicator();
42
- *
43
- * @example
44
- * // 单测:注入 baseUrl 并 mock fetch
45
- * const sym = new MetroSymbolicator({ baseUrl: 'http://localhost:8081' });
46
- * global.fetch = jest.fn().mockResolvedValue({ ok: true, json: async () => ({ stack: [...] }) });
47
- */
48
- declare class MetroSymbolicator {
49
- readonly baseUrl: string;
50
- constructor({ baseUrl }?: {
51
- baseUrl?: string;
52
- });
53
- /**
54
- * 从 NativeModules.SourceCode.scriptURL 推断 Metro server base URL。
55
- * web 平台使用相对路径;native 回退到 localhost:8081。
56
- */
57
- private static _getDefaultBaseUrl;
58
- /**
59
- * 解析 Error.stack 字符串为 Metro 所需的帧数组。
60
- *
61
- * 使用 stacktrace-parser 覆盖各 JS 引擎的格式:
62
- * V8/Node.js/Hermes(新):at methodName (url:line:col)
63
- * Gecko/Firefox/Hermes(旧):methodName@url:line:col
64
- * JavaScriptCore(iOS):methodName@url:line:col
65
- *
66
- * 无法符号化的帧(file 或 lineNumber 为 null)会被过滤掉。
67
- */
68
- parseErrorStack(stack: string): MetroFrame[];
69
- /**
70
- * 将帧列表发送到 Metro /symbolicate,返回符号化后的帧数组。
71
- */
72
- symbolicate(stack: MetroFrame[]): Promise<MetroFrame[] | null>;
73
- }
74
- /**
75
- * Sentry 事件捕获器。
76
- * 封装 beforeSend / beforeBreadcrumb 逻辑,通过注入的回调将事件数据交给调用方处理。
77
- *
78
- * @example
79
- * const capture = new SentryCapture({
80
- * onError(info) { console.log(info.message, info.symbolicatedFrames); },
81
- * onNetwork(info) { console.log(info.method, info.url, info.statusCode); },
82
- * });
83
- * Sentry.init({ dsn: '...', beforeSend: capture.beforeSend, beforeBreadcrumb: capture.beforeBreadcrumb });
84
- *
85
- * @example
86
- * // 单测:注入 mock symbolicator
87
- * const mockSym = { parseErrorStack: jest.fn(() => []), symbolicate: jest.fn(async () => null) };
88
- * const onError = jest.fn();
89
- * const capture = new SentryCapture({ onError, symbolicator: mockSym });
90
- * await capture.beforeSend(fakeEvent, { originalException: new Error('oops') });
91
- * expect(onError).toHaveBeenCalledWith(expect.objectContaining({ message: 'oops' }));
92
- */
93
- declare class SentryCapture {
94
- private readonly onError;
95
- private readonly onNetwork;
96
- private readonly symbolicator;
97
- constructor({ onError, onNetwork, symbolicator }?: SentryCaptureOptions);
98
- /**
99
- * Sentry beforeSend hook。
100
- * 优先使用原始 Error.stack 进行符号化;若无法获取则回退到 Sentry 事件帧。
101
- * 符号化完成后触发 onError 回调,原样返回 event。
102
- */
103
- beforeSend(event: ErrorEvent, hint: EventHint): Promise<ErrorEvent>;
104
- /**
105
- * Sentry beforeBreadcrumb hook。
106
- * 识别 HTTP 类型的 breadcrumb,触发 onNetwork 回调,原样返回 breadcrumb。
107
- */
108
- beforeBreadcrumb(breadcrumb: Breadcrumb, _hint?: BreadcrumbHint): Breadcrumb;
109
- }
110
-
111
- export { type ErrorCaptureInfo, type MetroFrame, MetroSymbolicator, type NetworkCaptureInfo, SentryCapture, type SentryCaptureOptions };
2
+ export { }
package/dist/index.d.ts CHANGED
@@ -1,111 +1,2 @@
1
- import { ErrorEvent, EventHint, Breadcrumb, BreadcrumbHint } from '@sentry/core';
2
1
 
3
- /**
4
- * Sentry Capture 模块
5
- *
6
- * 提供 Metro 符号化(MetroSymbolicator)和 Sentry 事件捕获(SentryCapture)。
7
- */
8
-
9
- /** Metro /symbolicate 接口的帧格式(请求与响应共用)。 */
10
- interface MetroFrame {
11
- file: string;
12
- lineNumber: number;
13
- column: number;
14
- methodName: string;
15
- }
16
- /** SentryCapture.onError 回调参数 */
17
- interface ErrorCaptureInfo {
18
- message: string;
19
- symbolicatedFrames: MetroFrame[] | null;
20
- componentStack: string | null;
21
- /** Sentry Mechanism.type,如 'onerror' / 'generic' / 'auto.function.react.error_boundary' */
22
- mechanismType: string | null;
23
- }
24
- /** SentryCapture.onNetwork 回调参数 */
25
- interface NetworkCaptureInfo {
26
- method: string | null;
27
- url: string | null;
28
- statusCode: number | null;
29
- }
30
- interface SentryCaptureOptions {
31
- onError?: (info: ErrorCaptureInfo) => void;
32
- onNetwork?: (info: NetworkCaptureInfo) => void;
33
- /** 可注入 mock 实例以在单元测试中隔离网络依赖 */
34
- symbolicator?: MetroSymbolicator;
35
- }
36
- /**
37
- * Metro 符号化器。
38
- * 将 bundle 行列号还原为源码位置(通过 Metro /symbolicate 接口)。
39
- *
40
- * @example
41
- * const sym = new MetroSymbolicator();
42
- *
43
- * @example
44
- * // 单测:注入 baseUrl 并 mock fetch
45
- * const sym = new MetroSymbolicator({ baseUrl: 'http://localhost:8081' });
46
- * global.fetch = jest.fn().mockResolvedValue({ ok: true, json: async () => ({ stack: [...] }) });
47
- */
48
- declare class MetroSymbolicator {
49
- readonly baseUrl: string;
50
- constructor({ baseUrl }?: {
51
- baseUrl?: string;
52
- });
53
- /**
54
- * 从 NativeModules.SourceCode.scriptURL 推断 Metro server base URL。
55
- * web 平台使用相对路径;native 回退到 localhost:8081。
56
- */
57
- private static _getDefaultBaseUrl;
58
- /**
59
- * 解析 Error.stack 字符串为 Metro 所需的帧数组。
60
- *
61
- * 使用 stacktrace-parser 覆盖各 JS 引擎的格式:
62
- * V8/Node.js/Hermes(新):at methodName (url:line:col)
63
- * Gecko/Firefox/Hermes(旧):methodName@url:line:col
64
- * JavaScriptCore(iOS):methodName@url:line:col
65
- *
66
- * 无法符号化的帧(file 或 lineNumber 为 null)会被过滤掉。
67
- */
68
- parseErrorStack(stack: string): MetroFrame[];
69
- /**
70
- * 将帧列表发送到 Metro /symbolicate,返回符号化后的帧数组。
71
- */
72
- symbolicate(stack: MetroFrame[]): Promise<MetroFrame[] | null>;
73
- }
74
- /**
75
- * Sentry 事件捕获器。
76
- * 封装 beforeSend / beforeBreadcrumb 逻辑,通过注入的回调将事件数据交给调用方处理。
77
- *
78
- * @example
79
- * const capture = new SentryCapture({
80
- * onError(info) { console.log(info.message, info.symbolicatedFrames); },
81
- * onNetwork(info) { console.log(info.method, info.url, info.statusCode); },
82
- * });
83
- * Sentry.init({ dsn: '...', beforeSend: capture.beforeSend, beforeBreadcrumb: capture.beforeBreadcrumb });
84
- *
85
- * @example
86
- * // 单测:注入 mock symbolicator
87
- * const mockSym = { parseErrorStack: jest.fn(() => []), symbolicate: jest.fn(async () => null) };
88
- * const onError = jest.fn();
89
- * const capture = new SentryCapture({ onError, symbolicator: mockSym });
90
- * await capture.beforeSend(fakeEvent, { originalException: new Error('oops') });
91
- * expect(onError).toHaveBeenCalledWith(expect.objectContaining({ message: 'oops' }));
92
- */
93
- declare class SentryCapture {
94
- private readonly onError;
95
- private readonly onNetwork;
96
- private readonly symbolicator;
97
- constructor({ onError, onNetwork, symbolicator }?: SentryCaptureOptions);
98
- /**
99
- * Sentry beforeSend hook。
100
- * 优先使用原始 Error.stack 进行符号化;若无法获取则回退到 Sentry 事件帧。
101
- * 符号化完成后触发 onError 回调,原样返回 event。
102
- */
103
- beforeSend(event: ErrorEvent, hint: EventHint): Promise<ErrorEvent>;
104
- /**
105
- * Sentry beforeBreadcrumb hook。
106
- * 识别 HTTP 类型的 breadcrumb,触发 onNetwork 回调,原样返回 breadcrumb。
107
- */
108
- beforeBreadcrumb(breadcrumb: Breadcrumb, _hint?: BreadcrumbHint): Breadcrumb;
109
- }
110
-
111
- export { type ErrorCaptureInfo, type MetroFrame, MetroSymbolicator, type NetworkCaptureInfo, SentryCapture, type SentryCaptureOptions };
2
+ export { }
package/dist/index.js CHANGED
@@ -1,161 +1,2 @@
1
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
-
20
- // src/index.ts
21
- var src_exports = {};
22
- __export(src_exports, {
23
- MetroSymbolicator: () => MetroSymbolicator,
24
- SentryCapture: () => SentryCapture
25
- });
26
- module.exports = __toCommonJS(src_exports);
27
-
28
- // src/capture.ts
29
- var import_stacktrace_parser = require("stacktrace-parser");
30
- var import_react_native = require("react-native");
31
- var MetroSymbolicator = class _MetroSymbolicator {
32
- constructor({ baseUrl } = {}) {
33
- this.baseUrl = baseUrl ?? _MetroSymbolicator._getDefaultBaseUrl();
34
- }
35
- /**
36
- * 从 NativeModules.SourceCode.scriptURL 推断 Metro server base URL。
37
- * web 平台使用相对路径;native 回退到 localhost:8081。
38
- */
39
- static _getDefaultBaseUrl() {
40
- try {
41
- const scriptURL = import_react_native.NativeModules?.SourceCode?.scriptURL;
42
- if (scriptURL) {
43
- const url = new URL(scriptURL);
44
- return `${url.protocol}//${url.host}`;
45
- }
46
- } catch (_) {
47
- }
48
- return import_react_native.Platform.OS === "web" ? "" : "http://localhost:8081";
49
- }
50
- /**
51
- * 解析 Error.stack 字符串为 Metro 所需的帧数组。
52
- *
53
- * 使用 stacktrace-parser 覆盖各 JS 引擎的格式:
54
- * V8/Node.js/Hermes(新):at methodName (url:line:col)
55
- * Gecko/Firefox/Hermes(旧):methodName@url:line:col
56
- * JavaScriptCore(iOS):methodName@url:line:col
57
- *
58
- * 无法符号化的帧(file 或 lineNumber 为 null)会被过滤掉。
59
- */
60
- parseErrorStack(stack) {
61
- return (0, import_stacktrace_parser.parse)(stack).filter(
62
- (f) => f.file != null && f.lineNumber != null
63
- ).map((f) => ({
64
- file: f.file,
65
- lineNumber: f.lineNumber,
66
- column: f.column != null ? f.column - 1 : 0,
67
- methodName: f.methodName === "<unknown>" ? "(anonymous)" : f.methodName
68
- }));
69
- }
70
- /**
71
- * 将帧列表发送到 Metro /symbolicate,返回符号化后的帧数组。
72
- */
73
- async symbolicate(stack) {
74
- if (stack.length === 0) return null;
75
- const endpoint = `${this.baseUrl}/symbolicate`;
76
- try {
77
- const res = await fetch(endpoint, {
78
- method: "POST",
79
- headers: { "Content-Type": "application/json" },
80
- body: JSON.stringify({ stack })
81
- });
82
- if (!res.ok) {
83
- console.warn(`[SentryCapture] /symbolicate returned ${res.status}`);
84
- return null;
85
- }
86
- const data = await res.json();
87
- return data.stack ?? null;
88
- } catch (e) {
89
- console.warn("[SentryCapture] symbolication failed:", e?.message ?? e);
90
- return null;
91
- }
92
- }
93
- };
94
- var SentryCapture = class {
95
- constructor({ onError, onNetwork, symbolicator } = {}) {
96
- this.onError = onError;
97
- this.onNetwork = onNetwork;
98
- this.symbolicator = symbolicator ?? new MetroSymbolicator();
99
- this.beforeSend = this.beforeSend.bind(this);
100
- this.beforeBreadcrumb = this.beforeBreadcrumb.bind(this);
101
- }
102
- /**
103
- * Sentry beforeSend hook。
104
- * 优先使用原始 Error.stack 进行符号化;若无法获取则回退到 Sentry 事件帧。
105
- * 符号化完成后触发 onError 回调,原样返回 event。
106
- */
107
- async beforeSend(event, hint) {
108
- globalThis.__SENTRY_LAST_RAW__ = { event, hint };
109
- const message = event.exception?.values?.[0]?.value ?? event.message ?? "(unknown)";
110
- let symbolicatedFrames = null;
111
- const originalException = hint?.originalException;
112
- const rawStack = originalException instanceof Error && typeof originalException.stack === "string" ? originalException.stack : null;
113
- if (rawStack !== null) {
114
- const frames = this.symbolicator.parseErrorStack(rawStack);
115
- if (frames.length > 0) {
116
- symbolicatedFrames = await this.symbolicator.symbolicate(frames);
117
- }
118
- }
119
- if (symbolicatedFrames === null) {
120
- const sentryFrames = event.exception?.values?.[0]?.stacktrace?.frames ?? [];
121
- const mapped = sentryFrames.filter(
122
- (f) => f.filename != null && f.lineno != null
123
- ).map(
124
- (f) => ({
125
- file: f.filename,
126
- lineNumber: f.lineno,
127
- column: f.colno ?? 0,
128
- methodName: f.function ?? "(anonymous)"
129
- })
130
- );
131
- if (mapped.length > 0) {
132
- symbolicatedFrames = await this.symbolicator.symbolicate(mapped);
133
- }
134
- }
135
- const componentStack = event.contexts?.react?.componentStack ?? null;
136
- const mechanismType = event.exception?.values?.[0]?.mechanism?.type ?? null;
137
- this.onError?.({ message, symbolicatedFrames, componentStack, mechanismType });
138
- return event;
139
- }
140
- /**
141
- * Sentry beforeBreadcrumb hook。
142
- * 识别 HTTP 类型的 breadcrumb,触发 onNetwork 回调,原样返回 breadcrumb。
143
- */
144
- beforeBreadcrumb(breadcrumb, _hint) {
145
- if (breadcrumb.type === "http") {
146
- const data = breadcrumb.data;
147
- this.onNetwork?.({
148
- method: data?.method ?? null,
149
- url: data?.url ?? null,
150
- statusCode: data?.status_code ?? null
151
- });
152
- }
153
- return breadcrumb;
154
- }
155
- };
156
- // Annotate the CommonJS export names for ESM import in node:
157
- 0 && (module.exports = {
158
- MetroSymbolicator,
159
- SentryCapture
160
- });
161
2
  //# sourceMappingURL=index.js.map
package/dist/index.mjs CHANGED
@@ -1,133 +1 @@
1
- // src/capture.ts
2
- import { parse as parseStackTrace } from "stacktrace-parser";
3
- import { NativeModules, Platform } from "react-native";
4
- var MetroSymbolicator = class _MetroSymbolicator {
5
- constructor({ baseUrl } = {}) {
6
- this.baseUrl = baseUrl ?? _MetroSymbolicator._getDefaultBaseUrl();
7
- }
8
- /**
9
- * 从 NativeModules.SourceCode.scriptURL 推断 Metro server base URL。
10
- * web 平台使用相对路径;native 回退到 localhost:8081。
11
- */
12
- static _getDefaultBaseUrl() {
13
- try {
14
- const scriptURL = NativeModules?.SourceCode?.scriptURL;
15
- if (scriptURL) {
16
- const url = new URL(scriptURL);
17
- return `${url.protocol}//${url.host}`;
18
- }
19
- } catch (_) {
20
- }
21
- return Platform.OS === "web" ? "" : "http://localhost:8081";
22
- }
23
- /**
24
- * 解析 Error.stack 字符串为 Metro 所需的帧数组。
25
- *
26
- * 使用 stacktrace-parser 覆盖各 JS 引擎的格式:
27
- * V8/Node.js/Hermes(新):at methodName (url:line:col)
28
- * Gecko/Firefox/Hermes(旧):methodName@url:line:col
29
- * JavaScriptCore(iOS):methodName@url:line:col
30
- *
31
- * 无法符号化的帧(file 或 lineNumber 为 null)会被过滤掉。
32
- */
33
- parseErrorStack(stack) {
34
- return parseStackTrace(stack).filter(
35
- (f) => f.file != null && f.lineNumber != null
36
- ).map((f) => ({
37
- file: f.file,
38
- lineNumber: f.lineNumber,
39
- column: f.column != null ? f.column - 1 : 0,
40
- methodName: f.methodName === "<unknown>" ? "(anonymous)" : f.methodName
41
- }));
42
- }
43
- /**
44
- * 将帧列表发送到 Metro /symbolicate,返回符号化后的帧数组。
45
- */
46
- async symbolicate(stack) {
47
- if (stack.length === 0) return null;
48
- const endpoint = `${this.baseUrl}/symbolicate`;
49
- try {
50
- const res = await fetch(endpoint, {
51
- method: "POST",
52
- headers: { "Content-Type": "application/json" },
53
- body: JSON.stringify({ stack })
54
- });
55
- if (!res.ok) {
56
- console.warn(`[SentryCapture] /symbolicate returned ${res.status}`);
57
- return null;
58
- }
59
- const data = await res.json();
60
- return data.stack ?? null;
61
- } catch (e) {
62
- console.warn("[SentryCapture] symbolication failed:", e?.message ?? e);
63
- return null;
64
- }
65
- }
66
- };
67
- var SentryCapture = class {
68
- constructor({ onError, onNetwork, symbolicator } = {}) {
69
- this.onError = onError;
70
- this.onNetwork = onNetwork;
71
- this.symbolicator = symbolicator ?? new MetroSymbolicator();
72
- this.beforeSend = this.beforeSend.bind(this);
73
- this.beforeBreadcrumb = this.beforeBreadcrumb.bind(this);
74
- }
75
- /**
76
- * Sentry beforeSend hook。
77
- * 优先使用原始 Error.stack 进行符号化;若无法获取则回退到 Sentry 事件帧。
78
- * 符号化完成后触发 onError 回调,原样返回 event。
79
- */
80
- async beforeSend(event, hint) {
81
- globalThis.__SENTRY_LAST_RAW__ = { event, hint };
82
- const message = event.exception?.values?.[0]?.value ?? event.message ?? "(unknown)";
83
- let symbolicatedFrames = null;
84
- const originalException = hint?.originalException;
85
- const rawStack = originalException instanceof Error && typeof originalException.stack === "string" ? originalException.stack : null;
86
- if (rawStack !== null) {
87
- const frames = this.symbolicator.parseErrorStack(rawStack);
88
- if (frames.length > 0) {
89
- symbolicatedFrames = await this.symbolicator.symbolicate(frames);
90
- }
91
- }
92
- if (symbolicatedFrames === null) {
93
- const sentryFrames = event.exception?.values?.[0]?.stacktrace?.frames ?? [];
94
- const mapped = sentryFrames.filter(
95
- (f) => f.filename != null && f.lineno != null
96
- ).map(
97
- (f) => ({
98
- file: f.filename,
99
- lineNumber: f.lineno,
100
- column: f.colno ?? 0,
101
- methodName: f.function ?? "(anonymous)"
102
- })
103
- );
104
- if (mapped.length > 0) {
105
- symbolicatedFrames = await this.symbolicator.symbolicate(mapped);
106
- }
107
- }
108
- const componentStack = event.contexts?.react?.componentStack ?? null;
109
- const mechanismType = event.exception?.values?.[0]?.mechanism?.type ?? null;
110
- this.onError?.({ message, symbolicatedFrames, componentStack, mechanismType });
111
- return event;
112
- }
113
- /**
114
- * Sentry beforeBreadcrumb hook。
115
- * 识别 HTTP 类型的 breadcrumb,触发 onNetwork 回调,原样返回 breadcrumb。
116
- */
117
- beforeBreadcrumb(breadcrumb, _hint) {
118
- if (breadcrumb.type === "http") {
119
- const data = breadcrumb.data;
120
- this.onNetwork?.({
121
- method: data?.method ?? null,
122
- url: data?.url ?? null,
123
- statusCode: data?.status_code ?? null
124
- });
125
- }
126
- return breadcrumb;
127
- }
128
- };
129
- export {
130
- MetroSymbolicator,
131
- SentryCapture
132
- };
133
1
  //# sourceMappingURL=index.mjs.map
@@ -4,12 +4,10 @@ var import_lgui_control = require("./lgui-control");
4
4
  var import_css_control = require("./css-control");
5
5
  var import_router_control = require("./router-control");
6
6
  var import_screenshot_control = require("./screenshot-control");
7
- var import_preview_control = require("./preview-control");
8
7
  globalThis.__DEVKIT_INJECTED__ = true;
9
8
  (0, import_hmr_control.setupHMRMessageControl)();
10
9
  (0, import_lgui_control.setupLGUIMessageControl)();
11
10
  (0, import_css_control.setupCSSInjectionControl)();
12
11
  (0, import_router_control.setupRouterControl)();
13
12
  (0, import_screenshot_control.setupScreenshotControl)();
14
- (0, import_preview_control.setupPreviewControl)();
15
13
  //# sourceMappingURL=entry-inject.js.map
@@ -24,193 +24,28 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
 
25
25
  // src/stubs/sentry-react-native-stub.ts
26
26
  var RealSentry = __toESM(require("@sentry/react-native"));
27
-
28
- // src/capture.ts
29
- var import_stacktrace_parser = require("stacktrace-parser");
27
+ var import_core = require("@sentry/core");
30
28
  var import_react_native = require("react-native");
31
- var MetroSymbolicator = class _MetroSymbolicator {
32
- constructor({ baseUrl } = {}) {
33
- this.baseUrl = baseUrl ?? _MetroSymbolicator._getDefaultBaseUrl();
34
- }
35
- /**
36
- * 从 NativeModules.SourceCode.scriptURL 推断 Metro server base URL。
37
- * web 平台使用相对路径;native 回退到 localhost:8081。
38
- */
39
- static _getDefaultBaseUrl() {
40
- try {
41
- const scriptURL = import_react_native.NativeModules?.SourceCode?.scriptURL;
42
- if (scriptURL) {
43
- const url = new URL(scriptURL);
44
- return `${url.protocol}//${url.host}`;
45
- }
46
- } catch (_) {
47
- }
48
- return import_react_native.Platform.OS === "web" ? "" : "http://localhost:8081";
49
- }
50
- /**
51
- * 解析 Error.stack 字符串为 Metro 所需的帧数组。
52
- *
53
- * 使用 stacktrace-parser 覆盖各 JS 引擎的格式:
54
- * V8/Node.js/Hermes(新):at methodName (url:line:col)
55
- * Gecko/Firefox/Hermes(旧):methodName@url:line:col
56
- * JavaScriptCore(iOS):methodName@url:line:col
57
- *
58
- * 无法符号化的帧(file 或 lineNumber 为 null)会被过滤掉。
59
- */
60
- parseErrorStack(stack) {
61
- return (0, import_stacktrace_parser.parse)(stack).filter(
62
- (f) => f.file != null && f.lineNumber != null
63
- ).map((f) => ({
64
- file: f.file,
65
- lineNumber: f.lineNumber,
66
- column: f.column != null ? f.column - 1 : 0,
67
- methodName: f.methodName === "<unknown>" ? "(anonymous)" : f.methodName
68
- }));
69
- }
70
- /**
71
- * 将帧列表发送到 Metro /symbolicate,返回符号化后的帧数组。
72
- */
73
- async symbolicate(stack) {
74
- if (stack.length === 0) return null;
75
- const endpoint = `${this.baseUrl}/symbolicate`;
76
- try {
77
- const res = await fetch(endpoint, {
78
- method: "POST",
79
- headers: { "Content-Type": "application/json" },
80
- body: JSON.stringify({ stack })
81
- });
82
- if (!res.ok) {
83
- console.warn(`[SentryCapture] /symbolicate returned ${res.status}`);
84
- return null;
85
- }
86
- const data = await res.json();
87
- return data.stack ?? null;
88
- } catch (e) {
89
- console.warn("[SentryCapture] symbolication failed:", e?.message ?? e);
90
- return null;
91
- }
92
- }
93
- };
94
- var SentryCapture = class {
95
- constructor({ onError, onNetwork, symbolicator } = {}) {
96
- this.onError = onError;
97
- this.onNetwork = onNetwork;
98
- this.symbolicator = symbolicator ?? new MetroSymbolicator();
99
- this.beforeSend = this.beforeSend.bind(this);
100
- this.beforeBreadcrumb = this.beforeBreadcrumb.bind(this);
101
- }
102
- /**
103
- * Sentry beforeSend hook。
104
- * 优先使用原始 Error.stack 进行符号化;若无法获取则回退到 Sentry 事件帧。
105
- * 符号化完成后触发 onError 回调,原样返回 event。
106
- */
107
- async beforeSend(event, hint) {
108
- globalThis.__SENTRY_LAST_RAW__ = { event, hint };
109
- const message = event.exception?.values?.[0]?.value ?? event.message ?? "(unknown)";
110
- let symbolicatedFrames = null;
111
- const originalException = hint?.originalException;
112
- const rawStack = originalException instanceof Error && typeof originalException.stack === "string" ? originalException.stack : null;
113
- if (rawStack !== null) {
114
- const frames = this.symbolicator.parseErrorStack(rawStack);
115
- if (frames.length > 0) {
116
- symbolicatedFrames = await this.symbolicator.symbolicate(frames);
117
- }
118
- }
119
- if (symbolicatedFrames === null) {
120
- const sentryFrames = event.exception?.values?.[0]?.stacktrace?.frames ?? [];
121
- const mapped = sentryFrames.filter(
122
- (f) => f.filename != null && f.lineno != null
123
- ).map(
124
- (f) => ({
125
- file: f.filename,
126
- lineNumber: f.lineno,
127
- column: f.colno ?? 0,
128
- methodName: f.function ?? "(anonymous)"
129
- })
130
- );
131
- if (mapped.length > 0) {
132
- symbolicatedFrames = await this.symbolicator.symbolicate(mapped);
133
- }
134
- }
135
- const componentStack = event.contexts?.react?.componentStack ?? null;
136
- const mechanismType = event.exception?.values?.[0]?.mechanism?.type ?? null;
137
- this.onError?.({ message, symbolicatedFrames, componentStack, mechanismType });
138
- return event;
139
- }
140
- /**
141
- * Sentry beforeBreadcrumb hook。
142
- * 识别 HTTP 类型的 breadcrumb,触发 onNetwork 回调,原样返回 breadcrumb。
143
- */
144
- beforeBreadcrumb(breadcrumb, _hint) {
145
- if (breadcrumb.type === "http") {
146
- const data = breadcrumb.data;
147
- this.onNetwork?.({
148
- method: data?.method ?? null,
149
- url: data?.url ?? null,
150
- statusCode: data?.status_code ?? null
151
- });
152
- }
153
- return breadcrumb;
154
- }
155
- };
156
-
157
- // src/stubs/sentry-react-native-stub.ts
158
- function getParentOrigin() {
159
- try {
160
- const w = globalThis.window;
161
- if (!w) return "*";
162
- return new URLSearchParams(w.location.search).get("parentOrigin") || "*";
163
- } catch {
164
- return "*";
165
- }
29
+ var SANDBOX_MARKER = /* @__PURE__ */ Symbol.for("vite-sandbox.expo-browser-observability.v2");
30
+ var FALLBACK_DSN = process.env["INJECT_SENTRY_DSN"] ?? "https://stubPublicKey@o0.ingest.sentry.io/0";
31
+ function reportVersionMismatch(expected, actual) {
32
+ const message = `Sentry SDK \u7248\u672C\u4E0D\u4E00\u81F4\uFF1Asandbox=${expected}\uFF0C@sentry/react-native core=${actual || "unknown"}\u3002\u89C2\u6D4B\u4E8B\u4EF6\u53EF\u80FD\u9759\u9ED8\u4E22\u5931\u3002`;
33
+ console.error(`[miaoda-expo-devkit] ${message}`);
166
34
  }
167
- var OVERRIDE_DSN = process.env["INJECT_SENTRY_DSN"] ?? "https://stubPublicKey@o0.ingest.sentry.io/0";
168
- var _capture = new SentryCapture({
169
- onError(info) {
170
- console.warn("[SentryStub] \u6355\u83B7\u5230\u9519\u8BEF\uFF1A", info.message);
171
- if (info.symbolicatedFrames?.length) {
172
- const top = info.symbolicatedFrames[0];
173
- console.warn(` at ${top.methodName} (${top.file}:${top.lineNumber}:${top.column})`);
174
- }
175
- const w = globalThis.window;
176
- if (w !== void 0 && w !== w.parent) {
177
- w.parent.postMessage(
178
- {
179
- type: "GLOBAL_ERROR",
180
- error: {
181
- message: info.message,
182
- symbolicatedFrames: info.symbolicatedFrames,
183
- componentStack: info.componentStack,
184
- mechanismType: info.mechanismType
185
- }
186
- },
187
- getParentOrigin()
188
- );
189
- }
190
- }
191
- });
192
35
  var stub = {
193
36
  ...RealSentry,
194
37
  /**
195
- * 拦截 init():替换 DSN 并注入内置捕获器。
196
- * 调用方传入的 beforeSend / beforeBreadcrumb 在内置捕获器之后串联执行。
38
+ * Native 保留应用初始化;独立 Web 使用安全 DSN;sandbox Web 已由前置运行时
39
+ * 初始化,因此这里只校验版本并阻止第二次初始化。
197
40
  */
198
41
  init: ((options) => {
199
- const upstreamBeforeSend = options?.beforeSend;
200
- const upstreamBeforeBreadcrumb = options?.beforeBreadcrumb;
201
- const initOptions = {
202
- ...options,
203
- dsn: OVERRIDE_DSN,
204
- async beforeSend(event, hint) {
205
- const processed = await _capture.beforeSend(event, hint);
206
- return upstreamBeforeSend ? await upstreamBeforeSend(processed, hint) : processed;
207
- },
208
- beforeBreadcrumb(breadcrumb, hint) {
209
- const processed = _capture.beforeBreadcrumb(breadcrumb, hint);
210
- return upstreamBeforeBreadcrumb ? upstreamBeforeBreadcrumb(processed, hint) : processed;
211
- }
212
- };
213
- return RealSentry.init(initOptions);
42
+ if (import_react_native.Platform.OS !== "web") return RealSentry.init(options);
43
+ const sandboxVersion = globalThis[SANDBOX_MARKER];
44
+ if (typeof sandboxVersion === "string") {
45
+ if (sandboxVersion !== import_core.SDK_VERSION) reportVersionMismatch(sandboxVersion, import_core.SDK_VERSION);
46
+ return void 0;
47
+ }
48
+ return RealSentry.init({ ...options, dsn: FALLBACK_DSN });
214
49
  })
215
50
  };
216
51
  module.exports = stub;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "miaoda-expo-devkit",
3
- "version": "0.1.1-beta.111",
3
+ "version": "0.1.1-beta.113",
4
4
  "description": "Expo 应用开发工具集:Sentry DSN 替换 stub、错误/网络捕获、Metro 符号化",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.js",
@@ -90,8 +90,7 @@
90
90
  "lodash.throttle": "^4.1.1",
91
91
  "metro-minify-esbuild": "^0.2.0",
92
92
  "oxc-resolver": "^11.19.1",
93
- "package-up": "^5.0.0",
94
- "stacktrace-parser": "^0.1.11"
93
+ "package-up": "^5.0.0"
95
94
  },
96
95
  "peerDependencies": {
97
96
  "@expo/config-plugins": ">=55.0.0",
@@ -29,50 +29,7 @@ diff --git a/build/src/start/server/metro/instantiateMetro.js b/build/src/start/
29
29
  // This function ensures that modules in source maps are sorted in the same
30
30
  // order as in a plain JS bundle.
31
31
  metro._getSortedModules = function(graph) {
32
- @@ -388,6 +388,11 @@
33
- // Patch HMR Server to send more info to the `_createModuleId` function for deterministic module IDs and add support for serializing HMR updates the same as all other bundles.
34
- hmrServer._prepareMessage = async function(group, options, changeEvent) {
35
- // Fork of https://github.com/facebook/metro/blob/3b3e0aaf725cfa6907bf2c8b5fbc0da352d29efe/packages/metro/src/HmrServer.js#L327-L393
36
- // with patch for `_createModuleId`.
37
- const logger = !options.isInitialUpdate ? changeEvent == null ? void 0 : changeEvent.logger : null;
38
- + const hmrChangeID = `${changeEvent == null ? 'initial' : changeEvent.changeId}:${group.revisionId}`;
39
- + aiErrorCollector == null ? void 0 : aiErrorCollector.handleMetroEvent({
40
- + type: 'hmr_update_started',
41
- + changeID: hmrChangeID
42
- + });
43
- try {
44
- @@ -394,7 +399,13 @@
45
- const revPromise = this._bundler.getRevision(group.revisionId);
46
- if (!revPromise) {
47
- + const revisionError = new (_RevisionNotFoundError()).default(group.revisionId);
48
- + aiErrorCollector == null ? void 0 : aiErrorCollector.handleMetroEvent({
49
- + type: 'hmr_update_failed',
50
- + changeID: hmrChangeID,
51
- + error: revisionError
52
- + });
53
- return {
54
- type: 'error',
55
- - body: (0, _formatBundlingError().default)(new (_RevisionNotFoundError()).default(group.revisionId))
56
- + body: (0, _formatBundlingError().default)(revisionError)
57
- };
58
- }
59
- @@ -428,3 +439,7 @@
60
- });
61
- logger == null ? void 0 : logger.point('serialize_end');
62
- + aiErrorCollector == null ? void 0 : aiErrorCollector.handleMetroEvent({
63
- + type: 'hmr_update_done',
64
- + changeID: hmrChangeID
65
- + });
66
- return {
67
- @@ -438,4 +453,6 @@
68
- this._config.reporter.update({
69
- type: 'bundling_error',
70
- - error
71
- + error,
72
- + isHmr: true,
73
- + changeID: hmrChangeID
74
- });
75
- @@ -493,1 +515,1 @@
32
+ @@ -493,1 +498,1 @@
76
33
  -//# sourceMappingURL=instantiateMetro.js.map
77
34
 
78
35
  +//# sourceMappingURL=instantiateMetro.js.map
@@ -80,7 +37,7 @@ diff --git a/build/src/start/server/metro/aiErrorCollector.js b/build/src/start/
80
37
  new file mode 100644
81
38
  --- /dev/null
82
39
  +++ b/build/src/start/server/metro/aiErrorCollector.js
83
- @@ -0,0 +1,222 @@
40
+ @@ -0,0 +1,178 @@
84
41
  +"use strict";
85
42
  +Object.defineProperty(exports, "__esModule", {
86
43
  + value: true
@@ -187,7 +144,6 @@ new file mode 100644
187
144
  + const outputFile = getOutputPath(projectRoot);
188
145
  + let activeBuildID = null;
189
146
  + let lastFailedBuildID = null;
190
- + let activeHmrChangeID = null;
191
147
  + let state = {
192
148
  + schemaVersion: 1,
193
149
  + status: 'compiling',
@@ -223,41 +179,8 @@ new file mode 100644
223
179
  + if (!event || typeof event !== 'object') return;
224
180
  + const type = event.type;
225
181
  + const buildID = event.buildID ?? event.bundleDetails?.buildID ?? null;
226
- + if (type === 'hmr_update_started') {
227
- + activeHmrChangeID = typeof event.changeID === 'string' ? event.changeID : null;
228
- + lastFailedBuildID = null;
229
- + publish({
230
- + schemaVersion: 1,
231
- + status: 'compiling',
232
- + buildID: activeBuildID,
233
- + error: null
234
- + });
235
- + return;
236
- + }
237
- + if (type === 'hmr_update_failed') {
238
- + if (activeHmrChangeID == null || event.changeID !== activeHmrChangeID) return;
239
- + publish({
240
- + schemaVersion: 1,
241
- + status: 'failed',
242
- + buildID: activeBuildID,
243
- + error: normalizeError(event.error, projectRoot, activeBuildID)
244
- + });
245
- + return;
246
- + }
247
- + if (type === 'hmr_update_done') {
248
- + if (activeHmrChangeID == null || event.changeID !== activeHmrChangeID) return;
249
- + activeHmrChangeID = null;
250
- + publish({
251
- + schemaVersion: 1,
252
- + status: 'succeeded',
253
- + buildID: activeBuildID,
254
- + error: null
255
- + });
256
- + return;
257
- + }
258
182
  + if (type === 'bundle_build_started') {
259
183
  + activeBuildID = buildID;
260
- + activeHmrChangeID = null;
261
184
  + lastFailedBuildID = null;
262
185
  + publish({ schemaVersion: 1, status: 'compiling', buildID, error: null });
263
186
  + return;
@@ -274,16 +197,8 @@ new file mode 100644
274
197
  + return;
275
198
  + }
276
199
  + if (type === 'bundling_error') {
277
- + const isHmrError = event.isHmr === true;
278
- + if (isHmrError && activeHmrChangeID != null && event.changeID !== activeHmrChangeID) return;
279
- + const errorBuildID = isHmrError ? activeBuildID : buildID ?? lastFailedBuildID ?? activeBuildID;
200
+ + const errorBuildID = buildID ?? lastFailedBuildID ?? activeBuildID;
280
201
  + if (!isCurrentBuild(errorBuildID, true)) return;
281
- + if (isHmrError) {
282
- + // HMR errors do not carry Metro build IDs; keep the change token for its recovery event.
283
- + activeHmrChangeID = event.changeID ?? activeHmrChangeID;
284
- + }
285
- + lastFailedBuildID = errorBuildID;
286
- +
287
202
  + publish({
288
203
  + schemaVersion: 1,
289
204
  + status: 'failed',
@@ -294,8 +209,6 @@ new file mode 100644
294
209
  + }
295
210
  + if (type === 'bundle_build_done') {
296
211
  + if (!isCurrentBuild(buildID)) return;
297
- + activeHmrChangeID = null;
298
- + lastFailedBuildID = null;
299
212
  + publish({ schemaVersion: 1, status: 'succeeded', buildID: buildID ?? activeBuildID, error: null });
300
213
  + }
301
214
  + }
@@ -1,60 +0,0 @@
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 preview_control_exports = {};
20
- __export(preview_control_exports, {
21
- setupPreviewControl: () => setupPreviewControl
22
- });
23
- module.exports = __toCommonJS(preview_control_exports);
24
- function getParentOrigin() {
25
- try {
26
- const w = globalThis.window;
27
- if (!w) return "*";
28
- return new URLSearchParams(w.location.search).get("parentOrigin") || "*";
29
- } catch {
30
- return "*";
31
- }
32
- }
33
- function postPreviewSuccess() {
34
- const w = globalThis.window;
35
- if (!w || w.editorPreviewSuccessPosted) {
36
- return;
37
- }
38
- w.editorPreviewSuccessPosted = true;
39
- w.parent.postMessage(
40
- {
41
- type: "editor-preview-success"
42
- },
43
- getParentOrigin()
44
- );
45
- }
46
- function setupPreviewControl() {
47
- if (typeof document === "undefined" || typeof window === "undefined") {
48
- return;
49
- }
50
- if (document.readyState === "complete") {
51
- postPreviewSuccess();
52
- return;
53
- }
54
- window.addEventListener("load", postPreviewSuccess, { once: true });
55
- }
56
- // Annotate the CommonJS export names for ESM import in node:
57
- 0 && (module.exports = {
58
- setupPreviewControl
59
- });
60
- //# sourceMappingURL=preview-control.js.map