UNPKG

9.9 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;
25const os = require('os');
26
27const green = chalk.green;
28const cyan = chalk.cyan;
29
30function getGitStatus() {
31 try {
32 let stdout = execSync(`git status --porcelain`, {
33 stdio: ['pipe', 'pipe', 'ignore'],
34 }).toString();
35 return stdout.trim();
36 } catch (e) {
37 return '';
38 }
39}
40
41inquirer
42 .prompt({
43 type: 'confirm',
44 name: 'shouldEject',
45 message: 'Are you sure you want to eject? This action is permanent.',
46 default: false,
47 })
48 .then(answer => {
49 if (!answer.shouldEject) {
50 console.log(cyan('Close one! Eject aborted.'));
51 return;
52 }
53
54 const gitStatus = getGitStatus();
55 if (gitStatus) {
56 console.error(
57 chalk.red(
58 'This git repository has untracked files or uncommitted changes:'
59 ) +
60 '\n\n' +
61 gitStatus
62 .split('\n')
63 .map(line => line.match(/ .*/g)[0].trim())
64 .join('\n') +
65 '\n\n' +
66 chalk.red(
67 'Remove untracked files, stash or commit any changes, and try again.'
68 )
69 );
70 process.exit(1);
71 }
72
73 console.log('Ejecting...');
74
75 const ownPath = paths.ownPath;
76 const appPath = paths.appPath;
77
78 function verifyAbsent(file) {
79 if (fs.existsSync(path.join(appPath, file))) {
80 console.error(
81 `\`${file}\` already exists in your app folder. We cannot ` +
82 'continue as you would lose all the changes in that file or directory. ' +
83 'Please move or delete it (maybe make a copy for backup) and run this ' +
84 'command again.'
85 );
86 process.exit(1);
87 }
88 }
89
90 const folders = ['config', 'config/jest', 'scripts'];
91
92 // Make shallow array of files paths
93 const files = folders.reduce((files, folder) => {
94 return files.concat(
95 fs
96 .readdirSync(path.join(ownPath, folder))
97 // set full path
98 .map(file => path.join(ownPath, folder, file))
99 // omit dirs from file list
100 .filter(file => fs.lstatSync(file).isFile())
101 );
102 }, []);
103
104 // Ensure that the app folder is clean and we won't override any files
105 folders.forEach(verifyAbsent);
106 files.forEach(verifyAbsent);
107
108 // Prepare Jest config early in case it throws
109 const jestConfig = createJestConfig(
110 filePath => path.posix.join('<rootDir>', filePath),
111 null,
112 true
113 );
114
115 console.log();
116 console.log(cyan(`Copying files into ${appPath}`));
117
118 folders.forEach(folder => {
119 fs.mkdirSync(path.join(appPath, folder));
120 });
121
122 files.forEach(file => {
123 let content = fs.readFileSync(file, 'utf8');
124
125 // Skip flagged files
126 if (content.match(/\/\/ @remove-file-on-eject/)) {
127 return;
128 }
129 content =
130 content
131 // Remove dead code from .js files on eject
132 .replace(
133 /\/\/ @remove-on-eject-begin([\s\S]*?)\/\/ @remove-on-eject-end/gm,
134 ''
135 )
136 // Remove dead code from .applescript files on eject
137 .replace(
138 /-- @remove-on-eject-begin([\s\S]*?)-- @remove-on-eject-end/gm,
139 ''
140 )
141 .trim() + '\n';
142 console.log(` Adding ${cyan(file.replace(ownPath, ''))} to the project`);
143 fs.writeFileSync(file.replace(ownPath, appPath), content);
144 });
145 console.log();
146
147 const ownPackage = require(path.join(ownPath, 'package.json'));
148 const appPackage = require(path.join(appPath, 'package.json'));
149
150 console.log(cyan('Updating the dependencies'));
151 const ownPackageName = ownPackage.name;
152 if (appPackage.devDependencies) {
153 // We used to put react-scripts in devDependencies
154 if (appPackage.devDependencies[ownPackageName]) {
155 console.log(` Removing ${cyan(ownPackageName)} from devDependencies`);
156 delete appPackage.devDependencies[ownPackageName];
157 }
158 }
159 appPackage.dependencies = appPackage.dependencies || {};
160 if (appPackage.dependencies[ownPackageName]) {
161 console.log(` Removing ${cyan(ownPackageName)} from dependencies`);
162 delete appPackage.dependencies[ownPackageName];
163 }
164 Object.keys(ownPackage.dependencies).forEach(key => {
165 // For some reason optionalDependencies end up in dependencies after install
166 if (ownPackage.optionalDependencies[key]) {
167 return;
168 }
169 console.log(` Adding ${cyan(key)} to dependencies`);
170 appPackage.dependencies[key] = ownPackage.dependencies[key];
171 });
172 // Sort the deps
173 const unsortedDependencies = appPackage.dependencies;
174 appPackage.dependencies = {};
175 Object.keys(unsortedDependencies)
176 .sort()
177 .forEach(key => {
178 appPackage.dependencies[key] = unsortedDependencies[key];
179 });
180 console.log();
181
182 console.log(cyan('Updating the scripts'));
183 delete appPackage.scripts['eject'];
184 Object.keys(appPackage.scripts).forEach(key => {
185 Object.keys(ownPackage.bin).forEach(binKey => {
186 const regex = new RegExp(binKey + ' (\\w+)', 'g');
187 if (!regex.test(appPackage.scripts[key])) {
188 return;
189 }
190 appPackage.scripts[key] = appPackage.scripts[key].replace(
191 regex,
192 'node scripts/$1.js'
193 );
194 console.log(
195 ` Replacing ${cyan(`"${binKey} ${key}"`)} with ${cyan(
196 `"node scripts/${key}.js"`
197 )}`
198 );
199 });
200 });
201
202 console.log();
203 console.log(cyan('Configuring package.json'));
204 // Add Jest config
205 console.log(` Adding ${cyan('Jest')} configuration`);
206 appPackage.jest = jestConfig;
207
208 // Add Babel config
209 console.log(` Adding ${cyan('Babel')} preset`);
210 appPackage.babel = {
211 presets: ['react-app'],
212 };
213
214 // Add ESlint config
215 console.log(` Adding ${cyan('ESLint')} configuration`);
216 appPackage.eslintConfig = {
217 extends: 'react-app',
218 };
219
220 fs.writeFileSync(
221 path.join(appPath, 'package.json'),
222 JSON.stringify(appPackage, null, 2) + os.EOL
223 );
224 console.log();
225
226 if (fs.existsSync(paths.appTypeDeclarations)) {
227 try {
228 // Read app declarations file
229 let content = fs.readFileSync(paths.appTypeDeclarations, 'utf8');
230 const ownContent =
231 fs.readFileSync(paths.ownTypeDeclarations, 'utf8').trim() + os.EOL;
232
233 // Remove react-scripts reference since they're getting a copy of the types in their project
234 content =
235 content
236 // Remove react-scripts types
237 .replace(
238 /^\s*\/\/\/\s*<reference\s+types.+?"react-scripts".*\/>.*(?:\n|$)/gm,
239 ''
240 )
241 .trim() + os.EOL;
242
243 fs.writeFileSync(
244 paths.appTypeDeclarations,
245 (ownContent + os.EOL + content).trim() + os.EOL
246 );
247 } catch (e) {
248 // It's not essential that this succeeds, the TypeScript user should
249 // be able to re-create these types with ease.
250 }
251 }
252
253 // "Don't destroy what isn't ours"
254 if (ownPath.indexOf(appPath) === 0) {
255 try {
256 // remove react-scripts and react-scripts binaries from app node_modules
257 Object.keys(ownPackage.bin).forEach(binKey => {
258 fs.removeSync(path.join(appPath, 'node_modules', '.bin', binKey));
259 });
260 fs.removeSync(ownPath);
261 } catch (e) {
262 // It's not essential that this succeeds
263 }
264 }
265
266 if (fs.existsSync(paths.yarnLockFile)) {
267 const windowsCmdFilePath = path.join(
268 appPath,
269 'node_modules',
270 '.bin',
271 'react-scripts.cmd'
272 );
273 let windowsCmdFileContent;
274 if (process.platform === 'win32') {
275 // https://github.com/facebook/create-react-app/pull/3806#issuecomment-357781035
276 // Yarn is diligent about cleaning up after itself, but this causes the react-scripts.cmd file
277 // to be deleted while it is running. This trips Windows up after the eject completes.
278 // We'll read the batch file and later "write it back" to match npm behavior.
279 try {
280 windowsCmdFileContent = fs.readFileSync(windowsCmdFilePath);
281 } catch (err) {
282 // If this fails we're not worse off than if we didn't try to fix it.
283 }
284 }
285
286 console.log(cyan('Running yarn...'));
287 spawnSync('yarnpkg', ['--cwd', process.cwd()], { stdio: 'inherit' });
288
289 if (windowsCmdFileContent && !fs.existsSync(windowsCmdFilePath)) {
290 try {
291 fs.writeFileSync(windowsCmdFilePath, windowsCmdFileContent);
292 } catch (err) {
293 // If this fails we're not worse off than if we didn't try to fix it.
294 }
295 }
296 } else {
297 console.log(cyan('Running npm install...'));
298 spawnSync('npm', ['install', '--loglevel', 'error'], {
299 stdio: 'inherit',
300 });
301 }
302 console.log(green('Ejected successfully!'));
303 console.log();
304
305 console.log(
306 green('Please consider sharing why you ejected in this survey:')
307 );
308 console.log(green(' http://goo.gl/forms/Bi6CZjk1EqsdelXk1'));
309 console.log();
310 });