UNPKG

8.55 kBJavaScriptView Raw
1// @remove-file-on-eject
2/**
3 * Copyright (c) 2015-present, Facebook, Inc.
4 * All rights reserved.
5 *
6 * This source code is licensed under the BSD-style license found in the
7 * LICENSE file in the root directory of this source tree. An additional grant
8 * of patent rights can be found in the PATENTS file in the same directory.
9 */
10'use strict';
11
12// Makes the script crash on unhandled rejections instead of silently
13// ignoring them. In the future, promise rejections that are not handled will
14// terminate the Node.js process with a non-zero exit code.
15process.on('unhandledRejection', err => {
16 throw err;
17});
18
19const fs = require('fs-extra');
20const path = require('path');
21const execSync = require('child_process').execSync;
22const chalk = require('chalk');
23const paths = require('../config/paths');
24const createJestConfig = require('./utils/createJestConfig');
25const inquirer = require('@0xaio/react-dev-utils/inquirer');
26const spawnSync = require('@0xaio/react-dev-utils/crossSpawn').sync;
27
28const green = chalk.green;
29const cyan = chalk.cyan;
30
31function getGitStatus() {
32 try {
33 let stdout = execSync(`git status --porcelain`, {
34 stdio: ['pipe', 'pipe', 'ignore'],
35 }).toString();
36 return stdout.trim();
37 } catch (e) {
38 return '';
39 }
40}
41
42inquirer
43 .prompt({
44 type: 'confirm',
45 name: 'shouldEject',
46 message: 'Are you sure you want to eject? This action is permanent.',
47 default: false,
48 })
49 .then(answer => {
50 if (!answer.shouldEject) {
51 console.log(cyan('Close one! Eject aborted.'));
52 return;
53 }
54
55 const gitStatus = getGitStatus();
56 if (gitStatus) {
57 console.error(
58 chalk.red(
59 `This git repository has untracked files or uncommitted changes:\n\n` +
60 gitStatus.split('\n').map(line => ' ' + line) +
61 '\n\n' +
62 'Remove untracked files, stash or commit any changes, and try again.'
63 )
64 );
65 process.exit(1);
66 }
67
68 console.log('Ejecting...');
69
70 const ownPath = paths.ownPath;
71 const appPath = paths.appPath;
72
73 function verifyAbsent(file) {
74 if (fs.existsSync(path.join(appPath, file))) {
75 console.error(
76 `\`${file}\` already exists in your app folder. We cannot ` +
77 'continue as you would lose all the changes in that file or directory. ' +
78 'Please move or delete it (maybe make a copy for backup) and run this ' +
79 'command again.'
80 );
81 process.exit(1);
82 }
83 }
84
85 const folders = ['config', 'config/jest', 'scripts'];
86
87 // Make shallow array of files paths
88 const files = folders.reduce((files, folder) => {
89 return files.concat(
90 fs
91 .readdirSync(path.join(ownPath, folder))
92 // set full path
93 .map(file => path.join(ownPath, folder, file))
94 // omit dirs from file list
95 .filter(file => fs.lstatSync(file).isFile())
96 );
97 }, []);
98
99 // Ensure that the app folder is clean and we won't override any files
100 folders.forEach(verifyAbsent);
101 files.forEach(verifyAbsent);
102
103 // Prepare Jest config early in case it throws
104 const jestConfig = createJestConfig(
105 filePath => path.posix.join('<rootDir>', filePath),
106 null,
107 true
108 );
109
110 console.log();
111 console.log(cyan(`Copying files into ${appPath}`));
112
113 folders.forEach(folder => {
114 fs.mkdirSync(path.join(appPath, folder));
115 });
116
117 files.forEach(file => {
118 let content = fs.readFileSync(file, 'utf8');
119
120 // Skip flagged files
121 if (content.match(/\/\/ @remove-file-on-eject/)) {
122 return;
123 }
124 content =
125 content
126 // Remove dead code from .js files on eject
127 .replace(
128 /\/\/ @remove-on-eject-begin([\s\S]*?)\/\/ @remove-on-eject-end/gm,
129 ''
130 )
131 // Remove dead code from .applescript files on eject
132 .replace(
133 /-- @remove-on-eject-begin([\s\S]*?)-- @remove-on-eject-end/gm,
134 ''
135 )
136 .trim() + '\n';
137 console.log(` Adding ${cyan(file.replace(ownPath, ''))} to the project`);
138 fs.writeFileSync(file.replace(ownPath, appPath), content);
139 });
140 console.log();
141
142 const ownPackage = require(path.join(ownPath, 'package.json'));
143 const appPackage = require(path.join(appPath, 'package.json'));
144
145 console.log(cyan('Updating the dependencies'));
146 const ownPackageName = ownPackage.name;
147 if (appPackage.devDependencies) {
148 // We used to put react-scripts in devDependencies
149 if (appPackage.devDependencies[ownPackageName]) {
150 console.log(` Removing ${cyan(ownPackageName)} from devDependencies`);
151 delete appPackage.devDependencies[ownPackageName];
152 }
153 }
154 appPackage.dependencies = appPackage.dependencies || {};
155 if (appPackage.dependencies[ownPackageName]) {
156 console.log(` Removing ${cyan(ownPackageName)} from dependencies`);
157 delete appPackage.dependencies[ownPackageName];
158 }
159 Object.keys(ownPackage.dependencies).forEach(key => {
160 // For some reason optionalDependencies end up in dependencies after install
161 if (ownPackage.optionalDependencies[key]) {
162 return;
163 }
164 console.log(` Adding ${cyan(key)} to dependencies`);
165 appPackage.dependencies[key] = ownPackage.dependencies[key];
166 });
167 // Sort the deps
168 const unsortedDependencies = appPackage.dependencies;
169 appPackage.dependencies = {};
170 Object.keys(unsortedDependencies).sort().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 });