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.Preserve,
|
129 | value: 'preserve',
|
130 | reason: 'JSX is compiled by Babel',
|
131 | },
|
132 | paths: { value: undefined, reason: 'aliased imports are not supported' },
|
133 | };
|
134 |
|
135 | const formatDiagnosticHost = {
|
136 | getCanonicalFileName: fileName => fileName,
|
137 | getCurrentDirectory: ts.sys.getCurrentDirectory,
|
138 | getNewLine: () => os.EOL,
|
139 | };
|
140 |
|
141 | const messages = [];
|
142 | let appTsConfig;
|
143 | let parsedTsConfig;
|
144 | let parsedCompilerOptions;
|
145 | try {
|
146 | const { config: readTsConfig, error } = ts.readConfigFile(
|
147 | paths.appTsConfig,
|
148 | ts.sys.readFile
|
149 | );
|
150 |
|
151 | if (error) {
|
152 | throw new Error(ts.formatDiagnostic(error, formatDiagnosticHost));
|
153 | }
|
154 |
|
155 | appTsConfig = readTsConfig;
|
156 |
|
157 |
|
158 |
|
159 |
|
160 | let result;
|
161 | parsedTsConfig = immer(readTsConfig, config => {
|
162 | result = ts.parseJsonConfigFileContent(
|
163 | config,
|
164 | ts.sys,
|
165 | path.dirname(paths.appTsConfig)
|
166 | );
|
167 | });
|
168 |
|
169 | if (result.errors && result.errors.length) {
|
170 | throw new Error(
|
171 | ts.formatDiagnostic(result.errors[0], formatDiagnosticHost)
|
172 | );
|
173 | }
|
174 |
|
175 | parsedCompilerOptions = result.options;
|
176 | } catch (e) {
|
177 | if (e && e.name === 'SyntaxError') {
|
178 | console.error(
|
179 | chalk.red.bold(
|
180 | 'Could not parse',
|
181 | chalk.cyan('tsconfig.json') + '.',
|
182 | 'Please make sure it contains syntactically correct JSON.'
|
183 | )
|
184 | );
|
185 | }
|
186 |
|
187 | console.log(e && e.message ? `${e.message}` : '');
|
188 | process.exit(1);
|
189 | }
|
190 |
|
191 | if (appTsConfig.compilerOptions == null) {
|
192 | appTsConfig.compilerOptions = {};
|
193 | firstTimeSetup = true;
|
194 | }
|
195 |
|
196 | for (const option of Object.keys(compilerOptions)) {
|
197 | const { parsedValue, value, suggested, reason } = compilerOptions[option];
|
198 |
|
199 | const valueToCheck = parsedValue === undefined ? value : parsedValue;
|
200 | const coloredOption = chalk.cyan('compilerOptions.' + option);
|
201 |
|
202 | if (suggested != null) {
|
203 | if (parsedCompilerOptions[option] === undefined) {
|
204 | appTsConfig.compilerOptions[option] = suggested;
|
205 | messages.push(
|
206 | `${coloredOption} to be ${chalk.bold(
|
207 | 'suggested'
|
208 | )} value: ${chalk.cyan.bold(suggested)} (this can be changed)`
|
209 | );
|
210 | }
|
211 | } else if (parsedCompilerOptions[option] !== valueToCheck) {
|
212 | appTsConfig.compilerOptions[option] = value;
|
213 | messages.push(
|
214 | `${coloredOption} ${chalk.bold(
|
215 | valueToCheck == null ? 'must not' : 'must'
|
216 | )} be ${valueToCheck == null ? 'set' : chalk.cyan.bold(value)}` +
|
217 | (reason != null ? ` (${reason})` : '')
|
218 | );
|
219 | }
|
220 | }
|
221 |
|
222 |
|
223 | if (parsedTsConfig.include == null) {
|
224 | appTsConfig.include = ['src'];
|
225 | messages.push(
|
226 | `${chalk.cyan('include')} should be ${chalk.cyan.bold('src')}`
|
227 | );
|
228 | }
|
229 |
|
230 | if (messages.length > 0) {
|
231 | if (firstTimeSetup) {
|
232 | console.log(
|
233 | chalk.bold(
|
234 | 'Your',
|
235 | chalk.cyan('tsconfig.json'),
|
236 | 'has been populated with default values.'
|
237 | )
|
238 | );
|
239 | console.log();
|
240 | } else {
|
241 | console.warn(
|
242 | chalk.bold(
|
243 | 'The following changes are being made to your',
|
244 | chalk.cyan('tsconfig.json'),
|
245 | 'file:'
|
246 | )
|
247 | );
|
248 | messages.forEach(message => {
|
249 | console.warn(' - ' + message);
|
250 | });
|
251 | console.warn();
|
252 | }
|
253 | writeJson(paths.appTsConfig, appTsConfig);
|
254 | }
|
255 |
|
256 |
|
257 | if (!fs.existsSync(paths.appTypeDeclarations)) {
|
258 | fs.writeFileSync(
|
259 | paths.appTypeDeclarations,
|
260 | `/// <reference types="react-scripts" />${os.EOL}`
|
261 | );
|
262 | }
|
263 | }
|
264 |
|
265 | module.exports = verifyTypeScriptSetup;
|