UNPKG

8.27 kBJavaScriptView Raw
1// @remove-file-on-eject
2/**
3 * Copyright (c) 2015-present, Facebook, Inc.
4 *
5 * This source code is licensed under the MIT license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8'use strict';
9
10// Makes the script crash on unhandled rejections instead of silently
11// ignoring them. In the future, promise rejections that are not handled will
12// terminate the Node.js process with a non-zero exit code.
13process.on('unhandledRejection', err => {
14 throw err;
15});
16
17const fs = require('fs-extra');
18const path = require('path');
19const chalk = require('react-dev-utils/chalk');
20const execSync = require('child_process').execSync;
21const spawn = require('react-dev-utils/crossSpawn');
22const { defaultBrowsers } = require('react-dev-utils/browsersHelper');
23const os = require('os');
24const verifyTypeScriptSetup = require('./utils/verifyTypeScriptSetup');
25
26function isInGitRepository() {
27 try {
28 execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' });
29 return true;
30 } catch (e) {
31 return false;
32 }
33}
34
35function isInMercurialRepository() {
36 try {
37 execSync('hg --cwd . root', { stdio: 'ignore' });
38 return true;
39 } catch (e) {
40 return false;
41 }
42}
43
44function tryGitInit(appPath) {
45 let didInit = false;
46 try {
47 execSync('git --version', { stdio: 'ignore' });
48 if (isInGitRepository() || isInMercurialRepository()) {
49 return false;
50 }
51
52 execSync('git init', { stdio: 'ignore' });
53 didInit = true;
54
55 execSync('git add -A', { stdio: 'ignore' });
56 execSync('git commit -m "Initial commit from Create React App"', {
57 stdio: 'ignore',
58 });
59 return true;
60 } catch (e) {
61 if (didInit) {
62 // If we successfully initialized but couldn't commit,
63 // maybe the commit author config is not set.
64 // In the future, we might supply our own committer
65 // like Ember CLI does, but for now, let's just
66 // remove the Git files to avoid a half-done state.
67 try {
68 // unlinkSync() doesn't work on directories.
69 fs.removeSync(path.join(appPath, '.git'));
70 } catch (removeErr) {
71 // Ignore.
72 }
73 }
74 return false;
75 }
76}
77
78module.exports = function(
79 appPath,
80 appName,
81 verbose,
82 originalDirectory,
83 template
84) {
85 const ownPath = path.dirname(
86 require.resolve(path.join(__dirname, '..', 'package.json'))
87 );
88 const appPackage = require(path.join(appPath, 'package.json'));
89 const useYarn = fs.existsSync(path.join(appPath, 'yarn.lock'));
90
91 // Copy over some of the devDependencies
92 appPackage.dependencies = appPackage.dependencies || {};
93
94 const useTypeScript = appPackage.dependencies['typescript'] != null;
95
96 // Setup the script rules
97 appPackage.scripts = {
98 start: 'react-scripts start',
99 build: 'react-scripts build',
100 test: 'react-scripts test',
101 eject: 'react-scripts eject',
102 };
103
104 // Setup the eslint config
105 appPackage.eslintConfig = {
106 extends: 'react-app',
107 };
108
109 // Setup the browsers list
110 appPackage.browserslist = defaultBrowsers;
111
112 fs.writeFileSync(
113 path.join(appPath, 'package.json'),
114 JSON.stringify(appPackage, null, 2) + os.EOL
115 );
116
117 const readmeExists = fs.existsSync(path.join(appPath, 'README.md'));
118 if (readmeExists) {
119 fs.renameSync(
120 path.join(appPath, 'README.md'),
121 path.join(appPath, 'README.old.md')
122 );
123 }
124
125 // Copy the files for the user
126 const templatePath = template
127 ? path.resolve(originalDirectory, template)
128 : path.join(ownPath, useTypeScript ? 'template-typescript' : 'template');
129 if (fs.existsSync(templatePath)) {
130 fs.copySync(templatePath, appPath);
131 } else {
132 console.error(
133 `Could not locate supplied template: ${chalk.green(templatePath)}`
134 );
135 return;
136 }
137
138 // modifies README.md commands based on user used package manager.
139 if (useYarn) {
140 try {
141 const readme = fs.readFileSync(path.join(appPath, 'README.md'), 'utf8');
142 fs.writeFileSync(
143 path.join(appPath, 'README.md'),
144 readme
145 .replace(/npm start/g, 'yarn start')
146 .replace(/npm test/g, 'yarn test')
147 .replace(/npm run build/g, 'yarn build')
148 .replace(/npm run eject/g, 'yarn eject'),
149 'utf8'
150 );
151 } catch (err) {
152 // Silencing the error. As it fall backs to using default npm commands.
153 }
154 }
155
156 // Rename gitignore after the fact to prevent npm from renaming it to .npmignore
157 // See: https://github.com/npm/npm/issues/1862
158 try {
159 fs.moveSync(
160 path.join(appPath, 'gitignore'),
161 path.join(appPath, '.gitignore'),
162 []
163 );
164 } catch (err) {
165 // Append if there's already a `.gitignore` file there
166 if (err.code === 'EEXIST') {
167 const data = fs.readFileSync(path.join(appPath, 'gitignore'));
168 fs.appendFileSync(path.join(appPath, '.gitignore'), data);
169 fs.unlinkSync(path.join(appPath, 'gitignore'));
170 } else {
171 throw err;
172 }
173 }
174
175 let command;
176 let args;
177
178 if (useYarn) {
179 command = 'yarnpkg';
180 args = ['add'];
181 } else {
182 command = 'npm';
183 args = ['install', '--save', verbose && '--verbose'].filter(e => e);
184 }
185 args.push('react', 'react-dom');
186
187 // Install additional template dependencies, if present
188 const templateDependenciesPath = path.join(
189 appPath,
190 '.template.dependencies.json'
191 );
192 if (fs.existsSync(templateDependenciesPath)) {
193 const templateDependencies = require(templateDependenciesPath).dependencies;
194 args = args.concat(
195 Object.keys(templateDependencies).map(key => {
196 return `${key}@${templateDependencies[key]}`;
197 })
198 );
199 fs.unlinkSync(templateDependenciesPath);
200 }
201
202 // Install react and react-dom for backward compatibility with old CRA cli
203 // which doesn't install react and react-dom along with react-scripts
204 // or template is presetend (via --internal-testing-template)
205 if (!isReactInstalled(appPackage) || template) {
206 console.log(`Installing react and react-dom using ${command}...`);
207 console.log();
208
209 const proc = spawn.sync(command, args, { stdio: 'inherit' });
210 if (proc.status !== 0) {
211 console.error(`\`${command} ${args.join(' ')}\` failed`);
212 return;
213 }
214 }
215
216 if (useTypeScript) {
217 verifyTypeScriptSetup();
218 }
219
220 if (tryGitInit(appPath)) {
221 console.log();
222 console.log('Initialized a git repository.');
223 }
224
225 // Display the most elegant way to cd.
226 // This needs to handle an undefined originalDirectory for
227 // backward compatibility with old global-cli's.
228 let cdpath;
229 if (originalDirectory && path.join(originalDirectory, appName) === appPath) {
230 cdpath = appName;
231 } else {
232 cdpath = appPath;
233 }
234
235 // Change displayed command to yarn instead of yarnpkg
236 const displayedCommand = useYarn ? 'yarn' : 'npm';
237
238 console.log();
239 console.log(`Success! Created ${appName} at ${appPath}`);
240 console.log('Inside that directory, you can run several commands:');
241 console.log();
242 console.log(chalk.cyan(` ${displayedCommand} start`));
243 console.log(' Starts the development server.');
244 console.log();
245 console.log(
246 chalk.cyan(` ${displayedCommand} ${useYarn ? '' : 'run '}build`)
247 );
248 console.log(' Bundles the app into static files for production.');
249 console.log();
250 console.log(chalk.cyan(` ${displayedCommand} test`));
251 console.log(' Starts the test runner.');
252 console.log();
253 console.log(
254 chalk.cyan(` ${displayedCommand} ${useYarn ? '' : 'run '}eject`)
255 );
256 console.log(
257 ' Removes this tool and copies build dependencies, configuration files'
258 );
259 console.log(
260 ' and scripts into the app directory. If you do this, you can’t go back!'
261 );
262 console.log();
263 console.log('We suggest that you begin by typing:');
264 console.log();
265 console.log(chalk.cyan(' cd'), cdpath);
266 console.log(` ${chalk.cyan(`${displayedCommand} start`)}`);
267 if (readmeExists) {
268 console.log();
269 console.log(
270 chalk.yellow(
271 'You had a `README.md` file, we renamed it to `README.old.md`'
272 )
273 );
274 }
275 console.log();
276 console.log('Happy hacking!');
277};
278
279function isReactInstalled(appPackage) {
280 const dependencies = appPackage.dependencies || {};
281
282 return (
283 typeof dependencies.react !== 'undefined' &&
284 typeof dependencies['react-dom'] !== 'undefined'
285 );
286}