eject.js 6.4 KB
Newer Older
Daniel Grant's avatar
Daniel Grant committed
1
// @remove-file-on-eject
eanplatter's avatar
eanplatter committed
2
3
4
5
6
7
8
9
/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */
10
'use strict';
eanplatter's avatar
eanplatter committed
11

12
13
14
15
16
17
18
// Makes the script crash on unhandled rejections instead of silently
// ignoring them. In the future, promise rejections that are not handled will
// terminate the Node.js process with a non-zero exit code.
process.on('unhandledRejection', err => {
  throw err;
});

19
20
21
22
const fs = require('fs-extra');
const path = require('path');
const spawnSync = require('cross-spawn').sync;
const chalk = require('chalk');
23
const inquirer = require('inquirer');
24
25
const paths = require('../config/paths');
const createJestConfig = require('./utils/createJestConfig');
Daniel Grant's avatar
Daniel Grant committed
26

27
28
const green = chalk.green;
const cyan = chalk.cyan;
eanplatter's avatar
eanplatter committed
29

30
31
32
33
34
35
36
37
38
39
inquirer
  .prompt({
    type: 'confirm',
    name: 'shouldEject',
    message: 'Are you sure you want to eject? This action is permanent.',
    default: false,
  })
  .then(answer => {
    if (!answer.shouldEject) {
      console.log(cyan('Close one! Eject aborted.'));
40
41
      process.exit(1);
    }
42

43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
    console.log('Ejecting...');

    const ownPath = paths.ownPath;
    const appPath = paths.appPath;

    function verifyAbsent(file) {
      if (fs.existsSync(path.join(appPath, file))) {
        console.error(
          `\`${file}\` already exists in your app folder. We cannot ` +
            'continue as you would lose all the changes in that file or directory. ' +
            'Please move or delete it (maybe make a copy for backup) and run this ' +
            'command again.'
        );
        process.exit(1);
      }
    }
Daniel Grant's avatar
Daniel Grant committed
59

60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
    const folders = ['config', 'config/jest', 'scripts', 'scripts/utils'];

    // Make shallow array of files paths
    const files = folders.reduce(
      (files, folder) => {
        return files.concat(
          fs
            .readdirSync(path.join(ownPath, folder))
            // set full path
            .map(file => path.join(ownPath, folder, file))
            // omit dirs from file list
            .filter(file => fs.lstatSync(file).isFile())
        );
      },
      []
    );

    // Ensure that the app folder is clean and we won't override any files
    folders.forEach(verifyAbsent);
    files.forEach(verifyAbsent);

    console.log();
    console.log(cyan(`Copying files into ${appPath}`));

    folders.forEach(folder => {
      fs.mkdirSync(path.join(appPath, folder));
    });

    files.forEach(file => {
      let content = fs.readFileSync(file, 'utf8');

      // Skip flagged files
      if (content.match(/\/\/ @remove-file-on-eject/)) {
        return;
      }
      content = content
        // Remove dead code from .js files on eject
        .replace(
          /\/\/ @remove-on-eject-begin([\s\S]*?)\/\/ @remove-on-eject-end/mg,
          ''
        )
        // Remove dead code from .applescript files on eject
        .replace(
          /-- @remove-on-eject-begin([\s\S]*?)-- @remove-on-eject-end/mg,
          ''
        )
        .trim() + '\n';
      console.log(`  Adding ${cyan(file.replace(ownPath, ''))} to the project`);
      fs.writeFileSync(file.replace(ownPath, appPath), content);
    });
    console.log();

    const ownPackage = require(path.join(ownPath, 'package.json'));
    const appPackage = require(path.join(appPath, 'package.json'));

    console.log(cyan('Updating the dependencies'));
    const ownPackageName = ownPackage.name;
    if (appPackage.devDependencies[ownPackageName]) {
      console.log(`  Removing ${cyan(ownPackageName)} from devDependencies`);
      delete appPackage.devDependencies[ownPackageName];
Daniel Grant's avatar
Daniel Grant committed
120
    }
121
122
123
    if (appPackage.dependencies[ownPackageName]) {
      console.log(`  Removing ${cyan(ownPackageName)} from dependencies`);
      delete appPackage.dependencies[ownPackageName];
Dan Abramov's avatar
Dan Abramov committed
124
    }
eanplatter's avatar
eanplatter committed
125

126
127
128
129
130
131
132
133
134
135
136
137
    Object.keys(ownPackage.dependencies).forEach(key => {
      // For some reason optionalDependencies end up in dependencies after install
      if (ownPackage.optionalDependencies[key]) {
        return;
      }
      console.log(`  Adding ${cyan(key)} to devDependencies`);
      appPackage.devDependencies[key] = ownPackage.dependencies[key];
    });
    console.log();
    console.log(cyan('Updating the scripts'));
    delete appPackage.scripts['eject'];
    Object.keys(appPackage.scripts).forEach(key => {
138
      Object.keys(ownPackage.bin).forEach(binKey => {
139
140
141
142
143
144
145
146
        const regex = new RegExp(binKey + ' (\\w+)', 'g');
        appPackage.scripts[key] = appPackage.scripts[key].replace(
          regex,
          'node scripts/$1.js'
        );
        console.log(
          `  Replacing ${cyan(`"${binKey} ${key}"`)} with ${cyan(`"node scripts/${key}.js"`)}`
        );
147
      });
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
    });

    console.log();
    console.log(cyan('Configuring package.json'));
    // Add Jest config
    console.log(`  Adding ${cyan('Jest')} configuration`);
    appPackage.jest = createJestConfig(
      filePath => path.posix.join('<rootDir>', filePath),
      null,
      true
    );

    // Add Babel config
    console.log(`  Adding ${cyan('Babel')} preset`);
    appPackage.babel = {
      presets: ['react-app'],
    };

    // Add ESlint config
    console.log(`  Adding ${cyan('ESLint')} configuration`);
    appPackage.eslintConfig = {
      extends: 'react-app',
    };

    fs.writeFileSync(
      path.join(appPath, 'package.json'),
      JSON.stringify(appPackage, null, 2) + '\n'
    );
    console.log();

    // "Don't destroy what isn't ours"
    if (ownPath.indexOf(appPath) === 0) {
      try {
        // remove react-scripts and react-scripts binaries from app node_modules
        Object.keys(ownPackage.bin).forEach(binKey => {
          fs.removeSync(path.join(appPath, 'node_modules', '.bin', binKey));
        });
        fs.removeSync(ownPath);
      } catch (e) {
        // It's not essential that this succeeds
      }
189
    }
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206

    if (fs.existsSync(paths.yarnLockFile)) {
      console.log(cyan('Running yarn...'));
      spawnSync('yarnpkg', [], { stdio: 'inherit' });
    } else {
      console.log(cyan('Running npm install...'));
      spawnSync('npm', ['install'], { stdio: 'inherit' });
    }
    console.log(green('Ejected successfully!'));
    console.log();

    console.log(
      green('Please consider sharing why you ejected in this survey:')
    );
    console.log(green('  http://goo.gl/forms/Bi6CZjk1EqsdelXk1'));
    console.log();
  });