🌳
pt0/nextF/serverF/runNextF.mts
1import { createServer, type IncomingMessage, type ServerResponse } from 'http'
2import { parse } from 'url'
3import next from 'next'
4import * as _ from 'lodash-es'
21export type RunNextCtxStore = {
22 pagesDir?: string
23 handleRequest?: (opts: {app: any, req: IncomingMessage, res: ServerResponse, parsedUrl: any}) => Promise<boolean | void>
24 handleUpgrade?: (req: IncomingMessage, socket: any, head: Buffer) => void
27export const runNext = async (props?: {dev?: boolean, devListenAllInterfaces?: boolean}) => {
28 return await withDb(async () => {
29 return await doRunNext(props)
30 }, {})
34const doRunNext = async (props: {dev?: boolean, devListenAllInterfaces?: boolean}={}) => {
35 const dev = _.isBoolean(props.dev) ? props.dev : getReqEnvConfItem('nextIsDev')
36 const nodeEnv = process.env.NODE_ENV
37 betLog('doRunNext', {dev, nodeEnv})
39 if (dev) {
40 Error.stackTraceLimit = 100
41 process.on('uncaughtException', (err) => {
42 console.error('[uncaughtException]', err?.stack || err)
43 })
44 process.on('unhandledRejection', (reason) => {
45 const stack = (reason as Error)?.stack || reason
46 console.error('[unhandledRejection]', stack)
47 })
48 }
50 const port = getNextSvcPortNo() as number
53 await maybeKillExisting(port)
55 const hostname = (isDevPc && !props.devListenAllInterfaces && !getEnvConfItem('devListenAllInterfaces')) ? '127.0.0.1' : '0.0.0.0'
57 const ctxStore = (runNextCtx.getStore() ?? {}) as RunNextCtxStore
58 const {pagesDir} = ctxStore
59 const app = next({
60 dev: dev as boolean, port,
61 dir: pagesDir,
62 })
63 throwIf(() => dev && nodeEnv != 'development', {
64 nodeEnv, dev,
65 })
67 const handle = app.getRequestHandler()
68 const {handleRequest, handleUpgrade} = (runNextCtx.getStore() ?? {}) as RunNextCtxStore
70 await app.prepare()
73 const server = createServer(async (req, res) => {
74 const startTime = Date.now()
75 try {
76 const parsedUrl = parse(req.url!, true)
77 const { pathname } = parsedUrl
79 if (pathname?.startsWith('/health/')) {
80 const probeType = pathname.split('/')[2]
81 handleProbe(probeType, res)
82 return
83 }
85 if (handleRequest) {
86 if (await handleRequest({app, req, res, parsedUrl})) return
87 }
89 await handle(req, res, parsedUrl)
90 if (pathname && !pathname.startsWith('/_next/') && !pathname.startsWith('/api/graphql')) {
91 console.log(`${req.method} ${pathname} ${res.statusCode} in ${Date.now() - startTime}ms`)
92 }
93 } catch (err) {
94 res.statusCode = 500
95 res.end(genericServerErr)
96 const isSharedErr = err instanceof PtErr
97 console.error('Error occurred handling', req.url, {isSharedErr}, err)
98 await maybeNotifError({err, reqCtx: undefined})
99 }
100 })
102 server.on('error', (err) => handleServerEaddrinuse(err, port))
104 if (handleUpgrade) {
105 const g = globalThis as unknown as { __runNextUpgradeServer?: typeof server, __runNextUpgradeHandler?: typeof handleUpgrade }
106 const prevServer = g.__runNextUpgradeServer
107 if (prevServer && prevServer !== server) {
108 prevServer.removeListener('upgrade', g.__runNextUpgradeHandler!)
109 }
110 if (prevServer !== server) {
111 server.on('upgrade', handleUpgrade)
112 g.__runNextUpgradeServer = server
113 g.__runNextUpgradeHandler = handleUpgrade
114 }
115 }
117 server.listen(port, hostname as string, () => {
118 const displayHost = isDevPc && pagesDir ? `${pagesDir.split('/').pop()}.localhost` : hostname
119 const name = process.env.PT_DEPLOY_NAME, deployHostname = process.env.PT_DEPLOY_HOSTNAME
120 const host = isDevPc ? `${displayHost}:${port}` : deployHostname
121 const servedBy = name ? ` {name: '${name}', host: '${host}'}` : ''
122 console.log(`> Ready on http://${displayHost}:${port}${servedBy}`)
123 })
125 return new Promise((resolv, reject) => {}) // fake promise, to accurately represent server^ keeps running