-
Notifications
You must be signed in to change notification settings - Fork 26k
/
index.ts
140 lines (125 loc) · 4.31 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
import type NextServer from '../../next-server'
import { getNodeOptionsWithoutInspect } from '../utils'
import { deserializeErr, errorToJSON } from '../../render'
import crypto from 'crypto'
import isError from '../../../lib/is-error'
import { genRenderExecArgv } from '../worker-utils'
// we can't use process.send as jest-worker relies on
// it already and can cause unexpected message errors
// so we create an IPC server for communicating
export async function createIpcServer(
server: InstanceType<typeof NextServer>
): Promise<{
ipcPort: number
ipcServer: import('http').Server
ipcValidationKey: string
}> {
// Generate a random key in memory to validate messages from other processes.
// This is just a simple guard against other processes attempting to send
// traffic to the IPC server.
const ipcValidationKey = crypto.randomBytes(32).toString('hex')
const ipcServer = (require('http') as typeof import('http')).createServer(
async (req, res) => {
try {
const url = new URL(req.url || '/', 'http://n')
const key = url.searchParams.get('key')
if (key !== ipcValidationKey) {
return res.end()
}
const method = url.searchParams.get('method')
const args: any[] = JSON.parse(url.searchParams.get('args') || '[]')
if (!method || !Array.isArray(args)) {
return res.end()
}
if (typeof (server as any)[method] === 'function') {
if (method === 'logErrorWithOriginalStack' && args[0]?.stack) {
args[0] = deserializeErr(args[0])
}
let result = await (server as any)[method](...args)
if (result && typeof result === 'object' && result.stack) {
result = errorToJSON(result)
}
res.end(JSON.stringify(result || ''))
}
} catch (err: any) {
if (isError(err) && err.code !== 'ENOENT') {
console.error(err)
}
res.end(
JSON.stringify({
err: { name: err.name, message: err.message, stack: err.stack },
})
)
}
}
)
const ipcPort = await new Promise<number>((resolveIpc) => {
ipcServer.listen(0, server.hostname, () => {
const addr = ipcServer.address()
if (addr && typeof addr === 'object') {
resolveIpc(addr.port)
}
})
})
return {
ipcPort,
ipcServer,
ipcValidationKey,
}
}
export const createWorker = async (
ipcPort: number,
ipcValidationKey: string,
isNodeDebugging: boolean | 'brk' | undefined,
type: 'pages' | 'app',
useServerActions?: boolean
) => {
const { initialEnv } = require('@next/env') as typeof import('@next/env')
const { Worker } = require('next/dist/compiled/jest-worker')
const worker = new Worker(require.resolve('../render-server'), {
numWorkers: 1,
// TODO: do we want to allow more than 10 OOM restarts?
maxRetries: 10,
forkOptions: {
env: {
FORCE_COLOR: '1',
...initialEnv,
// we don't pass down NODE_OPTIONS as it can
// allow more memory usage than expected
NODE_OPTIONS: getNodeOptionsWithoutInspect()
.replace(/--max-old-space-size=[\d]{1,}/, '')
.trim(),
__NEXT_PRIVATE_RENDER_WORKER: type,
__NEXT_PRIVATE_ROUTER_IPC_PORT: ipcPort + '',
__NEXT_PRIVATE_ROUTER_IPC_KEY: ipcValidationKey,
__NEXT_PRIVATE_STANDALONE_CONFIG:
process.env.__NEXT_PRIVATE_STANDALONE_CONFIG,
NODE_ENV: process.env.NODE_ENV,
...(type === 'app'
? {
__NEXT_PRIVATE_PREBUNDLED_REACT: useServerActions
? 'experimental'
: 'next',
}
: {}),
...(process.env.NEXT_CPU_PROF
? { __NEXT_PRIVATE_CPU_PROFILE: `CPU.${type}-renderer` }
: {}),
},
execArgv: await genRenderExecArgv(isNodeDebugging, type),
},
exposedMethods: [
'initialize',
'deleteCache',
'deleteAppClientCache',
'clearModuleContext',
],
}) as any as InstanceType<typeof Worker> & {
initialize: typeof import('../render-server').initialize
deleteCache: typeof import('../render-server').deleteCache
deleteAppClientCache: typeof import('../render-server').deleteAppClientCache
}
worker.getStderr().pipe(process.stderr)
worker.getStdout().pipe(process.stdout)
return worker
}