1import JsonFile, { JSONObject } from '@expo/json-file';
2import chalk from 'chalk';
3import path from 'path';
4
5import { Log } from '../../../log';
6
7/**
8 * Force updates a project tsconfig with Expo values.
9 */
10export async function forceUpdateTSConfig(projectRoot: string) {
11  // This runs after the TypeScript prerequisite, so we know the tsconfig.json exists
12  const tsConfigPath = path.join(projectRoot, 'tsconfig.json');
13  const { tsConfig, updates } = getTSConfigUpdates(
14    JsonFile.read(tsConfigPath, {
15      json5: true,
16    })
17  );
18
19  await writeUpdates(tsConfigPath, tsConfig, updates);
20}
21
22export function getTSConfigUpdates(tsConfig: JSONObject) {
23  const updates = new Set<string>();
24
25  if (!tsConfig.include) {
26    tsConfig.include = ['**/*.ts', '**/*.tsx', '.expo/types/**/*.ts', 'expo-env.d.ts'];
27    updates.add('include');
28  } else if (Array.isArray(tsConfig.include)) {
29    if (!tsConfig.include.includes('.expo/types/**/*.ts')) {
30      tsConfig.include = [...tsConfig.include, '.expo/types/**/*.ts'];
31      updates.add('include');
32    }
33
34    if (!tsConfig.include.includes('expo-env.d.ts')) {
35      tsConfig.include = [...tsConfig.include, 'expo-env.d.ts'];
36      updates.add('include');
37    }
38  }
39
40  return { tsConfig, updates };
41}
42
43export async function forceRemovalTSConfig(projectRoot: string) {
44  // This runs after the TypeScript prerequisite, so we know the tsconfig.json exists
45  const tsConfigPath = path.join(projectRoot, 'tsconfig.json');
46  const { tsConfig, updates } = getTSConfigRemoveUpdates(
47    JsonFile.read(tsConfigPath, {
48      json5: true,
49    })
50  );
51
52  await writeUpdates(tsConfigPath, tsConfig, updates);
53}
54
55export function getTSConfigRemoveUpdates(tsConfig: JSONObject) {
56  const updates = new Set<string>();
57
58  if (Array.isArray(tsConfig.include)) {
59    const filtered = (tsConfig.include as string[]).filter(
60      (i) => i !== 'expo-env.d.ts' && i !== '.expo/types/**/*.ts'
61    );
62
63    if (filtered.length !== tsConfig.include.length) {
64      updates.add('include');
65    }
66
67    tsConfig.include = filtered;
68  }
69
70  return { tsConfig, updates };
71}
72
73async function writeUpdates(tsConfigPath: string, tsConfig: JSONObject, updates: Set<string>) {
74  if (updates.size) {
75    await JsonFile.writeAsync(tsConfigPath, tsConfig);
76    for (const update of updates) {
77      Log.log(
78        chalk`{bold TypeScript}: The {cyan tsconfig.json#${update}} property has been updated`
79      );
80    }
81  }
82}
83