UNPKG

7.68 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 // Rename gitignore after the fact to prevent npm from renaming it to .npmignore
139 // See: https://github.com/npm/npm/issues/1862
140 try {
141 fs.moveSync(
142 path.join(appPath, 'gitignore'),
143 path.join(appPath, '.gitignore'),
144 []
145 );
146 } catch (err) {
147 // Append if there's already a `.gitignore` file there
148 if (err.code === 'EEXIST') {
149 const data = fs.readFileSync(path.join(appPath, 'gitignore'));
150 fs.appendFileSync(path.join(appPath, '.gitignore'), data);
151 fs.unlinkSync(path.join(appPath, 'gitignore'));
152 } else {
153 throw err;
154 }
155 }
156
157 let command;
158 let args;
159
160 if (useYarn) {
161 command = 'yarnpkg';
162 args = ['add'];
163 } else {
164 command = 'npm';
165 args = ['install', '--save', verbose && '--verbose'].filter(e => e);
166 }
167 args.push('react', 'react-dom');
168
169 // Install additional template dependencies, if present
170 const templateDependenciesPath = path.join(
171 appPath,
172 '.template.dependencies.json'
173 );
174 if (fs.existsSync(templateDependenciesPath)) {
175 const templateDependencies = require(templateDependenciesPath).dependencies;
176 args = args.concat(
177 Object.keys(templateDependencies).map(key => {
178 return `${key}@${templateDependencies[key]}`;
179 })
180 );
181 fs.unlinkSync(templateDependenciesPath);
182 }
183
184 // Install react and react-dom for backward compatibility with old CRA cli
185 // which doesn't install react and react-dom along with react-scripts
186 // or template is presetend (via --internal-testing-template)
187 if (!isReactInstalled(appPackage) || template) {
188 console.log(`Installing react and react-dom using ${command}...`);
189 console.log();
190
191 const proc = spawn.sync(command, args, { stdio: 'inherit' });
192 if (proc.status !== 0) {
193 console.error(`\`${command} ${args.join(' ')}\` failed`);
194 return;
195 }
196 }
197
198 if (useTypeScript) {
199 verifyTypeScriptSetup();
200 }
201
202 if (tryGitInit(appPath)) {
203 console.log();
204 console.log('Initialized a git repository.');
205 }
206
207 // Display the most elegant way to cd.
208 // This needs to handle an undefined originalDirectory for
209 // backward compatibility with old global-cli's.
210 let cdpath;
211 if (originalDirectory && path.join(originalDirectory, appName) === appPath) {
212 cdpath = appName;
213 } else {
214 cdpath = appPath;
215 }
216
217 // Change displayed command to yarn instead of yarnpkg
218 const displayedCommand = useYarn ? 'yarn' : 'npm';
219
220 console.log();
221 console.log(`Success! Created ${appName} at ${appPath}`);
222 console.log('Inside that directory, you can run several commands:');
223 console.log();
224 console.log(chalk.cyan(` ${displayedCommand} start`));
225 console.log(' Starts the development server.');
226 console.log();
227 console.log(
228 chalk.cyan(` ${displayedCommand} ${useYarn ? '' : 'run '}build`)
229 );
230 console.log(' Bundles the app into static files for production.');
231 console.log();
232 console.log(chalk.cyan(` ${displayedCommand} test`));
233 console.log(' Starts the test runner.');
234 console.log();
235 console.log(
236 chalk.cyan(` ${displayedCommand} ${useYarn ? '' : 'run '}eject`)
237 );
238 console.log(
239 ' Removes this tool and copies build dependencies, configuration files'
240 );
241 console.log(
242 ' and scripts into the app directory. If you do this, you can’t go back!'
243 );
244 console.log();
245 console.log('We suggest that you begin by typing:');
246 console.log();
247 console.log(chalk.cyan(' cd'), cdpath);
248 console.log(` ${chalk.cyan(`${displayedCommand} start`)}`);
249 if (readmeExists) {
250 console.log();
251 console.log(
252 chalk.yellow(
253 'You had a `README.md` file, we renamed it to `README.old.md`'
254 )
255 );
256 }
257 console.log();
258 console.log('Happy hacking!');
259};
260
261function isReactInstalled(appPackage) {
262 const dependencies = appPackage.dependencies || {};
263
264 return (
265 typeof dependencies.react !== 'undefined' &&
266 typeof dependencies['react-dom'] !== 'undefined'
267 );
268}