Do not clear the console (#6758)
* Do not clear the console Its rude to clear the console, you may be sharing output with other processes even in tty mode. * Remove unused dependency * Dedupe and cleanup dev output without clearing * use logError * Remove exit handler * Add next helper * Add log helpers * Switch store to log helpers and a shallow object compare * Update other files to use new logging utility * request => build * Update ready on messages * Use case insensitive matching
This commit is contained in:
parent
1c305ab36e
commit
52dd42a6bb
10 changed files with 84 additions and 83 deletions
|
@ -22,7 +22,8 @@
|
|||
"prepublish": "lerna run prepublish",
|
||||
"publish-canary": "lerna version prerelease --preid canary --force-publish && release --pre",
|
||||
"publish-stable": "lerna version --force-publish",
|
||||
"lint-staged": "lint-staged"
|
||||
"lint-staged": "lint-staged",
|
||||
"next": "./packages/next/dist/bin/next"
|
||||
},
|
||||
"pre-commit": "lint-staged",
|
||||
"lint-staged": {
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
export function onExit(fn: Function) {
|
||||
function exit(signal: string = '') {
|
||||
try {
|
||||
if (listeners.length) {
|
||||
fn()
|
||||
}
|
||||
} finally {
|
||||
while (listeners.length) {
|
||||
const { event, handler } = listeners.shift()!
|
||||
process.removeListener(event, handler)
|
||||
}
|
||||
|
||||
if (signal) {
|
||||
process.kill(process.pid, signal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const listeners = [
|
||||
{ event: 'SIGINT', handler: () => exit('SIGINT') },
|
||||
{ event: 'SIGHUP', handler: () => exit('SIGHUP') },
|
||||
{ event: 'SIGQUIT', handler: () => exit('SIGQUIT') },
|
||||
{ event: 'SIGTERM', handler: () => exit('SIGTERM') },
|
||||
|
||||
{
|
||||
event: 'uncaughtException',
|
||||
handler: (error: Error) => {
|
||||
console.error('Got uncaughtException:', error)
|
||||
exit()
|
||||
process.exit(1)
|
||||
},
|
||||
},
|
||||
{ event: 'exit', handler: () => exit() },
|
||||
]
|
||||
|
||||
for (const { event, handler } of listeners) {
|
||||
process.on(event as any, handler)
|
||||
}
|
||||
}
|
34
packages/next/build/output/log.ts
Normal file
34
packages/next/build/output/log.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
import chalk from 'chalk'
|
||||
|
||||
const prefixes = {
|
||||
wait: chalk`[ {cyan wait} ] `,
|
||||
error: chalk`[ {red error} ]`,
|
||||
warn: chalk`[ {yellow warn} ] `,
|
||||
ready: chalk`[ {green ready} ]`,
|
||||
info: chalk`[ {cyan {dim info}} ] `,
|
||||
event: chalk`[ {magenta event} ]`,
|
||||
}
|
||||
|
||||
export function wait(...message: string[]) {
|
||||
console.log(prefixes.wait, ...message)
|
||||
}
|
||||
|
||||
export function error(...message: string[]) {
|
||||
console.log(prefixes.error, ...message)
|
||||
}
|
||||
|
||||
export function warn(...message: string[]) {
|
||||
console.log(prefixes.warn, ...message)
|
||||
}
|
||||
|
||||
export function ready(...message: string[]) {
|
||||
console.log(prefixes.ready, ...message)
|
||||
}
|
||||
|
||||
export function info(...message: string[]) {
|
||||
console.log(prefixes.info, ...message)
|
||||
}
|
||||
|
||||
export function event(...message: string[]) {
|
||||
console.log(prefixes.event, ...message)
|
||||
}
|
|
@ -1,9 +1,8 @@
|
|||
import chalk from 'chalk'
|
||||
import createStore from 'next/dist/compiled/unistore'
|
||||
import readline from 'readline'
|
||||
import { onExit } from './exit'
|
||||
import stripAnsi from 'strip-ansi'
|
||||
|
||||
import * as Log from './log'
|
||||
|
||||
export type OutputState =
|
||||
| { bootstrap: true; appUrl: string | null }
|
||||
| ({ bootstrap: false; appUrl: string | null } & (
|
||||
|
@ -16,62 +15,66 @@ export type OutputState =
|
|||
|
||||
export const store = createStore<OutputState>({ appUrl: null, bootstrap: true })
|
||||
|
||||
process.stdout.write('\n'.repeat(process.stdout.rows || 1))
|
||||
process.stdout.write('\u001b[?25l')
|
||||
onExit(() => {
|
||||
process.stdout.write('\u001b[?25h')
|
||||
})
|
||||
let lastStore: OutputState = {} as any
|
||||
function hasStoreChanged(nextStore: OutputState) {
|
||||
if (
|
||||
[...new Set([...Object.keys(lastStore), ...Object.keys(nextStore)])].every(
|
||||
key => Object.is((lastStore as any)[key], (nextStore as any)[key])
|
||||
)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
lastStore = nextStore
|
||||
return true
|
||||
}
|
||||
|
||||
store.subscribe(state => {
|
||||
if (process.stdout.isTTY) {
|
||||
readline.cursorTo(process.stdout, 0, 0)
|
||||
readline.clearScreenDown(process.stdout)
|
||||
if (!hasStoreChanged(state)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (state.bootstrap) {
|
||||
console.log(chalk.cyan('Starting the development server ...'))
|
||||
if (state.appUrl) {
|
||||
console.log()
|
||||
console.log(` > Waiting on ${state.appUrl!}`)
|
||||
}
|
||||
Log.wait('starting the development server ...')
|
||||
Log.info(`waiting on ${state.appUrl!} ...`)
|
||||
return
|
||||
}
|
||||
|
||||
if (state.loading) {
|
||||
console.log('Compiling ...')
|
||||
Log.wait('compiling ...')
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if (state.errors) {
|
||||
console.log(chalk.red('Failed to compile.'))
|
||||
console.log()
|
||||
Log.error(state.errors[0])
|
||||
|
||||
const cleanError = stripAnsi(state.errors[0])
|
||||
if (cleanError.indexOf('SyntaxError') > -1) {
|
||||
const matches = cleanError.match(/\[.*\]=/)
|
||||
if (matches) {
|
||||
for (const match of matches) {
|
||||
const prop = (match.split(']').shift() || '').substr(1)
|
||||
console.log(`AMP bind syntax [${prop}]='' is not supported in JSX, use 'data-amp-bind-${prop}' instead. https://err.sh/zeit/next.js/amp-bind-jsx-alt`)
|
||||
console.log(
|
||||
`AMP bind syntax [${prop}]='' is not supported in JSX, use 'data-amp-bind-${prop}' instead. https://err.sh/zeit/next.js/amp-bind-jsx-alt`
|
||||
)
|
||||
}
|
||||
console.log()
|
||||
return
|
||||
}
|
||||
}
|
||||
console.log(state.errors[0])
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (state.warnings) {
|
||||
console.log(chalk.yellow('Compiled with warnings.'))
|
||||
console.log()
|
||||
console.log(state.warnings.join('\n\n'))
|
||||
Log.warn(state.warnings.join('\n\n'))
|
||||
Log.info(`ready on ${state.appUrl!}`)
|
||||
return
|
||||
}
|
||||
|
||||
console.log(chalk.green('Compiled successfully!'))
|
||||
if (state.appUrl) {
|
||||
console.log()
|
||||
console.log(` > Ready on ${state.appUrl!}`)
|
||||
Log.ready('compiled successfully')
|
||||
if (state.appUrl) {
|
||||
Log.info(`ready on ${state.appUrl!}`)
|
||||
}
|
||||
}
|
||||
console.log()
|
||||
console.log('Note that pages will be compiled when you first load them.')
|
||||
})
|
||||
|
|
|
@ -6,6 +6,7 @@ import { PHASE_DEVELOPMENT_SERVER } from 'next-server/constants'
|
|||
import ErrorDebug from './error-debug'
|
||||
import AmpHtmlValidator from 'amphtml-validator'
|
||||
import { ampValidation } from '../build/output/index'
|
||||
import * as Log from '../build/output/log'
|
||||
|
||||
const React = require('react')
|
||||
|
||||
|
@ -176,7 +177,7 @@ export default class DevServer extends Server {
|
|||
const out = await super.renderErrorToHTML(err, req, res, pathname, query)
|
||||
return out
|
||||
} catch (err2) {
|
||||
if (!this.quiet) console.error(err2)
|
||||
if (!this.quiet) Log.error(err2)
|
||||
res.statusCode = 500
|
||||
return super.renderErrorToHTML(err2, req, res, pathname, query)
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import { ROUTE_NAME_REGEX, IS_BUNDLED_PAGE_REGEX } from 'next-server/constants'
|
|||
import { stringify } from 'querystring'
|
||||
import { findPageFile } from './lib/find-page-file'
|
||||
import { isWriteable } from '../build/is-writeable'
|
||||
import * as Log from '../build/output/log'
|
||||
|
||||
const ADDED = Symbol('added')
|
||||
const BUILDING = Symbol('building')
|
||||
|
@ -51,6 +52,7 @@ export default function onDemandEntryHandler (devMiddleware, multiCompiler, {
|
|||
let reloading = false
|
||||
let stopped = false
|
||||
let reloadCallbacks = new EventEmitter()
|
||||
let lastEntry = null
|
||||
|
||||
for (const compiler of compilers) {
|
||||
compiler.hooks.make.tapPromise('NextJsOnDemandEntries', (compilation) => {
|
||||
|
@ -60,7 +62,7 @@ export default function onDemandEntryHandler (devMiddleware, multiCompiler, {
|
|||
const { name, absolutePagePath } = entries[page]
|
||||
const pageExists = await isWriteable(absolutePagePath)
|
||||
if (!pageExists) {
|
||||
console.warn('Page was removed', page)
|
||||
Log.event('page was removed', page)
|
||||
delete entries[page]
|
||||
return
|
||||
}
|
||||
|
@ -164,11 +166,10 @@ export default function onDemandEntryHandler (devMiddleware, multiCompiler, {
|
|||
const entryInfo = entries[page]
|
||||
let toSend
|
||||
|
||||
// If there's no entry.
|
||||
// Then it seems like an weird issue.
|
||||
// If there's no entry, it may have been invalidated and needs to be re-built.
|
||||
if (!entryInfo) {
|
||||
const message = `Client pings, but there's no entry for page: ${page}`
|
||||
console.error(message)
|
||||
if (page !== lastEntry) Log.event(`client pings, but there's no entry for page: ${page}`)
|
||||
lastEntry = page
|
||||
return { invalid: true }
|
||||
}
|
||||
|
||||
|
@ -257,7 +258,7 @@ export default function onDemandEntryHandler (devMiddleware, multiCompiler, {
|
|||
}
|
||||
}
|
||||
|
||||
console.log(`> Building page: ${normalizedPage}`)
|
||||
Log.event(`build page: ${normalizedPage}`)
|
||||
|
||||
entries[normalizedPage] = { name, absolutePagePath, status: ADDED }
|
||||
doneCallbacks.once(normalizedPage, handleCallback)
|
||||
|
@ -364,7 +365,7 @@ function disposeInactiveEntries (devMiddleware, entries, lastAccessPages, maxIna
|
|||
disposingPages.forEach((page) => {
|
||||
delete entries[page]
|
||||
})
|
||||
console.log(`> Disposing inactive page(s): ${disposingPages.join(', ')}`)
|
||||
Log.event(`disposing inactive page(s): ${disposingPages.join(', ')}`)
|
||||
devMiddleware.invalidate()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ describe('CLI Usage', () => {
|
|||
test('custom directory', async () => {
|
||||
const port = await findPort()
|
||||
const output = await runNextCommandDev([dir, '--port', port], true)
|
||||
expect(output).toMatch(/Ready on/)
|
||||
expect(output).toMatch(/ready on/i)
|
||||
})
|
||||
|
||||
test('--port', async () => {
|
||||
|
|
|
@ -32,7 +32,7 @@ const startServer = async (optEnv = {}) => {
|
|||
optEnv
|
||||
)
|
||||
|
||||
server = await initNextServerScript(scriptPath, /Ready on/, env, /ReferenceError: options is not defined/)
|
||||
server = await initNextServerScript(scriptPath, /ready on/i, env, /ReferenceError: options is not defined/)
|
||||
}
|
||||
|
||||
describe('Custom Server', () => {
|
||||
|
|
|
@ -26,7 +26,7 @@ const startServer = async (optEnv = {}) => {
|
|||
optEnv
|
||||
)
|
||||
|
||||
server = await initNextServerScript(scriptPath, /Ready on/, env)
|
||||
server = await initNextServerScript(scriptPath, /ready on/i, env)
|
||||
}
|
||||
|
||||
describe('FileSystemPublicRoutes', () => {
|
||||
|
|
|
@ -123,7 +123,7 @@ export function runNextCommandDev (argv, stdOut) {
|
|||
|
||||
function handleStdout (data) {
|
||||
const message = data.toString()
|
||||
if (/> Ready on/.test(message)) {
|
||||
if (/ready on/i.test(message)) {
|
||||
resolve(stdOut ? message : instance)
|
||||
}
|
||||
process.stdout.write(message)
|
||||
|
|
Loading…
Reference in a new issue