UNPKG

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