18d307f52SEvan Baconimport resolveFrom from 'resolve-from';
28d307f52SEvan Bacon
3036e9444SEvan Baconconst debug = require('debug')('expo:metro:import');
4036e9444SEvan Bacon
58d307f52SEvan Bacon// These resolvers enable us to test the CLI in older projects.
68d307f52SEvan Bacon// We may be able to get rid of this in the future.
78d307f52SEvan Bacon// TODO: Maybe combine with AsyncResolver?
88d307f52SEvan Baconclass MetroImportError extends Error {
98d307f52SEvan Bacon  constructor(projectRoot: string, moduleId: string) {
108d307f52SEvan Bacon    super(
118d307f52SEvan Bacon      `Missing package "${moduleId}" in the project at: ${projectRoot}\n` +
128d307f52SEvan Bacon        'This usually means "react-native" is not installed. ' +
138d307f52SEvan Bacon        'Please verify that dependencies in package.json include "react-native" ' +
148d307f52SEvan Bacon        'and run `yarn` or `npm install`.'
158d307f52SEvan Bacon    );
168d307f52SEvan Bacon  }
178d307f52SEvan Bacon}
188d307f52SEvan Bacon
19*edeec536SEvan Baconexport function importCliServerApiFromProject(
20*edeec536SEvan Bacon  projectRoot: string
21*edeec536SEvan Bacon): typeof import('@react-native-community/cli-server-api') {
22*edeec536SEvan Bacon  return importFromProject(projectRoot, '@react-native-community/cli-server-api');
23*edeec536SEvan Bacon}
24*edeec536SEvan Bacon
25*edeec536SEvan Baconexport function importMetroSourceMapComposeSourceMapsFromProject(
26*edeec536SEvan Bacon  projectRoot: string
27*edeec536SEvan Bacon): typeof import('metro-source-map').composeSourceMaps {
28*edeec536SEvan Bacon  return importFromProject(projectRoot, 'metro-source-map/src/composeSourceMaps');
29*edeec536SEvan Bacon}
30*edeec536SEvan Bacon
31*edeec536SEvan Baconexport function resolveFromProject(projectRoot: string, moduleId: string) {
328d307f52SEvan Bacon  const resolvedPath = resolveFrom.silent(projectRoot, moduleId);
338d307f52SEvan Bacon  if (!resolvedPath) {
348d307f52SEvan Bacon    throw new MetroImportError(projectRoot, moduleId);
358d307f52SEvan Bacon  }
368d307f52SEvan Bacon  return resolvedPath;
378d307f52SEvan Bacon}
388d307f52SEvan Bacon
398d307f52SEvan Baconfunction importFromProject(projectRoot: string, moduleId: string) {
408d307f52SEvan Bacon  return require(resolveFromProject(projectRoot, moduleId));
418d307f52SEvan Bacon}
428d307f52SEvan Bacon
438d307f52SEvan Bacon/** Import `metro` from the project. */
448d307f52SEvan Baconexport function importMetroFromProject(projectRoot: string): typeof import('metro') {
458d307f52SEvan Bacon  return importFromProject(projectRoot, 'metro');
468d307f52SEvan Bacon}
47*edeec536SEvan Baconexport function importMetroServerFromProject(projectRoot: string): typeof import('metro').Server {
48*edeec536SEvan Bacon  return importFromProject(projectRoot, 'metro/src/Server');
49*edeec536SEvan Bacon}
5033643b60SEvan Baconexport function importMetroCreateWebsocketServerFromProject(
5133643b60SEvan Bacon  projectRoot: string
5233643b60SEvan Bacon): typeof import('metro/src/lib/createWebsocketServer').createWebsocketServer {
5333643b60SEvan Bacon  return importFromProject(projectRoot, 'metro/src/lib/createWebsocketServer');
5433643b60SEvan Bacon}
5533643b60SEvan Baconexport function importMetroHmrServerFromProject(
5633643b60SEvan Bacon  projectRoot: string
5733643b60SEvan Bacon): typeof import('metro/src/HmrServer').MetroHmrServer {
5833643b60SEvan Bacon  return importFromProject(projectRoot, 'metro/src/HmrServer');
5933643b60SEvan Bacon}
608d307f52SEvan Bacon
61036e9444SEvan Baconexport function importExpoMetroConfig(projectRoot: string) {
62036e9444SEvan Bacon  return importFromProjectOrFallback<typeof import('@expo/metro-config')>(
63036e9444SEvan Bacon    projectRoot,
64036e9444SEvan Bacon    '@expo/metro-config'
65036e9444SEvan Bacon  );
66036e9444SEvan Bacon}
67036e9444SEvan Bacon
68036e9444SEvan Bacon/**
69036e9444SEvan Bacon * Attempt to use the local version of a module or fallback on the CLI version.
70036e9444SEvan Bacon * This should only ever happen when testing Expo CLI in development.
71036e9444SEvan Bacon */
72036e9444SEvan Baconexport function importFromProjectOrFallback<TModule>(
73036e9444SEvan Bacon  projectRoot: string,
74036e9444SEvan Bacon  moduleId: string
75036e9444SEvan Bacon): TModule {
76036e9444SEvan Bacon  const resolvedPath = resolveFrom.silent(projectRoot, moduleId);
77036e9444SEvan Bacon  if (!resolvedPath) {
78036e9444SEvan Bacon    debug(`requiring "${moduleId}" relative to the CLI`);
79036e9444SEvan Bacon    return require(require.resolve(moduleId));
80036e9444SEvan Bacon  }
81036e9444SEvan Bacon  debug(`requiring "${moduleId}" from the project:`, resolvedPath);
82036e9444SEvan Bacon  return require(resolvedPath);
838d307f52SEvan Bacon}
846d6b81f9SEvan Bacon
856d6b81f9SEvan Bacon/** Import `metro-resolver` from the project. */
866d6b81f9SEvan Baconexport function importMetroResolverFromProject(
876d6b81f9SEvan Bacon  projectRoot: string
886d6b81f9SEvan Bacon): typeof import('metro-resolver') {
896d6b81f9SEvan Bacon  return importFromProject(projectRoot, 'metro-resolver');
906d6b81f9SEvan Bacon}
916d6b81f9SEvan Bacon
925234fe38SCedric van Putten/** Import `metro-inspector-proxy` from the project. */
935234fe38SCedric van Puttenexport function importMetroInspectorProxyFromProject(
945234fe38SCedric van Putten  projectRoot: string
955234fe38SCedric van Putten): typeof import('metro-inspector-proxy') {
965234fe38SCedric van Putten  return importFromProject(projectRoot, 'metro-inspector-proxy');
975234fe38SCedric van Putten}
985234fe38SCedric van Putten
995234fe38SCedric van Putten/** Import `metro-inspector-proxy/src/Device` from the project. */
1005234fe38SCedric van Puttenexport function importMetroInspectorDeviceFromProject(
1015234fe38SCedric van Putten  projectRoot: string
1025234fe38SCedric van Putten): typeof import('metro-inspector-proxy/src/Device') {
1035234fe38SCedric van Putten  return importFromProject(projectRoot, 'metro-inspector-proxy/src/Device');
1045234fe38SCedric van Putten}
1055234fe38SCedric van Putten
1066d6b81f9SEvan Bacon/**
1076d6b81f9SEvan Bacon * Import the internal `saveAssets()` function from `react-native` for the purpose
1086d6b81f9SEvan Bacon * of saving production assets as-is instead of converting them to a hash.
1096d6b81f9SEvan Bacon */
1106d6b81f9SEvan Baconexport function importCliSaveAssetsFromProject(
1116d6b81f9SEvan Bacon  projectRoot: string
1126d6b81f9SEvan Bacon): typeof import('@react-native-community/cli-plugin-metro/build/commands/bundle/saveAssets').default {
1136d6b81f9SEvan Bacon  return importFromProject(
1146d6b81f9SEvan Bacon    projectRoot,
1156d6b81f9SEvan Bacon    '@react-native-community/cli-plugin-metro/build/commands/bundle/saveAssets'
1166d6b81f9SEvan Bacon  ).default;
1176d6b81f9SEvan Bacon}
118d42dd5d4SCedric van Putten
119d42dd5d4SCedric van Putten/** Resolve the installed Metro version from project */
120d42dd5d4SCedric van Puttenexport function resolveMetroVersionFromProject(projectRoot: string): string {
121d42dd5d4SCedric van Putten  return importFromProject(projectRoot, 'metro/package.json').version;
122d42dd5d4SCedric van Putten}
123