diff --git a/package.json b/package.json index 695fe050c3..9cda3d1b3a 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/packages/next/build/output/exit.ts b/packages/next/build/output/exit.ts deleted file mode 100644 index 29845a683f..0000000000 --- a/packages/next/build/output/exit.ts +++ /dev/null @@ -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) - } -} diff --git a/packages/next/build/output/log.ts b/packages/next/build/output/log.ts new file mode 100644 index 0000000000..b06b5a3b2e --- /dev/null +++ b/packages/next/build/output/log.ts @@ -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) +} diff --git a/packages/next/build/output/store.ts b/packages/next/build/output/store.ts index f688da62c9..5f8bfa81e5 100644 --- a/packages/next/build/output/store.ts +++ b/packages/next/build/output/store.ts @@ -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({ 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.') }) diff --git a/packages/next/server/next-dev-server.js b/packages/next/server/next-dev-server.js index 71d29c8200..ff8bebcba2 100644 --- a/packages/next/server/next-dev-server.js +++ b/packages/next/server/next-dev-server.js @@ -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) } diff --git a/packages/next/server/on-demand-entry-handler.js b/packages/next/server/on-demand-entry-handler.js index 1210d0cb83..aa7054ef77 100644 --- a/packages/next/server/on-demand-entry-handler.js +++ b/packages/next/server/on-demand-entry-handler.js @@ -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() } } diff --git a/test/integration/cli/test/index.test.js b/test/integration/cli/test/index.test.js index 8c8b8d47b0..309add6526 100644 --- a/test/integration/cli/test/index.test.js +++ b/test/integration/cli/test/index.test.js @@ -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 () => { diff --git a/test/integration/custom-server/test/index.test.js b/test/integration/custom-server/test/index.test.js index 219989b409..fb00de96cd 100644 --- a/test/integration/custom-server/test/index.test.js +++ b/test/integration/custom-server/test/index.test.js @@ -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', () => { diff --git a/test/integration/filesystempublicroutes/test/index.test.js b/test/integration/filesystempublicroutes/test/index.test.js index b238136795..8a5f6dbf98 100644 --- a/test/integration/filesystempublicroutes/test/index.test.js +++ b/test/integration/filesystempublicroutes/test/index.test.js @@ -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', () => { diff --git a/test/lib/next-test-utils.js b/test/lib/next-test-utils.js index 1d9b3beb5e..b264712f28 100644 --- a/test/lib/next-test-utils.js +++ b/test/lib/next-test-utils.js @@ -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)