@sabertazimi/react-scripts
Version:
Configuration and scripts for Bod CLI.
413 lines (364 loc) • 11.3 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.
*/
/* eslint-disable no-console */
// 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.
const process = require('node:process')
process.on('unhandledRejection', (err) => {
throw err
})
const path = require('node:path')
const execSync = require('node:child_process').execSync
const os = require('node:os')
const fs = require('fs-extra')
const chalk = require('react-dev-utils/chalk')
const spawn = require('react-dev-utils/crossSpawn')
const { defaultBrowsers } = require('react-dev-utils/browsersHelper')
const verifyTypeScriptSetup = require('./utils/verifyTypeScriptSetup')
function isInGitRepository() {
try {
execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' })
return true
}
catch (e) {
return false
}
}
function isInMercurialRepository() {
try {
execSync('hg --cwd . root', { stdio: 'ignore' })
return true
}
catch (e) {
return false
}
}
function tryGitInit() {
try {
execSync('git --version', { stdio: 'ignore' })
if (isInGitRepository() || isInMercurialRepository())
return false
execSync('git init', { stdio: 'ignore' })
return true
}
catch (e) {
console.warn('Git repo not initialized', e)
return false
}
}
module.exports = function (
appPath,
appName,
verbose,
originalDirectory,
templateName,
) {
const appPackage = require(path.join(appPath, 'package.json'))
const useYarn = fs.existsSync(path.join(appPath, 'yarn.lock'))
if (!templateName) {
console.log('')
console.error(
`A template was not provided. This is likely because you're using an outdated version of ${chalk.cyan(
'create-react-app',
)}.`,
)
console.error(
`Please note that global installs of ${chalk.cyan(
'create-react-app',
)} are no longer supported.`,
)
console.error(
`You can fix this by running ${chalk.cyan(
'npm uninstall -g create-react-app',
)} or ${chalk.cyan(
'yarn global remove create-react-app',
)} before using ${chalk.cyan('create-react-app')} again.`,
)
return
}
const templatePath = path.dirname(
require.resolve(`${templateName}/package.json`, { paths: [appPath] }),
)
const templateJsonPath = path.join(templatePath, 'template.json')
let templateJson = {}
if (fs.existsSync(templateJsonPath))
templateJson = require(templateJsonPath)
const templatePackage = templateJson.package || {}
// This was deprecated in CRA v5.
if (templateJson.dependencies || templateJson.scripts) {
console.log()
console.log(
chalk.red(
'Root-level `dependencies` and `scripts` keys in `template.json` were deprecated for Create React App 5.\n'
+ 'This template needs to be updated to use the new `package` key.',
),
)
console.log('For more information, visit https://cra.link/templates')
}
// Keys to ignore in templatePackage
const templatePackageBlacklist = [
'name',
'version',
'description',
'keywords',
'bugs',
'license',
'author',
'contributors',
'files',
'browser',
'bin',
'man',
'directories',
'repository',
'peerDependencies',
'bundledDependencies',
'optionalDependencies',
'engineStrict',
'os',
'cpu',
'preferGlobal',
'private',
'publishConfig',
]
// Keys from templatePackage that will be merged with appPackage
const templatePackageToMerge = ['dependencies', 'scripts']
// Keys from templatePackage that will be added to appPackage,
// replacing any existing entries.
const templatePackageToReplace = Object.keys(templatePackage).filter((key) => {
return (
!templatePackageBlacklist.includes(key)
&& !templatePackageToMerge.includes(key)
)
})
// Copy over some of the devDependencies
appPackage.dependencies = appPackage.dependencies || {}
// Setup the script rules
const templateScripts = templatePackage.scripts || {}
appPackage.scripts = Object.assign(
{
'build': 'react-scripts build',
'build:profile': 'react-scripts build --profile',
'start': 'react-scripts start',
'start:https': 'HTTPS=true react-scripts start',
'test': 'react-scripts test',
'test:coverage': 'react-scripts test --coverage',
'test:debug': 'react-scripts --inspect-brk test --runInBand --no-cache',
'test:node': 'react-scripts test --env=node',
},
templateScripts,
)
// Update scripts for Yarn users
if (useYarn) {
appPackage.scripts = Object.entries(appPackage.scripts).reduce(
(acc, [key, value]) => ({
...acc,
[key]: value.replace(/(npm run |npm )/, 'yarn '),
}),
{},
)
}
// Setup the stylelint config
appPackage.stylelint = {
extends: ['stylelint-config-bod'],
plugins: ['stylelint-prettier'],
rules: {
'prettier/prettier': true,
},
}
// Setup the prettier config
appPackage.prettier = {
arrowParens: 'avoid',
printWidth: 80,
semi: true,
singleQuote: true,
tabWidth: 2,
trailingComma: 'es5',
}
// Setup the browsers list
appPackage.browserslist = defaultBrowsers
// Add templatePackage keys/values to appPackage, replacing existing entries
templatePackageToReplace.forEach((key) => {
appPackage[key] = templatePackage[key]
})
fs.writeFileSync(
path.join(appPath, 'package.json'),
JSON.stringify(appPackage, null, 2) + os.EOL,
)
const readmeExists = fs.existsSync(path.join(appPath, 'README.md'))
if (readmeExists) {
fs.renameSync(
path.join(appPath, 'README.md'),
path.join(appPath, 'README.old.md'),
)
}
// Copy the files for the user
const templateDir = path.join(templatePath, 'template')
if (fs.existsSync(templateDir)) {
fs.copySync(templateDir, appPath)
}
else {
console.error(
`Could not locate supplied template: ${chalk.green(templateDir)}`,
)
return
}
// modifies README.md commands based on user used package manager.
if (useYarn) {
try {
const readme = fs.readFileSync(path.join(appPath, 'README.md'), 'utf8')
fs.writeFileSync(
path.join(appPath, 'README.md'),
readme.replace(/(npm run |npm )/g, 'yarn '),
'utf8',
)
}
catch (err) {
// Silencing the error. As it fall backs to using default npm commands.
}
}
const gitignoreExists = fs.existsSync(path.join(appPath, '.gitignore'))
if (gitignoreExists) {
// Append if there's already a `.gitignore` file there
const data = fs.readFileSync(path.join(appPath, 'gitignore'))
fs.appendFileSync(path.join(appPath, '.gitignore'), data)
fs.unlinkSync(path.join(appPath, 'gitignore'))
}
else {
// Rename gitignore after the fact to prevent npm from renaming it to .npmignore
// See: https://github.com/npm/npm/issues/1862
fs.moveSync(
path.join(appPath, 'gitignore'),
path.join(appPath, '.gitignore'),
[],
)
}
// Initialize git repo
let initializedGit = false
if (tryGitInit()) {
initializedGit = true
console.log()
console.log('Initialized a git repository.')
}
let command
let remove
let args
if (useYarn) {
command = 'yarnpkg'
remove = 'remove'
args = ['add']
}
else {
command = 'npm'
remove = 'uninstall'
args = [
'install',
'--no-audit', // https://github.com/facebook/create-react-app/issues/11174
'--save',
verbose && '--verbose',
].filter(e => e)
}
// Install additional template dependencies, if present.
const dependenciesToInstall = Object.entries({
...templatePackage.dependencies,
...templatePackage.devDependencies,
})
if (dependenciesToInstall.length) {
args = args.concat(
dependenciesToInstall.map(([dependency, version]) => {
return `${dependency}@${version}`
}),
)
}
// Install react and react-dom for backward compatibility with old CRA cli
// which doesn't install react and react-dom along with react-scripts
if (!isReactInstalled(appPackage))
args = args.concat(['react', 'react-dom'])
// Install template dependencies, and react and react-dom if missing.
if ((!isReactInstalled(appPackage) || templateName) && args.length > 1) {
console.log()
console.log(`Installing template dependencies using ${command}...`)
const proc = spawn.sync(command, args, { stdio: 'inherit' })
if (proc.status !== 0) {
console.error(`\`${command} ${args.join(' ')}\` failed`)
return
}
}
if (args.find(arg => arg.includes('typescript'))) {
console.log()
verifyTypeScriptSetup()
}
// Remove template
console.log(`Removing template package using ${command}...`)
console.log()
const proc = spawn.sync(command, [remove, templateName], {
stdio: 'inherit',
})
if (proc.status !== 0) {
console.error(`\`${command} ${args.join(' ')}\` failed`)
return
}
// Create git commit if git repo was initialized
if (initializedGit) {
console.log()
console.log('Please create your first git commit.')
}
// Display the most elegant way to cd.
// This needs to handle an undefined originalDirectory for
// backward compatibility with old global-cli's.
let cdpath
if (originalDirectory && path.join(originalDirectory, appName) === appPath)
cdpath = appName
else
cdpath = appPath
// Change displayed command to yarn instead of yarnpkg
const displayedCommand = useYarn ? 'yarn' : 'npm'
console.log()
console.log(`Success! Created ${appName} at ${appPath}`)
console.log('Inside that directory, you can run several commands:')
console.log()
console.log(chalk.cyan(` ${displayedCommand} start`))
console.log(' Starts the development server.')
console.log()
console.log(chalk.cyan(` ${displayedCommand} ${useYarn ? '' : 'run '}build`))
console.log(' Bundles the app into static files for production.')
console.log()
console.log(chalk.cyan(` ${displayedCommand} test`))
console.log(' Starts the test runner.')
console.log()
console.log(chalk.cyan(` ${displayedCommand} ${useYarn ? '' : 'run '}eject`))
console.log(
' Removes this tool and copies build dependencies, configuration files',
)
console.log(
' and scripts into the app directory. If you do this, you can’t go back!',
)
console.log()
console.log('We suggest that you begin by typing:')
console.log()
console.log(chalk.cyan(' cd'), cdpath)
console.log(` ${chalk.cyan(`${displayedCommand} start`)}`)
if (readmeExists) {
console.log()
console.log(
chalk.yellow(
'You had a `README.md` file, we renamed it to `README.old.md`',
),
)
}
console.log()
console.log('Happy hacking!')
}
function isReactInstalled(appPackage) {
const dependencies = appPackage.dependencies || {}
return (
typeof dependencies.react !== 'undefined'
&& typeof dependencies['react-dom'] !== 'undefined'
)
}