babel-preset-expo 0.0.1-canary-20240222-c729a13 → 0.0.1-canary-20240305-e60019e
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/build/client-module-proxy-plugin.d.ts +7 -0
- package/build/client-module-proxy-plugin.js +98 -0
- package/build/common.d.ts +2 -0
- package/build/common.js +6 -1
- package/build/environment-restricted-imports.d.ts +8 -0
- package/build/environment-restricted-imports.js +61 -0
- package/build/index.js +12 -0
- package/build/restricted-react-api-plugin.d.ts +7 -0
- package/build/restricted-react-api-plugin.js +94 -0
- package/package.json +3 -3
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.reactClientReferencesPlugin = void 0;
|
|
7
|
+
const url_1 = __importDefault(require("url"));
|
|
8
|
+
function reactClientReferencesPlugin(api) {
|
|
9
|
+
const { types: t } = api;
|
|
10
|
+
const reactServerAdapter = 'react-server-dom-webpack/server';
|
|
11
|
+
return {
|
|
12
|
+
name: 'expo-client-references',
|
|
13
|
+
visitor: {
|
|
14
|
+
Program(path, state) {
|
|
15
|
+
const isUseClient = path.node.directives.some((directive) => directive.value.value === 'use client');
|
|
16
|
+
// TODO: use server can be added to scopes inside of the file. https://github.com/facebook/react/blob/29fbf6f62625c4262035f931681c7b7822ca9843/packages/react-server-dom-webpack/src/ReactFlightWebpackNodeRegister.js#L55
|
|
17
|
+
const isUseServer = path.node.directives.some((directive) => directive.value.value === 'use server');
|
|
18
|
+
if (isUseClient && isUseServer) {
|
|
19
|
+
throw path.buildCodeFrameError("It's not possible to have both `use client` and `use server` directives in the same file.");
|
|
20
|
+
}
|
|
21
|
+
const filePath = state.file.opts.filename;
|
|
22
|
+
if (!filePath) {
|
|
23
|
+
// This can happen in tests or systems that use Babel standalone.
|
|
24
|
+
throw new Error('[Babel] Expected a filename to be set in the state');
|
|
25
|
+
}
|
|
26
|
+
const outputKey = url_1.default.pathToFileURL(filePath).href;
|
|
27
|
+
// File starts with "use client" directive.
|
|
28
|
+
if (!isUseClient && !isUseServer) {
|
|
29
|
+
// Do nothing for code that isn't marked as a client component.
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
// Clear the body
|
|
33
|
+
if (isUseClient) {
|
|
34
|
+
path.node.body = [];
|
|
35
|
+
path.node.directives = [];
|
|
36
|
+
// Inject the following:
|
|
37
|
+
//
|
|
38
|
+
// module.exports = require('react-server-dom-webpack/server').createClientModuleProxy(`${outputKey}#${filePath}`)
|
|
39
|
+
// TODO: Use `require.resolveWeak` instead of `filePath` to avoid leaking the file path.
|
|
40
|
+
// module.exports = require('react-server-dom-webpack/server').createClientModuleProxy(`${outputKey}#${require.resolveWeak(filePath)}`)
|
|
41
|
+
path.pushContainer('body', t.expressionStatement(t.assignmentExpression('=', t.memberExpression(t.identifier('module'), t.identifier('exports')), t.callExpression(t.memberExpression(t.callExpression(t.identifier('require'), [
|
|
42
|
+
t.stringLiteral(reactServerAdapter),
|
|
43
|
+
]), t.identifier('createClientModuleProxy')),
|
|
44
|
+
// `${outputKey}#${require.resolveWeak(filePath)}`
|
|
45
|
+
[t.stringLiteral(outputKey)]))));
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
// Inject the following:
|
|
49
|
+
//
|
|
50
|
+
// ;(() => {
|
|
51
|
+
// const { registerServerReference } = require('react-server-dom-webpack/server');
|
|
52
|
+
// if (typeof module.exports === 'function') registerServerReference(module.exports, moduleId, null);
|
|
53
|
+
// else {
|
|
54
|
+
// for (const key in module.exports) {
|
|
55
|
+
// if (typeof module.exports[key] === 'function') {
|
|
56
|
+
// registerServerReference(module.exports[key], moduleId, key);
|
|
57
|
+
// }
|
|
58
|
+
// }
|
|
59
|
+
// }
|
|
60
|
+
// })()
|
|
61
|
+
const mmexp = t.memberExpression(t.callExpression(t.identifier('require'), [t.stringLiteral(reactServerAdapter)]), t.identifier('registerServerReference'));
|
|
62
|
+
// Create the loop body
|
|
63
|
+
const loopBody = t.blockStatement([
|
|
64
|
+
t.ifStatement(t.binaryExpression('===', t.unaryExpression('typeof', t.memberExpression(t.memberExpression(t.identifier('module'), t.identifier('exports')), t.identifier('key'), true)), t.stringLiteral('function')), t.expressionStatement(t.callExpression(mmexp, [
|
|
65
|
+
t.memberExpression(t.memberExpression(t.identifier('module'), t.identifier('exports')), t.identifier('key'), true),
|
|
66
|
+
t.stringLiteral(outputKey),
|
|
67
|
+
t.identifier('key'),
|
|
68
|
+
]))),
|
|
69
|
+
]);
|
|
70
|
+
// Create the for-in loop
|
|
71
|
+
const forInStatement = t.forInStatement(t.variableDeclaration('const', [t.variableDeclarator(t.identifier('key'))]), t.memberExpression(t.identifier('module'), t.identifier('exports')), loopBody);
|
|
72
|
+
path.pushContainer('body', t.expressionStatement(t.callExpression(t.arrowFunctionExpression([], t.blockStatement([
|
|
73
|
+
t.ifStatement(t.binaryExpression('===', t.unaryExpression('typeof', t.memberExpression(t.identifier('module'), t.identifier('exports'))), t.stringLiteral('function')),
|
|
74
|
+
// registerServerReference(module.exports, moduleId, null);
|
|
75
|
+
t.blockStatement([
|
|
76
|
+
t.expressionStatement(t.callExpression(mmexp, [
|
|
77
|
+
t.memberExpression(t.identifier('module'), t.identifier('exports')),
|
|
78
|
+
t.stringLiteral(outputKey),
|
|
79
|
+
t.nullLiteral(),
|
|
80
|
+
])),
|
|
81
|
+
]),
|
|
82
|
+
// Else
|
|
83
|
+
t.blockStatement([
|
|
84
|
+
// for (const key in module.exports) {
|
|
85
|
+
// if (typeof module.exports[key] === 'function') {
|
|
86
|
+
// registerServerReference(module.exports[key], moduleId, key);
|
|
87
|
+
// }
|
|
88
|
+
// }
|
|
89
|
+
forInStatement,
|
|
90
|
+
])),
|
|
91
|
+
])), [])));
|
|
92
|
+
//
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
exports.reactClientReferencesPlugin = reactClientReferencesPlugin;
|
package/build/common.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ export declare function hasModule(name: string): boolean;
|
|
|
3
3
|
export declare function getBundler(caller: any): any;
|
|
4
4
|
export declare function getPlatform(caller: any): any;
|
|
5
5
|
export declare function getPossibleProjectRoot(caller: any): any;
|
|
6
|
+
/** If bundling for a react-server target. */
|
|
7
|
+
export declare function getIsReactServer(caller: any): boolean;
|
|
6
8
|
export declare function getIsDev(caller: any): any;
|
|
7
9
|
export declare function getIsFastRefreshEnabled(caller: any): any;
|
|
8
10
|
export declare function getIsProd(caller: any): boolean;
|
package/build/common.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.getAsyncRoutes = exports.getInlineEnvVarsEnabled = exports.getExpoRouterAbsoluteAppRoot = exports.getIsServer = exports.getBaseUrl = exports.getIsNodeModule = exports.getIsProd = exports.getIsFastRefreshEnabled = exports.getIsDev = exports.getPossibleProjectRoot = exports.getPlatform = exports.getBundler = exports.hasModule = void 0;
|
|
6
|
+
exports.getAsyncRoutes = exports.getInlineEnvVarsEnabled = exports.getExpoRouterAbsoluteAppRoot = exports.getIsServer = exports.getBaseUrl = exports.getIsNodeModule = exports.getIsProd = exports.getIsFastRefreshEnabled = exports.getIsDev = exports.getIsReactServer = exports.getPossibleProjectRoot = exports.getPlatform = exports.getBundler = exports.hasModule = void 0;
|
|
7
7
|
const path_1 = __importDefault(require("path"));
|
|
8
8
|
function hasModule(name) {
|
|
9
9
|
try {
|
|
@@ -56,6 +56,11 @@ function getPossibleProjectRoot(caller) {
|
|
|
56
56
|
return process.env.EXPO_PROJECT_ROOT;
|
|
57
57
|
}
|
|
58
58
|
exports.getPossibleProjectRoot = getPossibleProjectRoot;
|
|
59
|
+
/** If bundling for a react-server target. */
|
|
60
|
+
function getIsReactServer(caller) {
|
|
61
|
+
return caller?.isReactServer ?? false;
|
|
62
|
+
}
|
|
63
|
+
exports.getIsReactServer = getIsReactServer;
|
|
59
64
|
function getIsDev(caller) {
|
|
60
65
|
if (caller?.isDev != null)
|
|
61
66
|
return caller.isDev;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright © 2024 650 Industries.
|
|
3
|
+
*/
|
|
4
|
+
import { ConfigAPI, types } from '@babel/core';
|
|
5
|
+
/** Prevent importing certain known imports in given environments. This is for sanity to ensure a module never accidentally gets imported unexpectedly. */
|
|
6
|
+
export declare function environmentRestrictedImportsPlugin(api: ConfigAPI & {
|
|
7
|
+
types: typeof types;
|
|
8
|
+
}): babel.PluginObj;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.environmentRestrictedImportsPlugin = void 0;
|
|
4
|
+
const common_1 = require("./common");
|
|
5
|
+
const FORBIDDEN_CLIENT_IMPORTS = ['server-only'];
|
|
6
|
+
const FORBIDDEN_REACT_SERVER_IMPORTS = ['client-only'];
|
|
7
|
+
/** Prevent importing certain known imports in given environments. This is for sanity to ensure a module never accidentally gets imported unexpectedly. */
|
|
8
|
+
function environmentRestrictedImportsPlugin(api) {
|
|
9
|
+
const { types: t } = api;
|
|
10
|
+
const isReactServer = api.caller(common_1.getIsReactServer);
|
|
11
|
+
const forbiddenPackages = isReactServer
|
|
12
|
+
? FORBIDDEN_REACT_SERVER_IMPORTS
|
|
13
|
+
: FORBIDDEN_CLIENT_IMPORTS;
|
|
14
|
+
function checkSource(source, path) {
|
|
15
|
+
forbiddenPackages.forEach((forbiddenImport) => {
|
|
16
|
+
if (source === forbiddenImport) {
|
|
17
|
+
if (isReactServer) {
|
|
18
|
+
throw path.buildCodeFrameError(`Importing '${forbiddenImport}' module is not allowed in a React server bundle. Add the "use client" directive to this file or one of the parent modules to allow importing this module.`);
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
throw path.buildCodeFrameError(`Importing '${forbiddenImport}' module is not allowed in a client component.`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
name: 'expo-environment-restricted-imports-plugin',
|
|
28
|
+
visitor: {
|
|
29
|
+
ImportDeclaration(path) {
|
|
30
|
+
checkSource(path.node.source.value, path);
|
|
31
|
+
},
|
|
32
|
+
ExportAllDeclaration(path) {
|
|
33
|
+
if (path.node.source) {
|
|
34
|
+
checkSource(path.node.source.value, path);
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
ExportNamedDeclaration(path) {
|
|
38
|
+
if (path.node.source) {
|
|
39
|
+
checkSource(path.node.source.value, path);
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
CallExpression(path) {
|
|
43
|
+
if ((('name' in path.node.callee && path.node.callee.name === 'require') ||
|
|
44
|
+
(t.isMemberExpression(path.node.callee) &&
|
|
45
|
+
'name' in path.node.callee.property &&
|
|
46
|
+
['resolveWeak', 'importAll', 'importDefault'].includes(path.node.callee.property.name))) &&
|
|
47
|
+
path.node.arguments.length > 0 &&
|
|
48
|
+
t.isStringLiteral(path.node.arguments[0])) {
|
|
49
|
+
checkSource(path.node.arguments[0].value, path);
|
|
50
|
+
}
|
|
51
|
+
// Handle dynamic import() syntax
|
|
52
|
+
else if (path.node.callee.type === 'Import' &&
|
|
53
|
+
path.node.arguments.length > 0 &&
|
|
54
|
+
t.isStringLiteral(path.node.arguments[0])) {
|
|
55
|
+
checkSource(path.node.arguments[0].value, path);
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
exports.environmentRestrictedImportsPlugin = environmentRestrictedImportsPlugin;
|
package/build/index.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const client_module_proxy_plugin_1 = require("./client-module-proxy-plugin");
|
|
3
4
|
const common_1 = require("./common");
|
|
5
|
+
const environment_restricted_imports_1 = require("./environment-restricted-imports");
|
|
4
6
|
const expo_inline_manifest_plugin_1 = require("./expo-inline-manifest-plugin");
|
|
5
7
|
const expo_router_plugin_1 = require("./expo-router-plugin");
|
|
6
8
|
const inline_env_vars_1 = require("./inline-env-vars");
|
|
7
9
|
const lazyImports_1 = require("./lazyImports");
|
|
10
|
+
const restricted_react_api_plugin_1 = require("./restricted-react-api-plugin");
|
|
8
11
|
function getOptions(options, platform) {
|
|
9
12
|
const tag = platform === 'web' ? 'web' : 'native';
|
|
10
13
|
return {
|
|
@@ -18,6 +21,7 @@ function babelPresetExpo(api, options = {}) {
|
|
|
18
21
|
let platform = api.caller((caller) => caller?.platform);
|
|
19
22
|
const engine = api.caller((caller) => caller?.engine) ?? 'default';
|
|
20
23
|
const isDev = api.caller(common_1.getIsDev);
|
|
24
|
+
const isReactServer = api.caller(common_1.getIsReactServer);
|
|
21
25
|
const isFastRefreshEnabled = api.caller(common_1.getIsFastRefreshEnabled);
|
|
22
26
|
const baseUrl = api.caller(common_1.getBaseUrl);
|
|
23
27
|
const supportsStaticESM = api.caller((caller) => caller?.supportsStaticESM);
|
|
@@ -106,6 +110,14 @@ function babelPresetExpo(api, options = {}) {
|
|
|
106
110
|
if ((0, common_1.hasModule)('expo-router')) {
|
|
107
111
|
extraPlugins.push(expo_router_plugin_1.expoRouterBabelPlugin);
|
|
108
112
|
}
|
|
113
|
+
// Ensure these only run when the user opts-in to bundling for a react server to prevent unexpected behavior for
|
|
114
|
+
// users who are bundling using the client-only system.
|
|
115
|
+
if (isReactServer) {
|
|
116
|
+
extraPlugins.push(client_module_proxy_plugin_1.reactClientReferencesPlugin);
|
|
117
|
+
extraPlugins.push(restricted_react_api_plugin_1.environmentRestrictedReactAPIsPlugin);
|
|
118
|
+
}
|
|
119
|
+
// This plugin is fine to run whenever as the server-only imports were introduced as part of RSC and shouldn't be used in any client code.
|
|
120
|
+
extraPlugins.push(environment_restricted_imports_1.environmentRestrictedImportsPlugin);
|
|
109
121
|
if (isFastRefreshEnabled) {
|
|
110
122
|
extraPlugins.push([
|
|
111
123
|
require('react-refresh/babel'),
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.environmentRestrictedReactAPIsPlugin = void 0;
|
|
4
|
+
const INVALID_SERVER_REACT_DOM_APIS = [
|
|
5
|
+
'findDOMNode',
|
|
6
|
+
'flushSync',
|
|
7
|
+
'unstable_batchedUpdates',
|
|
8
|
+
'useFormStatus',
|
|
9
|
+
'useFormState',
|
|
10
|
+
];
|
|
11
|
+
// From the React docs: https://github.com/vercel/next.js/blob/d43a387d271263f2c1c4da6b9db826e382fc489c/packages/next-swc/crates/next-custom-transforms/src/transforms/react_server_components.rs#L665-L681
|
|
12
|
+
const INVALID_SERVER_REACT_APIS = [
|
|
13
|
+
'Component',
|
|
14
|
+
'createContext',
|
|
15
|
+
'createFactory',
|
|
16
|
+
'PureComponent',
|
|
17
|
+
'useDeferredValue',
|
|
18
|
+
'useEffect',
|
|
19
|
+
'useImperativeHandle',
|
|
20
|
+
'useInsertionEffect',
|
|
21
|
+
'useLayoutEffect',
|
|
22
|
+
'useReducer',
|
|
23
|
+
'useRef',
|
|
24
|
+
'useState',
|
|
25
|
+
'useSyncExternalStore',
|
|
26
|
+
'useTransition',
|
|
27
|
+
'useOptimistic',
|
|
28
|
+
];
|
|
29
|
+
function isNodeModule(path) {
|
|
30
|
+
return path != null && /[\\/]node_modules[\\/]/.test(path);
|
|
31
|
+
}
|
|
32
|
+
// Restricts imports from `react` and `react-dom` when using React Server Components.
|
|
33
|
+
const FORBIDDEN_IMPORTS = {
|
|
34
|
+
react: INVALID_SERVER_REACT_APIS,
|
|
35
|
+
'react-dom': INVALID_SERVER_REACT_DOM_APIS,
|
|
36
|
+
};
|
|
37
|
+
function environmentRestrictedReactAPIsPlugin(api) {
|
|
38
|
+
const { types: t } = api;
|
|
39
|
+
return {
|
|
40
|
+
name: 'expo-environment-restricted-react-api-plugin',
|
|
41
|
+
visitor: {
|
|
42
|
+
ImportDeclaration(path, state) {
|
|
43
|
+
// Skip node_modules
|
|
44
|
+
if (isNodeModule(state.file.opts.filename)) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const sourceValue = path.node.source.value;
|
|
48
|
+
const forbiddenList = FORBIDDEN_IMPORTS[sourceValue];
|
|
49
|
+
if (forbiddenList) {
|
|
50
|
+
path.node.specifiers.forEach((specifier) => {
|
|
51
|
+
if (t.isImportSpecifier(specifier)) {
|
|
52
|
+
const importName = t.isStringLiteral(specifier.imported)
|
|
53
|
+
? specifier.imported.value
|
|
54
|
+
: specifier.imported.name;
|
|
55
|
+
// Check for both named and namespace imports
|
|
56
|
+
const isForbidden = forbiddenList.includes(importName);
|
|
57
|
+
if (isForbidden) {
|
|
58
|
+
// Add special handling for `Component` since it is different to a function API.
|
|
59
|
+
throw path.buildCodeFrameError(`Client-only "${sourceValue}" API "${importName}" cannot be imported in a React server component. Add the "use client" directive to the top of this file or one of the parent files to enable running this stateful code on a user's device.`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
const importName = t.isStringLiteral(specifier.local)
|
|
64
|
+
? specifier.local
|
|
65
|
+
: specifier.local.name;
|
|
66
|
+
// Save namespace import for later checks in MemberExpression
|
|
67
|
+
path.scope.setData('importedNamespace', { [importName]: sourceValue });
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
MemberExpression(path) {
|
|
73
|
+
const importedNamespaces = path.scope.getData('importedNamespace') || {};
|
|
74
|
+
Object.keys(importedNamespaces).forEach((namespace) => {
|
|
75
|
+
const library = importedNamespaces[namespace];
|
|
76
|
+
const forbiddenList = FORBIDDEN_IMPORTS[library];
|
|
77
|
+
const objectName = t.isIdentifier(path.node.object) ? path.node.object.name : null;
|
|
78
|
+
if (objectName === namespace &&
|
|
79
|
+
forbiddenList &&
|
|
80
|
+
t.isIdentifier(path.node.property) &&
|
|
81
|
+
forbiddenList.includes(path.node.property.name)) {
|
|
82
|
+
// Throw a special error for class components since it's not always clear why they cannot be used in RSC.
|
|
83
|
+
// e.g. https://x.com/Baconbrix/status/1749223042440392806?s=20
|
|
84
|
+
if (path.node.property.name === 'Component') {
|
|
85
|
+
throw path.buildCodeFrameError(`Class components cannot be used in a React server component due to their ability to contain stateful and interactive APIs that cannot be statically evaluated in non-interactive environments such as a server or at build-time. Migrate to a function component, or add the "use client" directive to the top of this file or one of the parent files to render this class component on a user's device.`);
|
|
86
|
+
}
|
|
87
|
+
throw path.buildCodeFrameError(`Client-only "${namespace}" API "${path.node.property.name}" cannot be used in a React server component. Add the "use client" directive to the top of this file or one of the parent files to enable running this stateful code on a user's device.`);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
exports.environmentRestrictedReactAPIsPlugin = environmentRestrictedReactAPIsPlugin;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "babel-preset-expo",
|
|
3
|
-
"version": "0.0.1-canary-
|
|
3
|
+
"version": "0.0.1-canary-20240305-e60019e",
|
|
4
4
|
"description": "The Babel preset for Expo projects",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"files": [
|
|
@@ -53,8 +53,8 @@
|
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@babel/core": "^7.20.0",
|
|
56
|
-
"expo-module-scripts": "0.0.1-canary-
|
|
56
|
+
"expo-module-scripts": "0.0.1-canary-20240305-e60019e",
|
|
57
57
|
"jest": "^29.2.1"
|
|
58
58
|
},
|
|
59
|
-
"gitHead": "
|
|
59
|
+
"gitHead": "e60019e11a6d46e330b57b18c64468a58d589875"
|
|
60
60
|
}
|