UNPKG

8.42 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 execSync = require('child_process').execSync;
20const chalk = require('chalk');
21const paths = require('../config/paths');
22const createJestConfig = require('./utils/createJestConfig');
23const inquirer = require('react-dev-utils/inquirer');
24const spawnSync = require('react-dev-utils/crossSpawn').sync;
25
26const green = chalk.green;
27const cyan = chalk.cyan;
28
29function getGitStatus() {
30 try {
31 let stdout = execSync(`git status --porcelain`, {
32 stdio: ['pipe', 'pipe', 'ignore'],
33 }).toString();
34 return stdout.trim();
35 } catch (e) {
36 return '';
37 }
38}
39
40inquirer
41 .prompt({
42 type: 'confirm',
43 name: 'shouldEject',
44 message: 'Are you sure you want to eject? This action is permanent.',
45 default: false,
46 })
47 .then(answer => {
48 if (!answer.shouldEject) {
49 console.log(cyan('Close one! Eject aborted.'));
50 return;
51 }
52
53 const gitStatus = getGitStatus();
54 if (gitStatus) {
55 console.error(
56 chalk.red(
57 `This git repository has untracked files or uncommitted changes:\n\n` +
58 gitStatus.split('\n').map(line => ' ' + line) +
59 '\n\n' +
60 'Remove untracked files, stash or commit any changes, and try again.'
61 )
62 );
63 process.exit(1);
64 }
65
66 console.log('Ejecting...');
67
68 const ownPath = paths.ownPath;
69 const appPath = paths.appPath;
70
71 function verifyAbsent(file) {
72 if (fs.existsSync(path.join(appPath, file))) {
73 console.error(
74 `\`${file}\` already exists in your app folder. We cannot ` +
75 'continue as you would lose all the changes in that file or directory. ' +
76 'Please move or delete it (maybe make a copy for backup) and run this ' +
77 'command again.'
78 );
79 process.exit(1);
80 }
81 }
82
83 const folders = ['config', 'config/jest', 'scripts'];
84
85 // Make shallow array of files paths
86 const files = folders.reduce((files, folder) => {
87 return files.concat(
88 fs
89 .readdirSync(path.join(ownPath, folder))
90 // set full path
91 .map(file => path.join(ownPath, folder, file))
92 // omit dirs from file list
93 .filter(file => fs.lstatSync(file).isFile())
94 );
95 }, []);
96
97 // Ensure that the app folder is clean and we won't override any files
98 folders.forEach(verifyAbsent);
99 files.forEach(verifyAbsent);
100
101 // Prepare Jest config early in case it throws
102 const jestConfig = createJestConfig(
103 filePath => path.posix.join('<rootDir>', filePath),
104 null,
105 true
106 );
107
108 console.log();
109 console.log(cyan(`Copying files into ${appPath}`));
110
111 folders.forEach(folder => {
112 fs.mkdirSync(path.join(appPath, folder));
113 });
114
115 files.forEach(file => {
116 let content = fs.readFileSync(file, 'utf8');
117
118 // Skip flagged files
119 if (content.match(/\/\/ @remove-file-on-eject/)) {
120 return;
121 }
122 content =
123 content
124 // Remove dead code from .js files on eject
125 .replace(
126 /\/\/ @remove-on-eject-begin([\s\S]*?)\/\/ @remove-on-eject-end/gm,
127 ''
128 )
129 // Remove dead code from .applescript files on eject
130 .replace(
131 /-- @remove-on-eject-begin([\s\S]*?)-- @remove-on-eject-end/gm,
132 ''
133 )
134 .trim() + '\n';
135 console.log(` Adding ${cyan(file.replace(ownPath, ''))} to the project`);
136 fs.writeFileSync(file.replace(ownPath, appPath), content);
137 });
138 console.log();
139
140 const ownPackage = require(path.join(ownPath, 'package.json'));
141 const appPackage = require(path.join(appPath, 'package.json'));
142
143 console.log(cyan('Updating the dependencies'));
144 const ownPackageName = ownPackage.name;
145 if (appPackage.devDependencies) {
146 // We used to put react-scripts in devDependencies
147 if (appPackage.devDependencies[ownPackageName]) {
148 console.log(` Removing ${cyan(ownPackageName)} from devDependencies`);
149 delete appPackage.devDependencies[ownPackageName];
150 }
151 }
152 appPackage.dependencies = appPackage.dependencies || {};
153 if (appPackage.dependencies[ownPackageName]) {
154 console.log(` Removing ${cyan(ownPackageName)} from dependencies`);
155 delete appPackage.dependencies[ownPackageName];
156 }
157 Object.keys(ownPackage.dependencies).forEach(key => {
158 // For some reason optionalDependencies end up in dependencies after install
159 if (ownPackage.optionalDependencies[key]) {
160 return;
161 }
162 console.log(` Adding ${cyan(key)} to dependencies`);
163 appPackage.dependencies[key] = ownPackage.dependencies[key];
164 });
165 // Sort the deps
166 const unsortedDependencies = appPackage.dependencies;
167 appPackage.dependencies = {};
168 Object.keys(unsortedDependencies)
169 .sort()
170 .forEach(key => {
171 appPackage.dependencies[key] = unsortedDependencies[key];
172 });
173 console.log();
174
175 console.log(cyan('Updating the scripts'));
176 delete appPackage.scripts['eject'];
177 Object.keys(appPackage.scripts).forEach(key => {
178 Object.keys(ownPackage.bin).forEach(binKey => {
179 const regex = new RegExp(binKey + ' (\\w+)', 'g');
180 if (!regex.test(appPackage.scripts[key])) {
181 return;
182 }
183 appPackage.scripts[key] = appPackage.scripts[key].replace(
184 regex,
185 'node scripts/$1.js'
186 );
187 console.log(
188 ` Replacing ${cyan(`"${binKey} ${key}"`)} with ${cyan(
189 `"node scripts/${key}.js"`
190 )}`
191 );
192 });
193 });
194
195 console.log();
196 console.log(cyan('Configuring package.json'));
197 // Add Jest config
198 console.log(` Adding ${cyan('Jest')} configuration`);
199 appPackage.jest = jestConfig;
200
201 // Add Babel config
202 console.log(` Adding ${cyan('Babel')} preset`);
203 appPackage.babel = {
204 presets: ['react-app'],
205 };
206
207 // Add ESlint config
208 console.log(` Adding ${cyan('ESLint')} configuration`);
209 appPackage.eslintConfig = {
210 extends: 'react-app',
211 };
212
213 fs.writeFileSync(
214 path.join(appPath, 'package.json'),
215 JSON.stringify(appPackage, null, 2) + '\n'
216 );
217 console.log();
218
219 // "Don't destroy what isn't ours"
220 if (ownPath.indexOf(appPath) === 0) {
221 try {
222 // remove react-scripts and react-scripts binaries from app node_modules
223 Object.keys(ownPackage.bin).forEach(binKey => {
224 fs.removeSync(path.join(appPath, 'node_modules', '.bin', binKey));
225 });
226 fs.removeSync(ownPath);
227 } catch (e) {
228 // It's not essential that this succeeds
229 }
230 }
231
232 if (fs.existsSync(paths.yarnLockFile)) {
233 // TODO: this is disabled for three reasons.
234 //
235 // 1. It produces garbage warnings on Windows on some systems:
236 // https://github.com/facebookincubator/create-react-app/issues/2030
237 //
238 // 2. For the above reason, it breaks Windows CI:
239 // https://github.com/facebookincubator/create-react-app/issues/2624
240 //
241 // 3. It is wrong anyway: re-running yarn will respect the lockfile
242 // rather than package.json we just updated. Instead we should have
243 // updated the lockfile. So we might as well not do it while it's broken.
244 // https://github.com/facebookincubator/create-react-app/issues/2627
245 //
246 // console.log(cyan('Running yarn...'));
247 // spawnSync('yarnpkg', [], { stdio: 'inherit' });
248 } else {
249 console.log(cyan('Running npm install...'));
250 spawnSync('npm', ['install', '--loglevel', 'error'], {
251 stdio: 'inherit',
252 });
253 }
254 console.log(green('Ejected successfully!'));
255 console.log();
256
257 console.log(
258 green('Please consider sharing why you ejected in this survey:')
259 );
260 console.log(green(' http://goo.gl/forms/Bi6CZjk1EqsdelXk1'));
261 console.log();
262 });