1import { ExpoConfig, getConfig, ProjectConfig } from '@expo/config';
2import assert from 'assert';
3import util from 'util';
4
5import * as Log from '../log';
6import { CommandError } from '../utils/errors';
7import { setNodeEnv } from '../utils/nodeEnv';
8import { profile } from '../utils/profile';
9
10type Options = {
11  type?: string;
12  full?: boolean;
13  json?: boolean;
14};
15
16export function logConfig(config: ExpoConfig | ProjectConfig) {
17  const isObjStr = (str: string): boolean => /^\w+: {/g.test(str);
18  Log.log(
19    util.inspect(config, {
20      colors: true,
21      compact: false,
22      // Sort objects to the end so that smaller values aren't hidden between large objects.
23      sorted(a: string, b: string) {
24        if (isObjStr(a)) return 1;
25        if (isObjStr(b)) return -1;
26        return 0;
27      },
28      showHidden: false,
29      depth: null,
30    })
31  );
32}
33
34export async function configAsync(projectRoot: string, options: Options) {
35  const loggingFunctions = {
36    log: console.log,
37    warn: console.warn,
38    error: console.error,
39  };
40  // Disable logging for this command if the user wants to get JSON output.
41  // This will ensure that only the JSON is printed to stdout.
42  if (options.json) {
43    console.log = function () {};
44    console.warn = function () {};
45    console.error = function () {};
46  }
47  setNodeEnv('development');
48  require('@expo/env').load(projectRoot);
49
50  if (options.type) {
51    assert.match(options.type, /^(public|prebuild|introspect)$/);
52  }
53
54  let config: ProjectConfig;
55
56  if (options.type === 'prebuild') {
57    const { getPrebuildConfigAsync } = await import('@expo/prebuild-config');
58
59    config = await profile(getPrebuildConfigAsync)(projectRoot, {
60      platforms: ['ios', 'android'],
61    });
62  } else if (options.type === 'introspect') {
63    const { getPrebuildConfigAsync } = await import('@expo/prebuild-config');
64    const { compileModsAsync } = await import('@expo/config-plugins/build/plugins/mod-compiler.js');
65
66    config = await profile(getPrebuildConfigAsync)(projectRoot, {
67      platforms: ['ios', 'android'],
68    });
69
70    await compileModsAsync(config.exp, {
71      projectRoot,
72      introspect: true,
73      platforms: ['ios', 'android'],
74      assertMissingModProviders: false,
75    });
76    // @ts-ignore
77    delete config.modRequest;
78    // @ts-ignore
79    delete config.modResults;
80  } else if (options.type === 'public') {
81    config = profile(getConfig)(projectRoot, {
82      skipSDKVersionRequirement: true,
83      isPublicConfig: true,
84    });
85  } else if (options.type) {
86    throw new CommandError(
87      `Invalid option: --type ${options.type}. Valid options are: public, prebuild`
88    );
89  } else {
90    config = profile(getConfig)(projectRoot, {
91      skipSDKVersionRequirement: true,
92    });
93  }
94
95  const configOutput = options.full ? config : config.exp;
96
97  if (!options.json) {
98    Log.log();
99    logConfig(configOutput);
100    Log.log();
101  } else {
102    process.stdout.write(JSON.stringify(configOutput));
103
104    // Re-enable logging functions for testing.
105    console.log = loggingFunctions.log;
106    console.warn = loggingFunctions.warn;
107    console.error = loggingFunctions.error;
108  }
109}
110