Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 

311 строки
9.7 KiB

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