@expo/metro-runtime 6.0.2 → 6.1.0

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.
@@ -0,0 +1,2 @@
1
+ export declare function captureStackForServerLogs(): void;
2
+ //# sourceMappingURL=metroServerLogs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metroServerLogs.d.ts","sourceRoot":"","sources":["../src/metroServerLogs.ts"],"names":[],"mappings":"AAAA,wBAAgB,yBAAyB,SAAK"}
@@ -0,0 +1,2 @@
1
+ export declare function captureStackForServerLogs(): void;
2
+ //# sourceMappingURL=metroServerLogs.native.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metroServerLogs.native.d.ts","sourceRoot":"","sources":["../src/metroServerLogs.native.ts"],"names":[],"mappings":"AASA,wBAAgB,yBAAyB,SAyCxC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@expo/metro-runtime",
3
- "version": "6.0.2",
3
+ "version": "6.1.0",
4
4
  "description": "Tools for making advanced Metro bundler features work",
5
5
  "sideEffects": true,
6
6
  "main": "src/index.ts",
@@ -53,5 +53,5 @@
53
53
  "devDependencies": {
54
54
  "react-dom": "19.1.0"
55
55
  },
56
- "gitHead": "eaa9b645058cf2233fbb27bb21a19bc605c90a88"
56
+ "gitHead": "de2ce610a804a8f3ddcee6ea9474543e81b903bd"
57
57
  }
package/src/index.ts CHANGED
@@ -10,12 +10,12 @@ import './location/install';
10
10
  import '@expo/metro-runtime/rsc/runtime';
11
11
 
12
12
  if (__DEV__) {
13
+ require('./metroServerLogs').captureStackForServerLogs();
14
+
13
15
  // TODO: Remove when fixed upstream. Expected in RN 0.82.
14
16
  // https://github.com/facebook/react-native/commit/c4082c9ce208a324c2d011823ca2ba432411aafc
15
17
  require('./promiseRejectionTracking').enablePromiseRejectionTracking();
16
- }
17
18
 
18
- if (__DEV__) {
19
19
  // @ts-expect-error: TODO: Remove this when we remove the log box.
20
20
  globalThis.__expo_dev_resetErrors = require('./error-overlay/LogBox').default.clearAllLogs;
21
21
  }
@@ -0,0 +1,88 @@
1
+ import React from 'react';
2
+
3
+ const HMRClient = require('react-native/Libraries/Utilities/HMRClient').default;
4
+
5
+ type ReactWithOwnerStack = typeof React & {
6
+ captureOwnerStack?: () => string | null;
7
+ };
8
+ const captureOwnerStack = (React as ReactWithOwnerStack).captureOwnerStack;
9
+
10
+ export function captureStackForServerLogs() {
11
+ const HMRClientLogOriginal = HMRClient.log;
12
+
13
+ HMRClient.log = (level: string, data: unknown[]) => {
14
+ // NOTE: Should we print stack trace for warnings as well?
15
+ if (![/*'warn',*/ 'error'].includes(level)) {
16
+ // If the log level is not warn or error, we don't need to capture the stack trace.
17
+ return HMRClientLogOriginal.apply(HMRClient, [level, data]);
18
+ }
19
+
20
+ const hasErrorLikeStack = data.some((item) => hasStringKey(item, 'stack'));
21
+ const hasComponentStack = data.some(
22
+ (item) =>
23
+ (typeof item === 'string' && isComponentStack(withoutANSIColorStyles(item))) ||
24
+ (hasStringKey(item, 'message') && isComponentStack(withoutANSIColorStyles(item.message)))
25
+ );
26
+
27
+ // This is not an Expo error. It's used only to capture the stack trace of the log call.
28
+ // If you see this, look higher in the stack to find the actual cause.
29
+ const syntheticStack = hasErrorLikeStack ? undefined : captureCurrentStack();
30
+ const componentStack = hasComponentStack ? undefined : captureOwnerStack?.();
31
+
32
+ const dataWithStack = [...data];
33
+ if (syntheticStack) {
34
+ dataWithStack.push(syntheticStack);
35
+ } else {
36
+ data.forEach((item) => {
37
+ if (hasStringKey(item, 'stack')) {
38
+ // We have to explicitly push the stack to the data array
39
+ // because otherwise it will be lost by `pretty-format`.
40
+ // TODO: Remove message from the stack to avoid duplication (message from `pretty-format` and from our added stack).
41
+ dataWithStack.push(item.stack);
42
+ }
43
+ });
44
+ }
45
+ if (componentStack) {
46
+ dataWithStack.push(componentStack);
47
+ }
48
+
49
+ HMRClientLogOriginal.apply(HMRClient, [level, dataWithStack]);
50
+ };
51
+ }
52
+
53
+ // Based on https://github.com/facebook/react-native/blob/a8bc74c0099252cb1d11ad3b80f3deac71dcc0d5/packages/react-native/Libraries/LogBox/Data/parseLogBoxLog.js#L447
54
+ function withoutANSIColorStyles(message: string): string {
55
+ return message.replace(
56
+ // eslint-disable-next-line no-control-regex
57
+ /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,
58
+ ''
59
+ );
60
+ }
61
+
62
+ // Based on https://github.com/facebook/react-native/blob/a8bc74c0099252cb1d11ad3b80f3deac71dcc0d5/packages/react-native/Libraries/LogBox/Data/parseLogBoxLog.js#L214
63
+ const RE_COMPONENT_STACK_LINE_OLD = / {4}in/;
64
+ const RE_COMPONENT_STACK_LINE_NEW = / {4}at/;
65
+ const RE_COMPONENT_STACK_LINE_STACK_FRAME = /@.*\n/;
66
+ function isComponentStack(consoleArgument: string) {
67
+ const isOldComponentStackFormat = RE_COMPONENT_STACK_LINE_OLD.test(consoleArgument);
68
+ const isNewComponentStackFormat = RE_COMPONENT_STACK_LINE_NEW.test(consoleArgument);
69
+ const isNewJSCComponentStackFormat = RE_COMPONENT_STACK_LINE_STACK_FRAME.test(consoleArgument);
70
+
71
+ return isOldComponentStackFormat || isNewComponentStackFormat || isNewJSCComponentStackFormat;
72
+ }
73
+
74
+ function hasStringKey(obj: unknown, key: string): obj is { [key: string]: string } {
75
+ return (
76
+ typeof obj === 'object' &&
77
+ obj !== null &&
78
+ key in obj &&
79
+ typeof (obj as Record<string, unknown>)[key] === 'string'
80
+ );
81
+ }
82
+
83
+ class NamelessError extends Error {
84
+ name = '';
85
+ }
86
+ function captureCurrentStack() {
87
+ return new NamelessError().stack;
88
+ }
@@ -0,0 +1 @@
1
+ export function captureStackForServerLogs() {}