2020-11-06 03:33:14 +01:00
|
|
|
import chalk from 'chalk'
|
2020-05-15 19:30:01 +02:00
|
|
|
import stripAnsi from 'next/dist/compiled/strip-ansi'
|
2019-03-19 00:21:18 +01:00
|
|
|
import textTable from 'next/dist/compiled/text-table'
|
2019-02-27 15:12:40 +01:00
|
|
|
import createStore from 'next/dist/compiled/unistore'
|
2019-06-05 20:15:42 +02:00
|
|
|
import formatWebpackMessages from '../../client/dev/error-overlay/format-webpack-messages'
|
2019-03-19 00:21:18 +01:00
|
|
|
import { OutputState, store as consoleStore } from './store'
|
2019-02-16 17:09:49 +01:00
|
|
|
|
2021-01-25 16:13:12 +01:00
|
|
|
export function startedDevelopmentServer(appUrl: string, bindAddr: string) {
|
|
|
|
consoleStore.setState({ appUrl, bindAddr })
|
2019-02-16 17:09:49 +01:00
|
|
|
}
|
|
|
|
|
2020-04-06 17:59:36 +02:00
|
|
|
let previousClient: import('webpack').Compiler | null = null
|
|
|
|
let previousServer: import('webpack').Compiler | null = null
|
2019-02-16 17:09:49 +01:00
|
|
|
|
2019-06-26 20:54:23 +02:00
|
|
|
type CompilerDiagnostics = {
|
|
|
|
errors: string[] | null
|
|
|
|
warnings: string[] | null
|
|
|
|
}
|
|
|
|
|
2019-02-16 17:09:49 +01:00
|
|
|
type WebpackStatus =
|
|
|
|
| { loading: true }
|
2020-05-15 19:30:01 +02:00
|
|
|
| ({ loading: false } & CompilerDiagnostics)
|
2019-02-16 17:09:49 +01:00
|
|
|
|
2019-03-19 00:21:18 +01:00
|
|
|
type AmpStatus = {
|
|
|
|
message: string
|
|
|
|
line: number
|
|
|
|
col: number
|
|
|
|
specUrl: string | null
|
2019-09-03 17:11:22 +02:00
|
|
|
code: string
|
2019-03-19 00:21:18 +01:00
|
|
|
}
|
|
|
|
|
2019-10-04 17:26:44 +02:00
|
|
|
export type AmpPageStatus = {
|
2019-03-19 00:21:18 +01:00
|
|
|
[page: string]: { errors: AmpStatus[]; warnings: AmpStatus[] }
|
|
|
|
}
|
|
|
|
|
|
|
|
type BuildStatusStore = {
|
2019-02-16 17:09:49 +01:00
|
|
|
client: WebpackStatus
|
|
|
|
server: WebpackStatus
|
2019-03-19 00:21:18 +01:00
|
|
|
amp: AmpPageStatus
|
2019-02-16 17:09:49 +01:00
|
|
|
}
|
|
|
|
|
2021-04-25 20:34:36 +02:00
|
|
|
// eslint typescript has a bug with TS enums
|
|
|
|
/* eslint-disable no-shadow */
|
2019-02-16 17:09:49 +01:00
|
|
|
enum WebpackStatusPhase {
|
|
|
|
COMPILING = 1,
|
|
|
|
COMPILED_WITH_ERRORS = 2,
|
2019-06-26 20:54:23 +02:00
|
|
|
COMPILED_WITH_WARNINGS = 4,
|
|
|
|
COMPILED = 5,
|
2019-02-16 17:09:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
function getWebpackStatusPhase(status: WebpackStatus): WebpackStatusPhase {
|
|
|
|
if (status.loading) {
|
|
|
|
return WebpackStatusPhase.COMPILING
|
|
|
|
}
|
|
|
|
if (status.errors) {
|
|
|
|
return WebpackStatusPhase.COMPILED_WITH_ERRORS
|
|
|
|
}
|
|
|
|
if (status.warnings) {
|
|
|
|
return WebpackStatusPhase.COMPILED_WITH_WARNINGS
|
|
|
|
}
|
|
|
|
return WebpackStatusPhase.COMPILED
|
|
|
|
}
|
|
|
|
|
2019-03-26 22:21:27 +01:00
|
|
|
export function formatAmpMessages(amp: AmpPageStatus) {
|
2019-03-19 00:21:18 +01:00
|
|
|
let output = chalk.bold('Amp Validation') + '\n\n'
|
|
|
|
let messages: string[][] = []
|
|
|
|
|
|
|
|
const chalkError = chalk.red('error')
|
|
|
|
function ampError(page: string, error: AmpStatus) {
|
|
|
|
messages.push([page, chalkError, error.message, error.specUrl || ''])
|
|
|
|
}
|
|
|
|
|
|
|
|
const chalkWarn = chalk.yellow('warn')
|
|
|
|
function ampWarn(page: string, warn: AmpStatus) {
|
|
|
|
messages.push([page, chalkWarn, warn.message, warn.specUrl || ''])
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const page in amp) {
|
2019-09-03 17:11:22 +02:00
|
|
|
let { errors, warnings } = amp[page]
|
|
|
|
|
|
|
|
const devOnlyFilter = (err: AmpStatus) => err.code !== 'DEV_MODE_ONLY'
|
|
|
|
errors = errors.filter(devOnlyFilter)
|
|
|
|
warnings = warnings.filter(devOnlyFilter)
|
|
|
|
if (!(errors.length || warnings.length)) {
|
|
|
|
// Skip page with no non-dev warnings
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2019-03-19 00:21:18 +01:00
|
|
|
if (errors.length) {
|
|
|
|
ampError(page, errors[0])
|
|
|
|
for (let index = 1; index < errors.length; ++index) {
|
|
|
|
ampError('', errors[index])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (warnings.length) {
|
|
|
|
ampWarn(errors.length ? '' : page, warnings[0])
|
|
|
|
for (let index = 1; index < warnings.length; ++index) {
|
|
|
|
ampWarn('', warnings[index])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
messages.push(['', '', '', ''])
|
|
|
|
}
|
|
|
|
|
2019-09-03 17:11:22 +02:00
|
|
|
if (!messages.length) {
|
|
|
|
return ''
|
|
|
|
}
|
|
|
|
|
2019-03-19 00:21:18 +01:00
|
|
|
output += textTable(messages, {
|
|
|
|
align: ['l', 'l', 'l', 'l'],
|
|
|
|
stringLength(str: string) {
|
|
|
|
return stripAnsi(str).length
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
return output
|
|
|
|
}
|
|
|
|
|
|
|
|
const buildStore = createStore<BuildStatusStore>()
|
2019-02-16 17:09:49 +01:00
|
|
|
|
2020-05-18 21:24:37 +02:00
|
|
|
buildStore.subscribe((state) => {
|
2019-03-19 00:21:18 +01:00
|
|
|
const { amp, client, server } = state
|
2019-02-16 17:09:49 +01:00
|
|
|
|
|
|
|
const [{ status }] = [
|
|
|
|
{ status: client, phase: getWebpackStatusPhase(client) },
|
|
|
|
{ status: server, phase: getWebpackStatusPhase(server) },
|
|
|
|
].sort((a, b) => a.phase.valueOf() - b.phase.valueOf())
|
|
|
|
|
2019-03-19 00:21:18 +01:00
|
|
|
const { bootstrap: bootstrapping, appUrl } = consoleStore.getState()
|
2019-02-16 17:09:49 +01:00
|
|
|
if (bootstrapping && status.loading) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-03-19 00:21:18 +01:00
|
|
|
let partialState: Partial<OutputState> = {
|
2019-02-16 17:09:49 +01:00
|
|
|
bootstrap: false,
|
|
|
|
appUrl: appUrl!,
|
|
|
|
}
|
2019-03-19 00:21:18 +01:00
|
|
|
|
|
|
|
if (status.loading) {
|
|
|
|
consoleStore.setState(
|
|
|
|
{ ...partialState, loading: true } as OutputState,
|
|
|
|
true
|
|
|
|
)
|
|
|
|
} else {
|
2020-05-15 19:30:01 +02:00
|
|
|
let { errors, warnings } = status
|
2019-06-26 20:54:23 +02:00
|
|
|
|
|
|
|
if (errors == null) {
|
|
|
|
if (Object.keys(amp).length > 0) {
|
2019-10-30 04:49:40 +01:00
|
|
|
warnings = (warnings || []).concat(formatAmpMessages(amp) || [])
|
2019-09-03 17:11:22 +02:00
|
|
|
if (!warnings.length) warnings = null
|
2019-06-26 20:54:23 +02:00
|
|
|
}
|
2019-03-19 00:21:18 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
consoleStore.setState(
|
2019-06-26 20:54:23 +02:00
|
|
|
{
|
|
|
|
...partialState,
|
|
|
|
loading: false,
|
|
|
|
typeChecking: false,
|
|
|
|
errors,
|
|
|
|
warnings,
|
|
|
|
} as OutputState,
|
2019-03-19 00:21:18 +01:00
|
|
|
true
|
|
|
|
)
|
|
|
|
}
|
2019-02-16 17:09:49 +01:00
|
|
|
})
|
|
|
|
|
2019-03-19 00:21:18 +01:00
|
|
|
export function ampValidation(
|
|
|
|
page: string,
|
|
|
|
errors: AmpStatus[],
|
|
|
|
warnings: AmpStatus[]
|
|
|
|
) {
|
|
|
|
const { amp } = buildStore.getState()
|
|
|
|
if (!(errors.length || warnings.length)) {
|
|
|
|
buildStore.setState({
|
|
|
|
amp: Object.keys(amp)
|
2020-05-18 21:24:37 +02:00
|
|
|
.filter((k) => k !== page)
|
2019-03-19 00:21:18 +01:00
|
|
|
.sort()
|
2019-11-11 04:24:53 +01:00
|
|
|
// eslint-disable-next-line no-sequences
|
2020-04-06 17:59:36 +02:00
|
|
|
.reduce((a, c) => ((a[c] = amp[c]), a), {} as AmpPageStatus),
|
2019-03-19 00:21:18 +01:00
|
|
|
})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
const newAmp: AmpPageStatus = { ...amp, [page]: { errors, warnings } }
|
|
|
|
buildStore.setState({
|
|
|
|
amp: Object.keys(newAmp)
|
|
|
|
.sort()
|
2019-11-11 04:24:53 +01:00
|
|
|
// eslint-disable-next-line no-sequences
|
2020-04-06 17:59:36 +02:00
|
|
|
.reduce((a, c) => ((a[c] = newAmp[c]), a), {} as AmpPageStatus),
|
2019-03-19 00:21:18 +01:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-06-26 20:54:23 +02:00
|
|
|
export function watchCompilers(
|
2020-04-06 17:59:36 +02:00
|
|
|
client: import('webpack').Compiler,
|
2020-05-15 19:30:01 +02:00
|
|
|
server: import('webpack').Compiler
|
2019-06-26 20:54:23 +02:00
|
|
|
) {
|
2019-02-16 17:09:49 +01:00
|
|
|
if (previousClient === client && previousServer === server) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-03-19 00:21:18 +01:00
|
|
|
buildStore.setState({
|
2019-02-16 17:09:49 +01:00
|
|
|
client: { loading: true },
|
|
|
|
server: { loading: true },
|
|
|
|
})
|
|
|
|
|
|
|
|
function tapCompiler(
|
|
|
|
key: string,
|
|
|
|
compiler: any,
|
|
|
|
onEvent: (status: WebpackStatus) => void
|
|
|
|
) {
|
|
|
|
compiler.hooks.invalid.tap(`NextJsInvalid-${key}`, () => {
|
|
|
|
onEvent({ loading: true })
|
|
|
|
})
|
|
|
|
|
2020-04-06 17:59:36 +02:00
|
|
|
compiler.hooks.done.tap(
|
|
|
|
`NextJsDone-${key}`,
|
|
|
|
(stats: import('webpack').Stats) => {
|
|
|
|
buildStore.setState({ amp: {} })
|
2019-03-19 00:21:18 +01:00
|
|
|
|
2020-04-06 17:59:36 +02:00
|
|
|
const { errors, warnings } = formatWebpackMessages(
|
|
|
|
stats.toJson({ all: false, warnings: true, errors: true })
|
|
|
|
)
|
2019-06-26 20:54:23 +02:00
|
|
|
|
2020-04-06 17:59:36 +02:00
|
|
|
const hasErrors = !!errors?.length
|
|
|
|
const hasWarnings = !!warnings?.length
|
2019-06-26 20:54:23 +02:00
|
|
|
|
2020-04-06 17:59:36 +02:00
|
|
|
onEvent({
|
|
|
|
loading: false,
|
|
|
|
errors: hasErrors ? errors : null,
|
|
|
|
warnings: hasWarnings ? warnings : null,
|
|
|
|
})
|
2019-06-26 20:54:23 +02:00
|
|
|
}
|
2020-04-06 17:59:36 +02:00
|
|
|
)
|
2019-02-16 17:09:49 +01:00
|
|
|
}
|
|
|
|
|
2020-05-18 21:24:37 +02:00
|
|
|
tapCompiler('client', client, (status) =>
|
2019-03-19 00:21:18 +01:00
|
|
|
buildStore.setState({ client: status })
|
2019-02-16 17:09:49 +01:00
|
|
|
)
|
2020-05-18 21:24:37 +02:00
|
|
|
tapCompiler('server', server, (status) =>
|
2019-03-19 00:21:18 +01:00
|
|
|
buildStore.setState({ server: status })
|
2019-02-16 17:09:49 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
previousClient = client
|
|
|
|
previousServer = server
|
|
|
|
}
|