1import {
2  AppJSONConfig,
3  ExpoConfig,
4  getConfig,
5  getProjectConfigDescriptionWithPaths,
6  ProjectConfig,
7} from '@expo/config';
8import chalk from 'chalk';
9
10import * as Log from '../../../log';
11import { env } from '../../../utils/env';
12import { PrerequisiteCommandError, ProjectPrerequisite } from '../Prerequisite';
13import { ensureDependenciesAsync } from '../dependencies/ensureDependenciesAsync';
14
15/** Ensure the project has the required web support settings. */
16export class WebSupportProjectPrerequisite extends ProjectPrerequisite {
17  /** Ensure a project that hasn't explicitly disabled web support has all the required packages for running in the browser. */
18  async assertImplementation(): Promise<void> {
19    if (env.EXPO_NO_WEB_SETUP) {
20      Log.warn('Skipping web setup: EXPO_NO_WEB_SETUP is enabled.');
21      return;
22    }
23    Log.debug('Ensuring web support is setup');
24
25    const result = await this._shouldSetupWebSupportAsync();
26
27    // Ensure web packages are installed
28    await this._ensureWebDependenciesInstalledAsync({ exp: result.exp });
29  }
30
31  /** Exposed for testing. */
32  async _shouldSetupWebSupportAsync(): Promise<ProjectConfig> {
33    const config = getConfig(this.projectRoot);
34
35    // Detect if the 'web' string is purposefully missing from the platforms array.
36    if (isWebPlatformExcluded(config.rootConfig)) {
37      // Get exact config description with paths.
38      const configName = getProjectConfigDescriptionWithPaths(this.projectRoot, config);
39      throw new PrerequisiteCommandError(
40        'WEB_SUPPORT',
41        chalk`Skipping web setup: {bold "web"} is not included in the project ${configName} {bold "platforms"} array.`
42      );
43    }
44
45    return config;
46  }
47
48  /** Exposed for testing. */
49  async _ensureWebDependenciesInstalledAsync({ exp }: { exp: ExpoConfig }): Promise<boolean> {
50    try {
51      return await ensureDependenciesAsync(this.projectRoot, {
52        exp,
53        installMessage: `It looks like you're trying to use web support but don't have the required dependencies installed.`,
54        warningMessage: chalk`If you're not using web, please remove the {bold "web"} string from the platforms array in the project Expo config.`,
55        requiredPackages: [
56          // use react-native-web/package.json to skip node module cache issues when the user installs
57          // the package and attempts to resolve the module in the same process.
58          { file: 'react-native-web/package.json', pkg: 'react-native-web' },
59          { file: 'react-dom/package.json', pkg: 'react-dom' },
60          { file: '@expo/webpack-config/package.json', pkg: '@expo/webpack-config' },
61        ],
62      });
63    } catch (error) {
64      // Reset the cached check so we can re-run the check if the user re-runs the command by pressing 'w' in the Terminal UI.
65      this.resetAssertion();
66      throw error;
67    }
68  }
69}
70
71/** Return `true` if the `web` platform is purposefully excluded from the project Expo config. */
72export function isWebPlatformExcluded(rootConfig: AppJSONConfig): boolean {
73  // Detect if the 'web' string is purposefully missing from the platforms array.
74  const isWebExcluded =
75    Array.isArray(rootConfig.expo?.platforms) &&
76    !!rootConfig.expo?.platforms.length &&
77    !rootConfig.expo?.platforms.includes('web');
78  return isWebExcluded;
79}
80