UNPKG

7.3 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('react-dev-utils/inquirer');
26const spawnSync = require('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(
89 (files, folder) => {
90 return files.concat(
91 fs
92 .readdirSync(path.join(ownPath, folder))
93 // set full path
94 .map(file => path.join(ownPath, folder, file))
95 // omit dirs from file list
96 .filter(file => fs.lstatSync(file).isFile())
97 );
98 },
99 []
100 );
101
102 // Ensure that the app folder is clean and we won't override any files
103 folders.forEach(verifyAbsent);
104 files.forEach(verifyAbsent);
105
106 // Prepare Jest config early in case it throws
107 const jestConfig = createJestConfig(
108 filePath => path.posix.join('<rootDir>', filePath),
109 null,
110 true
111 );
112
113 console.log();
114 console.log(cyan(`Copying files into ${appPath}`));
115
116 folders.forEach(folder => {
117 fs.mkdirSync(path.join(appPath, folder));
118 });
119
120 files.forEach(file => {
121 let content = fs.readFileSync(file, 'utf8');
122
123 // Skip flagged files
124 if (content.match(/\/\/ @remove-file-on-eject/)) {
125 return;
126 }
127 content = content
128 // Remove dead code from .js files on eject
129 .replace(
130 /\/\/ @remove-on-eject-begin([\s\S]*?)\/\/ @remove-on-eject-end/mg,
131 ''
132 )
133 // Remove dead code from .applescript files on eject
134 .replace(
135 /-- @remove-on-eject-begin([\s\S]*?)-- @remove-on-eject-end/mg,
136 ''
137 )
138 .trim() + '\n';
139 console.log(` Adding ${cyan(file.replace(ownPath, ''))} to the project`);
140 fs.writeFileSync(file.replace(ownPath, appPath), content);
141 });
142 console.log();
143
144 const ownPackage = require(path.join(ownPath, 'package.json'));
145 const appPackage = require(path.join(appPath, 'package.json'));
146
147 console.log(cyan('Updating the dependencies'));
148 const ownPackageName = ownPackage.name;
149 if (appPackage.devDependencies[ownPackageName]) {
150 console.log(` Removing ${cyan(ownPackageName)} from devDependencies`);
151 delete appPackage.devDependencies[ownPackageName];
152 }
153 if (appPackage.dependencies[ownPackageName]) {
154 console.log(` Removing ${cyan(ownPackageName)} from dependencies`);
155 delete appPackage.dependencies[ownPackageName];
156 }
157
158 Object.keys(ownPackage.dependencies).forEach(key => {
159 // For some reason optionalDependencies end up in dependencies after install
160 if (ownPackage.optionalDependencies[key]) {
161 return;
162 }
163 console.log(` Adding ${cyan(key)} to devDependencies`);
164 appPackage.devDependencies[key] = ownPackage.dependencies[key];
165 });
166 console.log();
167 console.log(cyan('Updating the scripts'));
168 delete appPackage.scripts['eject'];
169 Object.keys(appPackage.scripts).forEach(key => {
170 Object.keys(ownPackage.bin).forEach(binKey => {
171 const regex = new RegExp(binKey + ' (\\w+)', 'g');
172 appPackage.scripts[key] = appPackage.scripts[key].replace(
173 regex,
174 'node scripts/$1.js'
175 );
176 console.log(
177 ` Replacing ${cyan(`"${binKey} ${key}"`)} with ${cyan(`"node scripts/${key}.js"`)}`
178 );
179 });
180 });
181
182 console.log();
183 console.log(cyan('Configuring package.json'));
184 // Add Jest config
185 console.log(` Adding ${cyan('Jest')} configuration`);
186 appPackage.jest = jestConfig;
187
188 // Add Babel config
189 console.log(` Adding ${cyan('Babel')} preset`);
190 appPackage.babel = {
191 presets: ['react-app'],
192 };
193
194 // Add ESlint config
195 console.log(` Adding ${cyan('ESLint')} configuration`);
196 appPackage.eslintConfig = {
197 extends: 'react-app',
198 };
199
200 fs.writeFileSync(
201 path.join(appPath, 'package.json'),
202 JSON.stringify(appPackage, null, 2) + '\n'
203 );
204 console.log();
205
206 // "Don't destroy what isn't ours"
207 if (ownPath.indexOf(appPath) === 0) {
208 try {
209 // remove react-scripts and react-scripts binaries from app node_modules
210 Object.keys(ownPackage.bin).forEach(binKey => {
211 fs.removeSync(path.join(appPath, 'node_modules', '.bin', binKey));
212 });
213 fs.removeSync(ownPath);
214 } catch (e) {
215 // It's not essential that this succeeds
216 }
217 }
218
219 if (fs.existsSync(paths.yarnLockFile)) {
220 console.log(cyan('Running yarn...'));
221 spawnSync('yarnpkg', [], { stdio: 'inherit' });
222 } else {
223 console.log(cyan('Running npm install...'));
224 spawnSync('npm', ['install'], { stdio: 'inherit' });
225 }
226 console.log(green('Ejected successfully!'));
227 console.log();
228
229 console.log(
230 green('Please consider sharing why you ejected in this survey:')
231 );
232 console.log(green(' http://goo.gl/forms/Bi6CZjk1EqsdelXk1'));
233 console.log();
234 });