1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 | 'use strict';
|
10 |
|
11 | const chalk = require('react-dev-utils/chalk');
|
12 | const fs = require('fs');
|
13 | const resolve = require('resolve');
|
14 | const path = require('path');
|
15 | const paths = require('../../config/paths');
|
16 | const os = require('os');
|
17 | const immer = require('react-dev-utils/immer').produce;
|
18 | const globby = require('react-dev-utils/globby').sync;
|
19 |
|
20 | function writeJson(fileName, object) {
|
21 | fs.writeFileSync(
|
22 | fileName,
|
23 | JSON.stringify(object, null, 2).replace(/\n/g, os.EOL) + os.EOL
|
24 | );
|
25 | }
|
26 |
|
27 | function verifyNoTypeScript() {
|
28 | const typescriptFiles = globby(
|
29 | ['**/*.(ts|tsx)', '!**/node_modules', '!**/*.d.ts'],
|
30 | { cwd: paths.appSrc }
|
31 | );
|
32 | if (typescriptFiles.length > 0) {
|
33 | console.warn(
|
34 | chalk.yellow(
|
35 | `We detected TypeScript in your project (${chalk.bold(
|
36 | `src${path.sep}${typescriptFiles[0]}`
|
37 | )}) and created a ${chalk.bold('tsconfig.json')} file for you.`
|
38 | )
|
39 | );
|
40 | console.warn();
|
41 | return false;
|
42 | }
|
43 | return true;
|
44 | }
|
45 |
|
46 | function verifyTypeScriptSetup() {
|
47 | let firstTimeSetup = false;
|
48 |
|
49 | if (!fs.existsSync(paths.appTsConfig)) {
|
50 | if (verifyNoTypeScript()) {
|
51 | return;
|
52 | }
|
53 | writeJson(paths.appTsConfig, {});
|
54 | firstTimeSetup = true;
|
55 | }
|
56 |
|
57 | const isYarn = fs.existsSync(paths.yarnLockFile);
|
58 |
|
59 |
|
60 | let ts;
|
61 | try {
|
62 | ts = require(resolve.sync('typescript', {
|
63 | basedir: paths.appNodeModules,
|
64 | }));
|
65 | } catch (_) {
|
66 | console.error(
|
67 | chalk.bold.red(
|
68 | `It looks like you're trying to use TypeScript but do not have ${chalk.bold(
|
69 | 'typescript'
|
70 | )} installed.`
|
71 | )
|
72 | );
|
73 | console.error(
|
74 | chalk.bold(
|
75 | 'Please install',
|
76 | chalk.cyan.bold('typescript'),
|
77 | 'by running',
|
78 | chalk.cyan.bold(
|
79 | isYarn ? 'yarn add typescript' : 'npm install typescript'
|
80 | ) + '.'
|
81 | )
|
82 | );
|
83 | console.error(
|
84 | chalk.bold(
|
85 | 'If you are not trying to use TypeScript, please remove the ' +
|
86 | chalk.cyan('tsconfig.json') +
|
87 | ' file from your package root (and any TypeScript files).'
|
88 | )
|
89 | );
|
90 | console.error();
|
91 | process.exit(1);
|
92 | }
|
93 |
|
94 | const compilerOptions = {
|
95 |
|
96 |
|
97 |
|
98 | target: {
|
99 | parsedValue: ts.ScriptTarget.ES5,
|
100 | suggested: 'es5',
|
101 | },
|
102 | lib: { suggested: ['dom', 'dom.iterable', 'esnext'] },
|
103 | allowJs: { suggested: true },
|
104 | skipLibCheck: { suggested: true },
|
105 | esModuleInterop: { suggested: true },
|
106 | allowSyntheticDefaultImports: { suggested: true },
|
107 | strict: { suggested: true },
|
108 | forceConsistentCasingInFileNames: { suggested: true },
|
109 |
|
110 |
|
111 |
|
112 |
|
113 |
|
114 | module: {
|
115 | parsedValue: ts.ModuleKind.ESNext,
|
116 | value: 'esnext',
|
117 | reason: 'for import() and import/export',
|
118 | },
|
119 | moduleResolution: {
|
120 | parsedValue: ts.ModuleResolutionKind.NodeJs,
|
121 | value: 'node',
|
122 | reason: 'to match webpack resolution',
|
123 | },
|
124 | resolveJsonModule: { value: true, reason: 'to match webpack loader' },
|
125 | isolatedModules: { value: true, reason: 'implementation limitation' },
|
126 | noEmit: { value: true },
|
127 | jsx: {
|
128 | parsedValue: ts.JsxEmit.React,
|
129 | suggested: 'react',
|
130 | },
|
131 | paths: { value: undefined, reason: 'aliased imports are not supported' },
|
132 | };
|
133 |
|
134 | const formatDiagnosticHost = {
|
135 | getCanonicalFileName: fileName => fileName,
|
136 | getCurrentDirectory: ts.sys.getCurrentDirectory,
|
137 | getNewLine: () => os.EOL,
|
138 | };
|
139 |
|
140 | const messages = [];
|
141 | let appTsConfig;
|
142 | let parsedTsConfig;
|
143 | let parsedCompilerOptions;
|
144 | try {
|
145 | const { config: readTsConfig, error } = ts.readConfigFile(
|
146 | paths.appTsConfig,
|
147 | ts.sys.readFile
|
148 | );
|
149 |
|
150 | if (error) {
|
151 | throw new Error(ts.formatDiagnostic(error, formatDiagnosticHost));
|
152 | }
|
153 |
|
154 | appTsConfig = readTsConfig;
|
155 |
|
156 |
|
157 |
|
158 |
|
159 | let result;
|
160 | parsedTsConfig = immer(readTsConfig, config => {
|
161 | result = ts.parseJsonConfigFileContent(
|
162 | config,
|
163 | ts.sys,
|
164 | path.dirname(paths.appTsConfig)
|
165 | );
|
166 | });
|
167 |
|
168 | if (result.errors && result.errors.length) {
|
169 | throw new Error(
|
170 | ts.formatDiagnostic(result.errors[0], formatDiagnosticHost)
|
171 | );
|
172 | }
|
173 |
|
174 | parsedCompilerOptions = result.options;
|
175 | } catch (e) {
|
176 | if (e && e.name === 'SyntaxError') {
|
177 | console.error(
|
178 | chalk.red.bold(
|
179 | 'Could not parse',
|
180 | chalk.cyan('tsconfig.json') + '.',
|
181 | 'Please make sure it contains syntactically correct JSON.'
|
182 | )
|
183 | );
|
184 | }
|
185 |
|
186 | console.log(e && e.message ? `${e.message}` : '');
|
187 | process.exit(1);
|
188 | }
|
189 |
|
190 | if (appTsConfig.compilerOptions == null) {
|
191 | appTsConfig.compilerOptions = {};
|
192 | firstTimeSetup = true;
|
193 | }
|
194 |
|
195 | for (const option of Object.keys(compilerOptions)) {
|
196 | const { parsedValue, value, suggested, reason } = compilerOptions[option];
|
197 |
|
198 | const valueToCheck = parsedValue === undefined ? value : parsedValue;
|
199 | const coloredOption = chalk.cyan('compilerOptions.' + option);
|
200 |
|
201 | if (suggested != null) {
|
202 | if (parsedCompilerOptions[option] === undefined) {
|
203 | appTsConfig.compilerOptions[option] = suggested;
|
204 | messages.push(
|
205 | `${coloredOption} to be ${chalk.bold(
|
206 | 'suggested'
|
207 | )} value: ${chalk.cyan.bold(suggested)} (this can be changed)`
|
208 | );
|
209 | }
|
210 | } else if (parsedCompilerOptions[option] !== valueToCheck) {
|
211 | appTsConfig.compilerOptions[option] = value;
|
212 | messages.push(
|
213 | `${coloredOption} ${chalk.bold(
|
214 | valueToCheck == null ? 'must not' : 'must'
|
215 | )} be ${valueToCheck == null ? 'set' : chalk.cyan.bold(value)}` +
|
216 | (reason != null ? ` (${reason})` : '')
|
217 | );
|
218 | }
|
219 | }
|
220 |
|
221 |
|
222 | if (parsedTsConfig.include == null) {
|
223 | appTsConfig.include = ['src'];
|
224 | messages.push(
|
225 | `${chalk.cyan('include')} should be ${chalk.cyan.bold('src')}`
|
226 | );
|
227 | }
|
228 |
|
229 | if (messages.length > 0) {
|
230 | if (firstTimeSetup) {
|
231 | console.log(
|
232 | chalk.bold(
|
233 | 'Your',
|
234 | chalk.cyan('tsconfig.json'),
|
235 | 'has been populated with default values.'
|
236 | )
|
237 | );
|
238 | console.log();
|
239 | } else {
|
240 | console.warn(
|
241 | chalk.bold(
|
242 | 'The following changes are being made to your',
|
243 | chalk.cyan('tsconfig.json'),
|
244 | 'file:'
|
245 | )
|
246 | );
|
247 | messages.forEach(message => {
|
248 | console.warn(' - ' + message);
|
249 | });
|
250 | console.warn();
|
251 | }
|
252 | writeJson(paths.appTsConfig, appTsConfig);
|
253 | }
|
254 |
|
255 |
|
256 | if (!fs.existsSync(paths.appTypeDeclarations)) {
|
257 | fs.writeFileSync(
|
258 | paths.appTypeDeclarations,
|
259 | `/// <reference types="react-scripts" />${os.EOL}`
|
260 | );
|
261 | }
|
262 | }
|
263 |
|
264 | module.exports = verifyTypeScriptSetup;
|