feat: static target DX improvements (#7712)

[release]

Co-authored-by: Sébastien Chopin <seb@nuxtjs.com>
Co-authored-by: pimlie <pimlie@hotmail.com>
This commit is contained in:
pooya parsa 2020-07-16 17:10:54 +02:00 committed by GitHub
parent f81c588bf6
commit c5a4465572
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 364 additions and 219 deletions

View File

@ -19,7 +19,8 @@ module.exports = {
// But its performance overhead is pretty bad (30+%).
// detectOpenHandles: true
setupFilesAfterEnv: ['./test/utils/setup'],
setupFilesAfterEnv: ['./test/utils/setup-env'],
setupFiles: ['./test/utils/setup'],
coverageDirectory: './coverage',

View File

@ -20,10 +20,13 @@
"compression": "^1.7.4",
"connect": "^3.7.0",
"consola": "^2.14.0",
"crc": "^3.8.0",
"destr": "^1.0.0",
"esm": "^3.2.25",
"execa": "^3.4.0",
"exit": "^0.1.2",
"fs-extra": "^8.1.0",
"globby": "^11.0.1",
"hable": "^3.0.0",
"minimist": "^1.2.5",
"opener": "1.5.1",

View File

@ -85,7 +85,7 @@ export default {
const builder = await cmd.getBuilder(nuxt)
await builder.build()
const nextCommand = nuxt.options.target === TARGETS.static ? 'nuxt export' : 'nuxt start'
const nextCommand = nuxt.options.target === TARGETS.static ? 'nuxt generate' : 'nuxt start'
consola.info('Ready to run `' + (nextCommand) + '`')
}
}

View File

@ -1,50 +1,10 @@
import path from 'path'
import consola from 'consola'
import { TARGETS } from '@nuxt/utils'
import { common, locking } from '../options'
import { createLock } from '../utils'
import generate from './generate'
export default {
name: 'export',
description: 'Export a static generated web application',
usage: 'export <dir>',
options: {
...common,
...locking,
'fail-on-error': {
type: 'boolean',
default: false,
description: 'Exit with non-zero status code if there are errors when exporting pages'
}
},
async run (cmd) {
const config = await cmd.getNuxtConfig({
dev: false,
target: TARGETS.static,
_export: true
})
const nuxt = await cmd.getNuxt(config)
if (cmd.argv.lock) {
await cmd.setLock(await createLock({
id: 'export',
dir: nuxt.options.generate.dir,
root: config.rootDir
}))
}
const generator = await cmd.getGenerator(nuxt)
await nuxt.server.listen(0)
const { errors } = await generator.generate({
init: true,
build: false
})
await nuxt.close()
if (cmd.argv['fail-on-error'] && errors.length > 0) {
throw new Error('Error exporting pages, exiting with non-zero code')
}
consola.info('Ready to run `nuxt serve` or deploy `' + path.basename(nuxt.options.generate.dir) + '/` directory')
...generate,
run (...args) {
consola.warn('`nuxt export` has been deprecated! Please use `nuxt generate`.')
return generate.run.call(this, ...args)
}
}

View File

@ -1,6 +1,7 @@
import { TARGETS } from '@nuxt/utils'
import { common, locking } from '../options'
import { normalizeArg, createLock } from '../utils'
import { ensureBuild, generate } from '../utils/generate'
export default {
name: 'generate',
@ -54,23 +55,22 @@ export default {
}
},
async run (cmd) {
const config = await cmd.getNuxtConfig({
dev: false,
_build: cmd.argv.build,
_generate: true
})
if (config.target === TARGETS.static) {
throw new Error("Please use `nuxt export` when using `target: 'static'`")
}
// Forcing static target anyway
config.target = TARGETS.static
const config = await cmd.getNuxtConfig({ dev: false })
// Disable analyze if set by the nuxt config
config.build = config.build || {}
config.build.analyze = false
// Full static
if (config.target === TARGETS.static) {
await ensureBuild(cmd)
await generate(cmd)
return
}
// Forcing static target anyway
config.target = TARGETS.static
// Set flag to keep the prerendering behaviour
config._legacyGenerate = true

View File

@ -1,84 +1,10 @@
import { promises as fs } from 'fs'
import { join, extname, basename } from 'path'
import connect from 'connect'
import serveStatic from 'serve-static'
import compression from 'compression'
import { getNuxtConfig } from '@nuxt/config'
import { TARGETS } from '@nuxt/utils'
import { common, server } from '../options'
import { showBanner } from '../utils/banner'
import * as imports from '../imports'
import consola from 'consola'
import start from './start'
export default {
name: 'serve',
description: 'Serve the exported static application (should be compiled with `nuxt build` and `nuxt export` first)',
usage: 'serve <dir>',
options: {
'config-file': common['config-file'],
version: common.version,
help: common.help,
...server
},
async run (cmd) {
let options = await cmd.getNuxtConfig({ dev: false })
// add default options
options = getNuxtConfig(options)
try {
// overwrites with build config
const buildConfig = require(join(options.buildDir, 'nuxt/config.json'))
options.target = buildConfig.target
} catch (err) {}
if (options.target === TARGETS.server) {
throw new Error('You cannot use `nuxt serve` with ' + TARGETS.server + ' target, please use `nuxt start`')
}
const distStat = await fs.stat(options.generate.dir).catch(err => null) // eslint-disable-line handle-callback-err
if (!distStat || !distStat.isDirectory()) {
throw new Error('Output directory `' + basename(options.generate.dir) + '/` does not exists, please run `nuxt export` before `nuxt serve`.')
}
const app = connect()
app.use(compression({ threshold: 0 }))
app.use(
options.router.base,
serveStatic(options.generate.dir, {
extensions: ['html']
})
)
if (options.generate.fallback) {
const fallbackFile = await fs.readFile(join(options.generate.dir, options.generate.fallback), 'utf-8')
app.use((req, res, next) => {
const ext = extname(req.url) || '.html'
if (ext !== '.html') {
return next()
}
res.writeHeader(200, {
'Content-Type': 'text/html'
})
res.write(fallbackFile)
res.end()
})
}
const { port, host, socket, https } = options.server
const { Listener } = await imports.server()
const listener = new Listener({
port,
host,
socket,
https,
app,
dev: true, // try another port if taken
baseURL: options.router.base
})
await listener.listen()
const { Nuxt } = await imports.core()
showBanner({
constructor: Nuxt,
options,
server: {
listeners: [listener]
}
}, false)
...start,
run (...args) {
consola.warn('`nuxt serve` has been deprecated! Please use `nuxt start`.')
return start.run.call(this, ...args)
}
}

View File

@ -1,6 +1,8 @@
import { TARGETS } from '@nuxt/utils'
import consola from 'consola'
import { common, server } from '../options'
import { showBanner } from '../utils/banner'
import { serve } from '../utils/serve'
export default {
name: 'start',
@ -12,9 +14,12 @@ export default {
},
async run (cmd) {
const config = await cmd.getNuxtConfig({ dev: false, _start: true })
if (config.target === TARGETS.static) {
throw new Error('You cannot use `nuxt start` with ' + TARGETS.static + ' target, please use `nuxt export` and `nuxt serve`')
consola.info('Serving static dist')
return serve(cmd)
}
const nuxt = await cmd.getNuxt(config)
// Listen and show ready banner

View File

@ -0,0 +1,161 @@
import fs from 'fs'
import path, { relative } from 'path'
import crc32 from 'crc/lib/crc32'
import consola from 'consola'
import globby from 'globby'
import destr from 'destr'
import { TARGETS } from '@nuxt/utils'
export async function generate (cmd) {
const nuxt = await getNuxt({ server: true }, cmd)
const generator = await cmd.getGenerator(nuxt)
await nuxt.server.listen(0)
await generator.generate({ build: false })
await nuxt.close()
}
export async function ensureBuild (cmd) {
const nuxt = await getNuxt({ _build: true, server: false }, cmd)
const { options } = nuxt
if (options.generate.cache === false || process.env.NUXT_BUILD) {
const builder = await cmd.getBuilder(nuxt)
await builder.build()
await nuxt.close()
}
// Default build ignore files
const ignore = [
options.buildDir,
options.dir.static,
options.generate.dir,
'node_modules',
'.**/*',
'.*',
'README.md'
]
// Extend ignore
const { generate } = options
if (generate.cache.ignore === 'function') {
generate.cache.ignore = generate.cache.ignore(ignore)
} else if (Array.isArray(generate.cache.ignore)) {
generate.cache.ignore = generate.cache.ignore.concat(ignore)
}
await nuxt.callHook('generate:cache:ignore', generate.cache.ignore)
// Take a snapshot of current project
const snapshotOptions = {
rootDir: nuxt.options.rootDir,
ignore: nuxt.options.generate.cache.ignore,
globbyOptions: nuxt.options.generate.cache.globbyOptions
}
const currentBuildSnapshot = await snapshot(snapshotOptions)
// Current build meta
const currentBuild = {
// @ts-ignore
nuxtVersion: nuxt.constructor.version,
ssr: nuxt.options.ssr,
target: nuxt.options.target,
snapshot: currentBuildSnapshot
}
// Check if build can be skipped
const nuxtBuildFile = path.resolve(nuxt.options.buildDir, 'build.json')
if (fs.existsSync(nuxtBuildFile)) {
const previousBuild = destr(fs.readFileSync(nuxtBuildFile, 'utf-8')) || {}
// Quick diff
const needBuild = false
for (const field of ['nuxtVersion', 'ssr', 'target']) {
if (previousBuild[field] !== currentBuild[field]) {
consola.info(`Doing webpack rebuild because ${field} changed`)
break
}
}
// Full snapshot diff
if (!needBuild) {
const changed = compareSnapshots(previousBuild.snapshot, currentBuild.snapshot)
if (!changed) {
consola.success('Skipping webpack build as no changes detected')
return
} else {
consola.info(`Doing webpack rebuild because ${changed} modified`)
}
}
}
// Webpack build
const builder = await cmd.getBuilder(nuxt)
await builder.build()
// Write build.json
fs.writeFileSync(nuxtBuildFile, JSON.stringify(currentBuild, null, 2), 'utf-8')
await nuxt.close()
}
async function getNuxt (args, cmd) {
const config = await cmd.getNuxtConfig({ dev: false, ...args })
if (config.target === TARGETS.static) {
config._export = true
} else {
config._legacyGenerate = true
}
config.buildDir = (config.static && config.static.cacheDir) || path.resolve(config.rootDir, 'node_modules/.cache/nuxt')
config.build = config.build || {}
config.build.transpile = config.build.transpile || []
config.build.transpile.push(config.buildDir)
const nuxt = await cmd.getNuxt(config)
return nuxt
}
export function compareSnapshots (from, to) {
const allKeys = Array.from(new Set([
...Object.keys(from).sort(),
...Object.keys(to).sort()
]))
for (const key of allKeys) {
if (JSON.stringify(from[key]) !== JSON.stringify(to[key])) {
return key
}
}
return false
}
export async function snapshot ({ globbyOptions, ignore, rootDir }) {
const snapshot = {}
const files = await globby('**/*.*', {
...globbyOptions,
ignore,
cwd: rootDir,
absolute: true
})
await Promise.all(files.map(async (p) => {
const key = relative(rootDir, p)
try {
const fileContent = await fs.readFile(p)
snapshot[key] = {
checksum: await crc32(fileContent).toString(16)
}
} catch (e) {
// TODO: Check for other errors like permission denied
snapshot[key] = {
exists: false
}
}
}))
return snapshot
}

View File

@ -0,0 +1,73 @@
import { promises as fs } from 'fs'
import { join, extname, basename } from 'path'
import connect from 'connect'
import serveStatic from 'serve-static'
import compression from 'compression'
import { getNuxtConfig } from '@nuxt/config'
import { showBanner } from '../utils/banner'
import * as imports from '../imports'
export async function serve (cmd) {
const _config = await cmd.getNuxtConfig({ dev: false })
// add default options
const options = getNuxtConfig(_config)
try {
// overwrites with build config
const buildConfig = require(join(options.buildDir, 'nuxt/config.json'))
options.target = buildConfig.target
} catch (err) { }
const distStat = await fs.stat(options.generate.dir).catch(err => null) // eslint-disable-line handle-callback-err
if (!distStat || !distStat.isDirectory()) {
throw new Error('Output directory `' + basename(options.generate.dir) + '/` does not exists, please use `nuxt generate` before `nuxt start` for static target.')
}
const app = connect()
app.use(compression({ threshold: 0 }))
app.use(
options.router.base,
serveStatic(options.generate.dir, {
extensions: ['html']
})
)
if (options.generate.fallback) {
const fallbackFile = await fs.readFile(join(options.generate.dir, options.generate.fallback), 'utf-8')
app.use((req, res, next) => {
const ext = extname(req.url) || '.html'
if (ext !== '.html') {
return next()
}
res.writeHeader(200, {
'Content-Type': 'text/html'
})
res.write(fallbackFile)
res.end()
})
}
const { port, host, socket, https } = options.server
const { Listener } = await imports.server()
const listener = new Listener({
port,
host,
socket,
https,
app,
dev: true, // try another port if taken
baseURL: options.router.base
})
await listener.listen()
const { Nuxt } = await imports.core()
showBanner({
constructor: Nuxt,
options,
server: {
listeners: [listener]
}
}, false)
}

View File

@ -29,29 +29,29 @@ describe('export', () => {
expect(generator).toHaveBeenCalled()
expect(generator.mock.calls[0][0].init).toBe(true)
expect(generator.mock.calls[0][0].build).toBe(false)
// expect(generator.mock.calls[0][0].build).toBe(false)
})
test('force-exits by default', async () => {
mockGetNuxt({ generate: { dir: 'dist' } })
mockGetGenerator()
const cmd = NuxtCommand.from(exportCommand, ['export', '.'])
const cmd = NuxtCommand.from(exportCommand, ['generate', '.'])
await cmd.run()
expect(utils.forceExit).toHaveBeenCalledTimes(1)
expect(utils.forceExit).toHaveBeenCalledWith('export', 5)
expect(utils.forceExit).toHaveBeenCalledWith('generate', 5)
})
test('can set force exit explicitly', async () => {
mockGetNuxt({ generate: { dir: 'dist' } })
mockGetGenerator()
const cmd = NuxtCommand.from(exportCommand, ['export', '.', '--force-exit'])
const cmd = NuxtCommand.from(exportCommand, ['generate', '.', '--force-exit'])
await cmd.run()
expect(utils.forceExit).toHaveBeenCalledTimes(1)
expect(utils.forceExit).toHaveBeenCalledWith('export', false)
expect(utils.forceExit).toHaveBeenCalledWith('generate', false)
})
test('can disable force exit explicitly', async () => {
@ -97,7 +97,7 @@ describe('export', () => {
mockGetGenerator(() => ({ errors: [{ type: 'dummy' }] }))
const cmd = NuxtCommand.from(exportCommand, ['export', '.', '--fail-on-error'])
await expect(cmd.run()).rejects.toThrow('Error exporting pages, exiting with non-zero code')
await expect(cmd.run()).rejects.toThrow('Error generating pages, exiting with non-zero code')
})
test('do not throw an error when fail-on-error disabled and page errors', async () => {

View File

@ -19,16 +19,10 @@ describe('serve', () => {
expect(typeof serve.run).toBe('function')
})
test('error if starts with server target', () => {
mockGetNuxtConfig({ target: TARGETS.server })
const cmd = NuxtCommand.from(serve)
expect(cmd.run()).rejects.toThrow(new Error('You cannot use `nuxt serve` with server target, please use `nuxt start`'))
})
test('error if dist/ does not exists', () => {
mockGetNuxtConfig({ target: TARGETS.static })
const cmd = NuxtCommand.from(serve)
expect(cmd.run()).rejects.toThrow(new Error('Output directory `dist/` does not exists, please run `nuxt export` before `nuxt serve`.'))
expect(cmd.run()).rejects.toThrow(new Error('Output directory `dist/` does not exists, please use `nuxt generate` before `nuxt start` for static target.'))
})
test('no error if dist/ dir exists', async () => {

View File

@ -1,5 +1,4 @@
import fs from 'fs-extra'
import { TARGETS } from '@nuxt/utils'
import * as utils from '../../src/utils/'
import { consola, mockGetNuxtStart, mockGetNuxtConfig, NuxtCommand } from '../utils'
@ -36,13 +35,6 @@ describe('start', () => {
expect(consola.fatal).not.toHaveBeenCalled()
})
test('error if starts with static target', () => {
mockGetNuxtStart()
mockGetNuxtConfig({ target: TARGETS.static })
const cmd = NuxtCommand.from(start)
expect(cmd.run()).rejects.toThrow(new Error('You cannot use `nuxt start` with static target, please use `nuxt export` and `nuxt serve`'))
})
test('start doesnt force-exit by default', async () => {
mockGetNuxtStart()
mockGetNuxtConfig()

View File

@ -1,4 +1,3 @@
export default () => ({
dir: 'dist',
routes: [],
@ -8,6 +7,12 @@ export default () => ({
subFolders: true,
fallback: '200.html',
crawler: true,
cache: {
ignore: [],
globbyOptions: {
gitignore: true
}
},
staticAssets: {
base: undefined, // Default: "/_nuxt/static:
versionBase: undefined, // Default: "_nuxt/static/{version}""

View File

@ -115,9 +115,9 @@ export function getNuxtConfig (_options) {
options.router.base += '/'
}
// Alias export to generate
// TODO: switch to export by default for nuxt3
// Legacy support for export
if (options.export) {
consola.warn('export option is deprecated and will be removed in a future version! Please switch to generate')
options.generate = defu(options.export, options.generate)
}
exports.export = options.generate

View File

@ -196,6 +196,12 @@ Object {
"server": true,
},
"generate": Object {
"cache": Object {
"globbyOptions": Object {
"gitignore": true,
},
"ignore": Array [],
},
"concurrency": 500,
"crawler": true,
"dir": "/var/nuxt/test/dist",

View File

@ -175,6 +175,12 @@ Object {
"server": true,
},
"generate": Object {
"cache": Object {
"globbyOptions": Object {
"gitignore": true,
},
"ignore": Array [],
},
"concurrency": 500,
"crawler": true,
"dir": "dist",
@ -548,6 +554,12 @@ Object {
"server": true,
},
"generate": Object {
"cache": Object {
"globbyOptions": Object {
"gitignore": true,
},
"ignore": Array [],
},
"concurrency": 500,
"crawler": true,
"dir": "dist",

View File

@ -35,7 +35,17 @@ export default class Nuxt extends Hookable {
to: '_render:context',
message: '`render:routeContext(nuxt)` is deprecated, Please use `vue-renderer:ssr:context(context)`'
},
showReady: 'webpack:done'
showReady: 'webpack:done',
// Introduced in 2.13
'export:done': 'generate:done',
'export:before': 'generate:before',
'export:extendRoutes': 'generate:extendRoutes',
'export:distRemoved': 'generate:distRemoved',
'export:distCopied': 'generate:distCopied',
'export:route': 'generate:route',
'export:routeFailed': 'generate:routeFailed',
'export:page': 'generate:page',
'export:routeCreated': 'generate:routeCreated'
})
// Add Legacy aliases

View File

@ -6,16 +6,16 @@ import defu from 'defu'
import htmlMinifier from 'html-minifier'
import { parse } from 'node-html-parser'
import { isFullStatic, flatRoutes, isString, isUrl, promisifyRoute, waitFor, TARGETS } from '@nuxt/utils'
import { isFullStatic, flatRoutes, isString, isUrl, promisifyRoute, waitFor } from '@nuxt/utils'
export default class Generator {
constructor (nuxt, builder) {
this.nuxt = nuxt
this.options = nuxt.options
this.builder = builder
this.isFullStatic = false
// Set variables
this.isFullStatic = isFullStatic(this.options)
this.staticRoutes = path.resolve(this.options.srcDir, this.options.dir.static)
this.srcBuiltPath = path.resolve(this.options.buildDir, 'dist', 'client')
this.distPath = this.options.generate.dir
@ -23,6 +23,12 @@ export default class Generator {
this.distPath,
isUrl(this.options.build.publicPath) ? '' : this.options.build.publicPath
)
// Payloads for full static
if (this.isFullStatic) {
const { staticAssets } = this.options.generate
this.staticAssetsDir = path.resolve(this.distNuxtPath, staticAssets.dir, staticAssets.version)
this.staticAssetsBase = this.options.generate.staticAssets.versionBase
}
// Shared payload
this._payload = null
@ -35,18 +41,10 @@ export default class Generator {
consola.debug('Initializing generator...')
await this.initiate({ build, init })
// Payloads for full static
if (this.isFullStatic) {
consola.info('Full static mode activated')
const { staticAssets } = this.options.generate
this.staticAssetsDir = path.resolve(this.distNuxtPath, staticAssets.dir, staticAssets.version)
this.staticAssetsBase = this.options.generate.staticAssets.versionBase
}
consola.debug('Preparing routes for generate...')
const routes = await this.initRoutes()
consola.info('Generating pages')
consola.info('Generating pages' + (this.isFullStatic ? ' with full static mode' : ''))
const errors = await this.generateRoutes(routes)
await this.afterGenerate()
@ -72,23 +70,13 @@ export default class Generator {
// Start build process
await this.builder.build()
this.isFullStatic = isFullStatic(this.options)
} else {
const hasBuilt = await fsExtra.exists(path.resolve(this.options.buildDir, 'dist', 'server', 'client.manifest.json'))
if (!hasBuilt) {
const fullStaticArgs = isFullStatic(this.options) ? ' --target static' : ''
throw new Error(
`No build files found in ${this.srcBuiltPath}.\nPlease run \`nuxt build${fullStaticArgs}\` before calling \`nuxt export\``
`No build files found in ${this.srcBuiltPath}.\nPlease run \`nuxt build\``
)
}
const config = this.getBuildConfig()
if (!config || (config.target !== TARGETS.static && !this.options._legacyGenerate)) {
throw new Error(
`In order to use \`nuxt export\`, you need to run \`nuxt build --target static\``
)
}
this.isFullStatic = config.isFullStatic
this.options.render.ssr = config.ssr
}
// Initialize dist directory
@ -366,9 +354,15 @@ export default class Generator {
}
// Call hook to let user update the path & html
const page = { route, path: fileName, html, exclude: false }
const page = {
route,
path: fileName,
html,
exclude: false,
errors: pageErrors
}
page.page = page // Backward compatibility for export:page hook
await this.nuxt.callHook('generate:page', page)
await this.nuxt.callHook('export:page', { page, errors: pageErrors })
if (page.exclude) {
return false

View File

@ -1,3 +1,5 @@
import { GlobbyOptions } from 'globby'
/**
* NuxtOptionsGenerate
* Documentation: https://nuxtjs.org/api/configuration-generate
@ -18,4 +20,8 @@ export interface NuxtOptionsGenerate {
interval?: number
routes?: NuxtOptionsGenerateRoute[] | NuxtOptionsGenerateRoutesFunction | NuxtOptionsGenerateRoutesFunctionWithCallback
subFolders?: boolean
cache?: false | {
ignore?: string[] | function,
globbyOptions?: GlobbyOptions
}
}

View File

@ -5,7 +5,6 @@ export const template = {
dependencies,
dir: path.join(__dirname, '..', 'template'),
files: [
'nuxt/config.json',
'App.js',
'client.js',
'index.js',

View File

@ -1,5 +0,0 @@
<%= JSON.stringify({
isFullStatic: isFullStatic,
ssr: nuxtOptions.render.ssr,
target: nuxtOptions.target
}, null, 2) %>

23
test/utils/setup-env.js Normal file
View File

@ -0,0 +1,23 @@
import consola from 'consola'
import env from 'std-env'
import exit from 'exit'
const isWin = env.windows
describe.win = isWin ? describe : describe.skip
test.win = isWin ? test : test.skip
describe.posix = !isWin ? describe : describe.skip
test.posix = !isWin ? test : test.skip
jest.setTimeout(60000)
consola.mockTypes(() => jest.fn())
function errorTrap (error) {
process.stderr.write('\n' + error.stack + '\n')
exit(1)
}
process.on('unhandledRejection', errorTrap)
process.on('uncaughtException', errorTrap)

View File

@ -1,26 +1,6 @@
import consola from 'consola'
import chalk from 'chalk'
import env from 'std-env'
import exit from 'exit'
const isWin = env.windows
describe.win = isWin ? describe : describe.skip
test.win = isWin ? test : test.skip
describe.posix = !isWin ? describe : describe.skip
test.posix = !isWin ? test : test.skip
process.env.FORCE_COLOR = 0
const chalk = require('chalk')
chalk.level = 0
jest.setTimeout(60000)
consola.mockTypes(() => jest.fn())
function errorTrap (error) {
process.stderr.write('\n' + error.stack + '\n')
exit(1)
}
process.on('unhandledRejection', errorTrap)
process.on('uncaughtException', errorTrap)
chalk.supportsColor = false
process.env.FORCE_COLOR = 0