1/**
2 * A helper script to load the Expo config and loaded plugins from a project
3 */
4
5import fs from 'fs/promises';
6import module from 'module';
7import path from 'path';
8import resolveFrom from 'resolve-from';
9
10import { DEFAULT_IGNORE_PATHS } from '../Options';
11import { isIgnoredPath } from '../utils/Path';
12
13async function runAsync(programName: string, args: string[] = []) {
14  if (args.length < 1) {
15    console.log(`Usage: ${programName} <projectRoot> [ignoredFile]`);
16    return;
17  }
18
19  const projectRoot = path.resolve(args[0]);
20  const ignoredFile = args[1] ? path.resolve(args[1]) : null;
21
22  // @ts-expect-error: module internal _cache
23  const loadedModulesBefore = new Set(Object.keys(module._cache));
24
25  const { getConfig } = require(resolveFrom(path.resolve(projectRoot), 'expo/config'));
26  const config = await getConfig(projectRoot, { skipSDKVersionRequirement: true });
27  // @ts-expect-error: module internal _cache
28  const loadedModules = Object.keys(module._cache)
29    .filter((modulePath) => !loadedModulesBefore.has(modulePath))
30    .map((modulePath) => path.relative(projectRoot, modulePath));
31
32  const ignoredPaths = await loadIgnoredPathsAsync(ignoredFile);
33  const filteredLoadedModules = loadedModules.filter(
34    (modulePath) => !isIgnoredPath(modulePath, ignoredPaths)
35  );
36  console.log(JSON.stringify({ config, loadedModules: filteredLoadedModules }));
37}
38
39// If running from the command line
40if (require.main?.filename === __filename) {
41  (async () => {
42    const programIndex = process.argv.findIndex((arg) => arg === __filename);
43    try {
44      await runAsync(process.argv[programIndex], process.argv.slice(programIndex + 1));
45    } catch (e) {
46      console.error('Uncaught Error', e);
47      process.exit(1);
48    }
49  })();
50}
51
52/**
53 * Load the generated ignored paths file from caller and remove the file after loading
54 */
55async function loadIgnoredPathsAsync(ignoredFile: string | null) {
56  if (!ignoredFile) {
57    return DEFAULT_IGNORE_PATHS;
58  }
59
60  const ignorePaths = [];
61  try {
62    const fingerprintIgnore = await fs.readFile(ignoredFile, 'utf8');
63    const fingerprintIgnoreLines = fingerprintIgnore.split('\n');
64    for (const line of fingerprintIgnoreLines) {
65      const trimmedLine = line.trim();
66      if (trimmedLine) {
67        ignorePaths.push(trimmedLine);
68      }
69    }
70  } catch {}
71
72  try {
73    await fs.rm(ignoredFile);
74  } catch {}
75
76  return ignorePaths;
77}
78
79/**
80 * Get the path to the ExpoConfigLoader file.
81 */
82export function getExpoConfigLoaderPath() {
83  return path.join(__dirname, 'ExpoConfigLoader.js');
84}
85