@sabertazimi/react-scripts
Version:
Configuration and scripts for Bod CLI.
340 lines (303 loc) • 9.97 kB
JavaScript
// @remove-file-on-eject
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
// 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
})
const path = require('node:path')
const fs = require('fs-extra')
const prompts = require('prompts')
const execSync = require('node:child_process').execSync
const chalk = require('react-dev-utils/chalk')
const paths = require('../config/paths')
const createJestConfig = require('./utils/createJestConfig')
const spawnSync = require('react-dev-utils/crossSpawn').sync
const os = require('node:os')
const green = chalk.green
const cyan = chalk.cyan
function getGitStatus() {
try {
const stdout = execSync(`git status --porcelain`, {
stdio: ['pipe', 'pipe', 'ignore'],
}).toString()
return stdout.trim()
} catch (e) {
return ''
}
}
function tryGitAdd(appPath) {
try {
spawnSync(
'git',
['add', path.join(appPath, 'config'), path.join(appPath, 'scripts')],
{
stdio: 'inherit',
}
)
return true
} catch (e) {
return false
}
}
console.log(
chalk.cyan.bold(
'NOTE: Create React App 2+ supports TypeScript, Sass, CSS Modules and more without ejecting: ' +
'https://reactjs.org/blog/2018/10/01/create-react-app-v2.html'
)
)
console.log()
prompts({
type: 'confirm',
name: 'shouldEject',
message: 'Are you sure you want to eject? This action is permanent.',
initial: false,
}).then(answer => {
if (!answer.shouldEject) {
console.log(cyan('Close one! Eject aborted.'))
return
}
const gitStatus = getGitStatus()
if (gitStatus) {
console.error(
`${chalk.red(
'This git repository has untracked files or uncommitted changes:'
)}\n\n${gitStatus
.split('\n')
.map(line => line.match(/ .*/g)[0].trim())
.join('\n')}\n\n${chalk.red(
'Remove untracked files, stash or commit any changes, and try again.'
)}`
)
process.exit(1)
}
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)
}
}
const folders = [
'config',
'config/jest',
'scripts',
'config/webpack/persistentCache',
]
// 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)
// Prepare Jest config early in case it throws
const jestConfig = createJestConfig(
filePath => path.posix.join('<rootDir>', filePath),
null,
true
)
console.log()
console.log(cyan(`Copying files into ${appPath}`))
folders.forEach(folder => {
fs.mkdirSync(path.join(appPath, folder), { recursive: true })
})
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/gm,
''
)
// Remove dead code from .applescript files on eject
.replace(
/-- @remove-on-eject-begin([\s\S]*?)-- @remove-on-eject-end/gm,
''
)
.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) {
// We used to put react-scripts in devDependencies
if (appPackage.devDependencies[ownPackageName]) {
console.log(` Removing ${cyan(ownPackageName)} from devDependencies`)
delete appPackage.devDependencies[ownPackageName]
}
}
appPackage.dependencies = appPackage.dependencies || {}
if (appPackage.dependencies[ownPackageName]) {
console.log(` Removing ${cyan(ownPackageName)} from dependencies`)
delete appPackage.dependencies[ownPackageName]
}
Object.keys(ownPackage.dependencies).forEach(key => {
// For some reason optionalDependencies end up in dependencies after install
if (
ownPackage.optionalDependencies &&
ownPackage.optionalDependencies[key]
) {
return
}
console.log(` Adding ${cyan(key)} to dependencies`)
appPackage.dependencies[key] = ownPackage.dependencies[key]
})
// Sort the deps
const unsortedDependencies = appPackage.dependencies
appPackage.dependencies = {}
Object.keys(unsortedDependencies)
.sort()
.forEach(key => {
appPackage.dependencies[key] = unsortedDependencies[key]
})
console.log()
console.log(cyan('Updating the scripts'))
delete appPackage.scripts.eject
Object.keys(appPackage.scripts).forEach(key => {
Object.keys(ownPackage.bin).forEach(binKey => {
const regex = new RegExp(`${binKey} (\\w+)`, 'g')
if (!regex.test(appPackage.scripts[key])) {
return
}
appPackage.scripts[key] = appPackage.scripts[key].replace(
regex,
'node scripts/$1.js'
)
console.log(
` Replacing ${cyan(`"${binKey} ${key}"`)} with ${cyan(
`"node scripts/${key}.js"`
)}`
)
})
})
console.log()
console.log(cyan('Configuring package.json'))
// Add Jest config
console.log(` Adding ${cyan('Jest')} configuration`)
appPackage.jest = jestConfig
// Add Babel config
console.log(` Adding ${cyan('Babel')} preset`)
appPackage.babel = {
presets: ['react-app'],
}
// Add ESlint config
if (!appPackage.eslintConfig) {
console.log(` Adding ${cyan('ESLint')} configuration`)
appPackage.eslintConfig = {
extends: 'bod',
}
}
fs.writeFileSync(
path.join(appPath, 'package.json'),
JSON.stringify(appPackage, null, 2) + os.EOL
)
console.log()
if (fs.existsSync(paths.appTypeDeclarations)) {
try {
// Read app declarations file
let content = fs.readFileSync(paths.appTypeDeclarations, 'utf8')
const ownContent =
fs.readFileSync(paths.ownTypeDeclarations, 'utf8').trim() + os.EOL
// Remove react-scripts reference since they're getting a copy of the types in their project
content =
content
// Remove react-scripts types
.replace(
/^\s*\/\/\/\s*<reference\s+types.+?"react-scripts".*\/>.*(?:\n|$)/gm,
''
)
.trim() + os.EOL
fs.writeFileSync(
paths.appTypeDeclarations,
(ownContent + os.EOL + content).trim() + os.EOL
)
} catch (e) {
// It's not essential that this succeeds, the TypeScript user should
// be able to re-create these types with ease.
}
}
// "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
}
}
if (fs.existsSync(paths.yarnLockFile)) {
const windowsCmdFilePath = path.join(
appPath,
'node_modules',
'.bin',
'react-scripts.cmd'
)
let windowsCmdFileContent
if (process.platform === 'win32') {
// https://github.com/facebook/create-react-app/pull/3806#issuecomment-357781035
// Yarn is diligent about cleaning up after itself, but this causes the react-scripts.cmd file
// to be deleted while it is running. This trips Windows up after the eject completes.
// We'll read the batch file and later "write it back" to match npm behavior.
try {
windowsCmdFileContent = fs.readFileSync(windowsCmdFilePath)
} catch (err) {
// If this fails we're not worse off than if we didn't try to fix it.
}
}
console.log(cyan('Running yarn...'))
spawnSync('yarnpkg', ['--cwd', process.cwd()], { stdio: 'inherit' })
if (windowsCmdFileContent && !fs.existsSync(windowsCmdFilePath)) {
try {
fs.writeFileSync(windowsCmdFilePath, windowsCmdFileContent)
} catch (err) {
// If this fails we're not worse off than if we didn't try to fix it.
}
}
} else {
console.log(cyan('Running npm install...'))
spawnSync('npm', ['install', '--loglevel', 'error'], {
stdio: 'inherit',
})
}
console.log(green('Ejected successfully!'))
console.log()
if (tryGitAdd(appPath)) {
console.log(cyan('Staged ejected files for commit.'))
console.log()
}
console.log(green('Please consider sharing why you ejected in this survey:'))
console.log(green(' http://goo.gl/forms/Bi6CZjk1EqsdelXk1'))
console.log()
})