@platformatic/next 2.74.3 → 3.0.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config.d.ts +1 -1
- package/index.js +25 -349
- package/lib/caching/valkey.js +1 -1
- package/lib/schema.js +10 -6
- package/lib/stackable.js +329 -0
- package/package.json +10 -8
- package/schema.json +3 -2
package/config.d.ts
CHANGED
package/index.js
CHANGED
|
@@ -1,361 +1,37 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
cleanBasePath,
|
|
6
|
-
createChildProcessListener,
|
|
7
|
-
createServerListener,
|
|
8
|
-
errors,
|
|
9
|
-
getServerUrl,
|
|
10
|
-
importFile,
|
|
11
|
-
resolvePackage,
|
|
12
|
-
schemaOptions
|
|
13
|
-
} from '@platformatic/basic'
|
|
14
|
-
import { ConfigManager } from '@platformatic/config'
|
|
15
|
-
import { ChildProcess } from 'node:child_process'
|
|
16
|
-
import { once } from 'node:events'
|
|
17
|
-
import { readFile } from 'node:fs/promises'
|
|
18
|
-
import { dirname, resolve as pathResolve } from 'node:path'
|
|
19
|
-
import { pathToFileURL } from 'node:url'
|
|
20
|
-
import { parse, satisfies } from 'semver'
|
|
21
|
-
import { packageJson, schema } from './lib/schema.js'
|
|
22
|
-
|
|
23
|
-
const supportedVersions = ['^14.0.0', '^15.0.0']
|
|
24
|
-
|
|
25
|
-
export * as cachingValkey from './lib/caching/valkey.js'
|
|
26
|
-
|
|
27
|
-
export class NextStackable extends BaseStackable {
|
|
28
|
-
#basePath
|
|
29
|
-
#next
|
|
30
|
-
#nextVersion
|
|
31
|
-
#child
|
|
32
|
-
#server
|
|
33
|
-
|
|
34
|
-
constructor (options, root, configManager) {
|
|
35
|
-
super('next', packageJson.version, options, root, configManager)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
async init () {
|
|
39
|
-
// This is needed to avoid Next.js to throw an error when the lockfile is not correct
|
|
40
|
-
// and the user is using npm but has pnpm in its $PATH.
|
|
41
|
-
//
|
|
42
|
-
// See: https://github.com/platformatic/composer-next-node-fastify/pull/3
|
|
43
|
-
//
|
|
44
|
-
// PS by Paolo: Sob.
|
|
45
|
-
process.env.NEXT_IGNORE_INCORRECT_LOCKFILE = 'true'
|
|
46
|
-
|
|
47
|
-
this.#next = pathResolve(dirname(resolvePackage(this.root, 'next')), '../..')
|
|
48
|
-
const nextPackage = JSON.parse(await readFile(pathResolve(this.#next, 'package.json'), 'utf-8'))
|
|
49
|
-
this.#nextVersion = parse(nextPackage.version)
|
|
50
|
-
|
|
51
|
-
if (this.#nextVersion.major < 15 || (this.#nextVersion.major <= 15 && this.#nextVersion.minor < 1)) {
|
|
52
|
-
await import('./lib/create-context-patch.js')
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/* c8 ignore next 3 */
|
|
56
|
-
if (!supportedVersions.some(v => satisfies(nextPackage.version, v))) {
|
|
57
|
-
throw new errors.UnsupportedVersion('next', nextPackage.version, supportedVersions)
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
async start ({ listen }) {
|
|
62
|
-
// Make this idempotent
|
|
63
|
-
if (this.url) {
|
|
64
|
-
return this.url
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
this.on('config', config => {
|
|
68
|
-
this.#basePath = config.basePath
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
if (this.isProduction) {
|
|
72
|
-
await this.#startProduction(listen)
|
|
73
|
-
} else {
|
|
74
|
-
await this.#startDevelopment(listen)
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
await this._collectMetrics()
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
async stop () {
|
|
81
|
-
if (this.subprocess) {
|
|
82
|
-
return this.stopCommand()
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
globalThis.platformatic.events.emit('plt:next:close')
|
|
86
|
-
|
|
87
|
-
if (this.isProduction) {
|
|
88
|
-
await new Promise((resolve, reject) => {
|
|
89
|
-
this.#server.close(error => {
|
|
90
|
-
/* c8 ignore next 3 */
|
|
91
|
-
if (error) {
|
|
92
|
-
return reject(error)
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
resolve()
|
|
96
|
-
})
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
await this.childManager.close()
|
|
100
|
-
} else {
|
|
101
|
-
const exitPromise = once(this.#child, 'exit')
|
|
102
|
-
await this.childManager.close()
|
|
103
|
-
process.kill(this.#child.pid, 'SIGKILL')
|
|
104
|
-
await exitPromise
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
async build () {
|
|
109
|
-
if (!this.#nextVersion) {
|
|
110
|
-
await this.init()
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const config = this.configManager.current
|
|
114
|
-
const loader = new URL('./lib/loader.js', import.meta.url)
|
|
115
|
-
this.#basePath = config.application?.basePath ? cleanBasePath(config.application?.basePath) : ''
|
|
116
|
-
|
|
117
|
-
let command = config.application.commands.build
|
|
118
|
-
|
|
119
|
-
if (!command) {
|
|
120
|
-
await this.init()
|
|
121
|
-
command = ['node', pathResolve(this.#next, './dist/bin/next'), 'build', this.root]
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return this.buildWithCommand(command, this.#basePath, { loader, scripts: this.#getChildManagerScripts() })
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/* c8 ignore next 5 */
|
|
128
|
-
async getWatchConfig () {
|
|
129
|
-
return {
|
|
130
|
-
enabled: false,
|
|
131
|
-
path: this.root
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
getMeta () {
|
|
136
|
-
const composer = { prefix: this.basePath ?? this.#basePath, wantsAbsoluteUrls: true, needsRootTrailingSlash: false }
|
|
137
|
-
|
|
138
|
-
if (this.url) {
|
|
139
|
-
composer.tcp = true
|
|
140
|
-
composer.url = this.url
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
return { composer }
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
async #startDevelopment () {
|
|
147
|
-
const config = this.configManager.current
|
|
148
|
-
const loaderUrl = new URL('./lib/loader.js', import.meta.url)
|
|
149
|
-
const command = this.configManager.current.application.commands.development
|
|
150
|
-
|
|
151
|
-
this.#basePath = config.application?.basePath ? cleanBasePath(config.application?.basePath) : ''
|
|
152
|
-
|
|
153
|
-
if (command) {
|
|
154
|
-
return this.startWithCommand(command, loaderUrl, this.#getChildManagerScripts())
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const { hostname, port } = this.serverConfig ?? {}
|
|
158
|
-
const serverOptions = {
|
|
159
|
-
host: hostname || '127.0.0.1',
|
|
160
|
-
port: port || 0
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const context = await this.getChildManagerContext(this.#basePath)
|
|
164
|
-
|
|
165
|
-
this.childManager = new ChildManager({
|
|
166
|
-
loader: loaderUrl,
|
|
167
|
-
context: {
|
|
168
|
-
...context,
|
|
169
|
-
port: false,
|
|
170
|
-
wantsAbsoluteUrls: true
|
|
171
|
-
},
|
|
172
|
-
scripts: this.#getChildManagerScripts()
|
|
173
|
-
})
|
|
174
|
-
|
|
175
|
-
const promise = once(this.childManager, 'url')
|
|
176
|
-
await this.#startDevelopmentNext(serverOptions)
|
|
177
|
-
const [url, clientWs] = await promise
|
|
178
|
-
this.url = url
|
|
179
|
-
this.clientWs = clientWs
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
async #startDevelopmentNext (serverOptions) {
|
|
183
|
-
const { nextDev } = await importFile(pathResolve(this.#next, './dist/cli/next-dev.js'))
|
|
184
|
-
|
|
185
|
-
try {
|
|
186
|
-
await this.childManager.inject()
|
|
187
|
-
const childPromise = createChildProcessListener()
|
|
188
|
-
|
|
189
|
-
this.#ensurePipeableStreamsInFork()
|
|
190
|
-
|
|
191
|
-
if (this.#nextVersion.major === 14 && this.#nextVersion.minor < 2) {
|
|
192
|
-
await nextDev({
|
|
193
|
-
'--hostname': serverOptions.host,
|
|
194
|
-
'--port': serverOptions.port,
|
|
195
|
-
_: [this.root]
|
|
196
|
-
})
|
|
197
|
-
} else {
|
|
198
|
-
await nextDev(serverOptions, 'default', this.root)
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
this.#child = await childPromise
|
|
202
|
-
this.#child.stdout.setEncoding('utf8')
|
|
203
|
-
this.#child.stderr.setEncoding('utf8')
|
|
204
|
-
|
|
205
|
-
this.#child.stdout.pipe(process.stdout, { end: false })
|
|
206
|
-
this.#child.stderr.pipe(process.stderr, { end: false })
|
|
207
|
-
} finally {
|
|
208
|
-
await this.childManager.eject()
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
async #startProduction (listen) {
|
|
213
|
-
const config = this.configManager.current
|
|
214
|
-
const loaderUrl = new URL('./lib/loader.js', import.meta.url)
|
|
215
|
-
const command = this.configManager.current.application.commands.production
|
|
216
|
-
|
|
217
|
-
this.#basePath = config.application?.basePath ? cleanBasePath(config.application?.basePath) : ''
|
|
218
|
-
|
|
219
|
-
if (command) {
|
|
220
|
-
const childManagerScripts = this.#getChildManagerScripts()
|
|
221
|
-
|
|
222
|
-
if (this.#nextVersion.major < 15 || (this.#nextVersion.major <= 15 && this.#nextVersion.minor < 1)) {
|
|
223
|
-
childManagerScripts.push(new URL('./lib/create-context-patch.js', import.meta.url))
|
|
224
|
-
}
|
|
225
|
-
return this.startWithCommand(command, loaderUrl, childManagerScripts)
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
this.childManager = new ChildManager({
|
|
229
|
-
loader: loaderUrl,
|
|
230
|
-
context: {
|
|
231
|
-
config: this.configManager.current,
|
|
232
|
-
serviceId: this.serviceId,
|
|
233
|
-
workerId: this.workerId,
|
|
234
|
-
// Always use URL to avoid serialization problem in Windows
|
|
235
|
-
root: pathToFileURL(this.root).toString(),
|
|
236
|
-
basePath: this.#basePath,
|
|
237
|
-
logLevel: this.logger.level,
|
|
238
|
-
isEntrypoint: this.isEntrypoint,
|
|
239
|
-
runtimeBasePath: this.runtimeConfig.basePath,
|
|
240
|
-
wantsAbsoluteUrls: true,
|
|
241
|
-
telemetryConfig: this.telemetryConfig
|
|
242
|
-
},
|
|
243
|
-
scripts: this.#getChildManagerScripts()
|
|
244
|
-
})
|
|
245
|
-
|
|
246
|
-
this.verifyOutputDirectory(pathResolve(this.root, '.next'))
|
|
247
|
-
await this.#startProductionNext()
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
async #startProductionNext () {
|
|
251
|
-
try {
|
|
252
|
-
globalThis.platformatic.config = this.configManager.current
|
|
253
|
-
await this.childManager.inject()
|
|
254
|
-
const { nextStart } = await importFile(pathResolve(this.#next, './dist/cli/next-start.js'))
|
|
255
|
-
|
|
256
|
-
const { hostname, port } = this.serverConfig ?? {}
|
|
257
|
-
const serverOptions = {
|
|
258
|
-
hostname: hostname || '127.0.0.1',
|
|
259
|
-
port: port || 0
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
this.childManager.register()
|
|
263
|
-
const serverPromise = createServerListener(
|
|
264
|
-
(this.isEntrypoint ? serverOptions?.port : undefined) ?? true,
|
|
265
|
-
(this.isEntrypoint ? serverOptions?.hostname : undefined) ?? true
|
|
266
|
-
)
|
|
267
|
-
|
|
268
|
-
if (this.#nextVersion.major === 14 && this.#nextVersion.minor < 2) {
|
|
269
|
-
await nextStart({
|
|
270
|
-
'--hostname': serverOptions.host,
|
|
271
|
-
'--port': serverOptions.port,
|
|
272
|
-
_: [this.root]
|
|
273
|
-
})
|
|
274
|
-
} else {
|
|
275
|
-
await nextStart(serverOptions, this.root)
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
this.#server = await serverPromise
|
|
279
|
-
this.url = getServerUrl(this.#server)
|
|
280
|
-
} finally {
|
|
281
|
-
await this.childManager.eject()
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
#getChildManagerScripts () {
|
|
286
|
-
const scripts = []
|
|
287
|
-
|
|
288
|
-
if (this.#nextVersion.major === 15) {
|
|
289
|
-
scripts.push(new URL('./lib/loader-next-15.cjs', import.meta.url))
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
return scripts
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
// In development mode, Next.js starts the dev server using child_process.fork with stdio set to 'inherit'.
|
|
296
|
-
// In order to capture the output, we need to ensure that the streams are pipeable and thus we perform a one-time
|
|
297
|
-
// monkey-patch of the ChildProcess.prototype.spawn method to override stdio[1] and stdio[2] to 'pipe'.
|
|
298
|
-
#ensurePipeableStreamsInFork () {
|
|
299
|
-
const originalSpawn = ChildProcess.prototype.spawn
|
|
300
|
-
|
|
301
|
-
// IMPORTANT: If Next.js code changes this might not work anymore. When this gives error, dig into Next.js code
|
|
302
|
-
// to evaluate the new path and/or if this is still necessary.
|
|
303
|
-
const startServerPath = pathResolve(this.#next, './dist/server/lib/start-server.js')
|
|
304
|
-
|
|
305
|
-
ChildProcess.prototype.spawn = function (options) {
|
|
306
|
-
if (options.args?.[1] === startServerPath) {
|
|
307
|
-
options.stdio[1] = 'pipe'
|
|
308
|
-
options.stdio[2] = 'pipe'
|
|
309
|
-
|
|
310
|
-
// Uninstall the patch
|
|
311
|
-
ChildProcess.prototype.spawn = originalSpawn
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
return originalSpawn.call(this, options)
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
}
|
|
1
|
+
import { transform as basicTransform, resolve, validationOptions } from '@platformatic/basic'
|
|
2
|
+
import { kMetadata, loadConfiguration as utilsLoadConfiguration } from '@platformatic/foundation'
|
|
3
|
+
import { schema } from './lib/schema.js'
|
|
4
|
+
import { NextStackable } from './lib/stackable.js'
|
|
318
5
|
|
|
319
6
|
/* c8 ignore next 9 */
|
|
320
|
-
function
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
}
|
|
7
|
+
export async function transform (config, schema, options) {
|
|
8
|
+
config = await basicTransform(config, schema, options)
|
|
9
|
+
config.watch = { enabled: false }
|
|
324
10
|
|
|
325
|
-
if (
|
|
326
|
-
|
|
11
|
+
if (config.cache?.adapter === 'redis') {
|
|
12
|
+
config.cache.adapter = 'valkey'
|
|
327
13
|
}
|
|
328
14
|
|
|
329
|
-
|
|
330
|
-
this.current.cache.adapter = 'valkey'
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
return basicTransformConfig.call(this)
|
|
15
|
+
return config
|
|
334
16
|
}
|
|
335
17
|
|
|
336
|
-
export async function
|
|
337
|
-
const root =
|
|
18
|
+
export async function loadConfiguration (configOrRoot, sourceOrConfig, context) {
|
|
19
|
+
const { root, source } = await resolve(configOrRoot, sourceOrConfig, 'application')
|
|
338
20
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
context: opts.context
|
|
21
|
+
return utilsLoadConfiguration(source, context?.schema ?? schema, {
|
|
22
|
+
validationOptions,
|
|
23
|
+
transform,
|
|
24
|
+
replaceEnv: true,
|
|
25
|
+
root,
|
|
26
|
+
...context
|
|
346
27
|
})
|
|
347
|
-
await configManager.parseAndValidate()
|
|
348
|
-
|
|
349
|
-
return new NextStackable(opts, root, configManager)
|
|
350
28
|
}
|
|
351
29
|
|
|
352
|
-
export
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
schemaOptions,
|
|
356
|
-
transformConfig
|
|
357
|
-
},
|
|
358
|
-
buildStackable,
|
|
359
|
-
schema,
|
|
360
|
-
version: packageJson.version
|
|
30
|
+
export async function create (configOrRoot, sourceOrConfig, context) {
|
|
31
|
+
const config = await loadConfiguration(configOrRoot, sourceOrConfig, context)
|
|
32
|
+
return new NextStackable(config[kMetadata].root, config, context)
|
|
361
33
|
}
|
|
34
|
+
|
|
35
|
+
export * as cachingValkey from './lib/caching/valkey.js'
|
|
36
|
+
export { packageJson, schema, schemaComponents, version } from './lib/schema.js'
|
|
37
|
+
export * from './lib/stackable.js'
|
package/lib/caching/valkey.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { buildPinoFormatters, buildPinoTimestamp, ensureLoggableError } from '@platformatic/
|
|
1
|
+
import { buildPinoFormatters, buildPinoTimestamp, ensureLoggableError } from '@platformatic/foundation'
|
|
2
2
|
import { Redis } from 'iovalkey'
|
|
3
3
|
import { pack, unpack } from 'msgpackr'
|
|
4
4
|
import { existsSync, readFileSync } from 'node:fs'
|
package/lib/schema.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { schemaComponents } from '@platformatic/basic'
|
|
2
|
-
import { schemaComponents as utilsSchemaComponents } from '@platformatic/
|
|
1
|
+
import { schemaComponents as basicSchemaComponents } from '@platformatic/basic'
|
|
2
|
+
import { schemaComponents as utilsSchemaComponents } from '@platformatic/foundation'
|
|
3
3
|
import { readFileSync } from 'node:fs'
|
|
4
|
+
import { resolve } from 'node:path'
|
|
4
5
|
|
|
5
|
-
export const packageJson = JSON.parse(readFileSync(
|
|
6
|
+
export const packageJson = JSON.parse(readFileSync(resolve(import.meta.dirname, '../package.json'), 'utf8'))
|
|
7
|
+
export const version = packageJson.version
|
|
6
8
|
|
|
7
9
|
export const cache = {
|
|
8
10
|
type: 'object',
|
|
@@ -46,10 +48,12 @@ const next = {
|
|
|
46
48
|
additionalProperties: false
|
|
47
49
|
}
|
|
48
50
|
|
|
51
|
+
export const schemaComponents = { next }
|
|
52
|
+
|
|
49
53
|
export const schema = {
|
|
50
54
|
$id: `https://schemas.platformatic.dev/@platformatic/next/${packageJson.version}.json`,
|
|
51
55
|
$schema: 'http://json-schema.org/draft-07/schema#',
|
|
52
|
-
title: 'Platformatic Next.js
|
|
56
|
+
title: 'Platformatic Next.js Config',
|
|
53
57
|
type: 'object',
|
|
54
58
|
properties: {
|
|
55
59
|
$schema: {
|
|
@@ -57,8 +61,8 @@ export const schema = {
|
|
|
57
61
|
},
|
|
58
62
|
logger: utilsSchemaComponents.logger,
|
|
59
63
|
server: utilsSchemaComponents.server,
|
|
60
|
-
watch:
|
|
61
|
-
application:
|
|
64
|
+
watch: basicSchemaComponents.watch,
|
|
65
|
+
application: basicSchemaComponents.buildableApplication,
|
|
62
66
|
runtime: utilsSchemaComponents.wrappedRuntime,
|
|
63
67
|
next,
|
|
64
68
|
cache
|
package/lib/stackable.js
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BaseStackable,
|
|
3
|
+
ChildManager,
|
|
4
|
+
cleanBasePath,
|
|
5
|
+
createChildProcessListener,
|
|
6
|
+
createServerListener,
|
|
7
|
+
errors,
|
|
8
|
+
getServerUrl,
|
|
9
|
+
importFile,
|
|
10
|
+
resolvePackage
|
|
11
|
+
} from '@platformatic/basic'
|
|
12
|
+
import { ChildProcess } from 'node:child_process'
|
|
13
|
+
import { once } from 'node:events'
|
|
14
|
+
import { readFile, writeFile } from 'node:fs/promises'
|
|
15
|
+
import { dirname, resolve as resolvePath } from 'node:path'
|
|
16
|
+
import { pathToFileURL } from 'node:url'
|
|
17
|
+
import { parse, satisfies } from 'semver'
|
|
18
|
+
import { version } from './schema.js'
|
|
19
|
+
|
|
20
|
+
const supportedVersions = ['^14.0.0', '^15.0.0']
|
|
21
|
+
|
|
22
|
+
export class NextStackable extends BaseStackable {
|
|
23
|
+
#basePath
|
|
24
|
+
#next
|
|
25
|
+
#nextVersion
|
|
26
|
+
#child
|
|
27
|
+
#server
|
|
28
|
+
|
|
29
|
+
constructor (root, config, context) {
|
|
30
|
+
super('next', version, root, config, context)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async init () {
|
|
34
|
+
await super.init()
|
|
35
|
+
|
|
36
|
+
// This is needed to avoid Next.js to throw an error when the lockfile is not correct
|
|
37
|
+
// and the user is using npm but has pnpm in its $PATH.
|
|
38
|
+
//
|
|
39
|
+
// See: https://github.com/platformatic/composer-next-node-fastify/pull/3
|
|
40
|
+
//
|
|
41
|
+
// PS by Paolo: Sob.
|
|
42
|
+
process.env.NEXT_IGNORE_INCORRECT_LOCKFILE = 'true'
|
|
43
|
+
|
|
44
|
+
this.#next = resolvePath(dirname(resolvePackage(this.root, 'next')), '../..')
|
|
45
|
+
const nextPackage = JSON.parse(await readFile(resolvePath(this.#next, 'package.json'), 'utf-8'))
|
|
46
|
+
this.#nextVersion = parse(nextPackage.version)
|
|
47
|
+
|
|
48
|
+
if (this.#nextVersion.major < 15 || (this.#nextVersion.major <= 15 && this.#nextVersion.minor < 1)) {
|
|
49
|
+
await import('./create-context-patch.js')
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* c8 ignore next 3 */
|
|
53
|
+
if (!supportedVersions.some(v => satisfies(nextPackage.version, v))) {
|
|
54
|
+
throw new errors.UnsupportedVersion('next', nextPackage.version, supportedVersions)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async start ({ listen }) {
|
|
59
|
+
// Make this idempotent
|
|
60
|
+
if (this.url) {
|
|
61
|
+
return this.url
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
this.on('config', config => {
|
|
65
|
+
this.#basePath = config.basePath
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
if (this.isProduction) {
|
|
69
|
+
await this.#startProduction(listen)
|
|
70
|
+
} else {
|
|
71
|
+
await this.#startDevelopment(listen)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
await this._collectMetrics()
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async stop () {
|
|
78
|
+
if (this.subprocess) {
|
|
79
|
+
return this.stopCommand()
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
globalThis.platformatic.events.emit('plt:next:close')
|
|
83
|
+
|
|
84
|
+
if (this.isProduction) {
|
|
85
|
+
await new Promise((resolve, reject) => {
|
|
86
|
+
this.#server.close(error => {
|
|
87
|
+
/* c8 ignore next 3 */
|
|
88
|
+
if (error) {
|
|
89
|
+
return reject(error)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
resolve()
|
|
93
|
+
})
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
await this.childManager.close()
|
|
97
|
+
} else {
|
|
98
|
+
const exitPromise = once(this.#child, 'exit')
|
|
99
|
+
await this.childManager.close()
|
|
100
|
+
process.kill(this.#child.pid, 'SIGKILL')
|
|
101
|
+
await exitPromise
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async build () {
|
|
106
|
+
if (!this.#nextVersion) {
|
|
107
|
+
await this.init()
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const config = this.config
|
|
111
|
+
const loader = new URL('./loader.js', import.meta.url)
|
|
112
|
+
this.#basePath = config.application?.basePath ? cleanBasePath(config.application?.basePath) : ''
|
|
113
|
+
|
|
114
|
+
let command = config.application.commands.build
|
|
115
|
+
|
|
116
|
+
if (!command) {
|
|
117
|
+
await this.init()
|
|
118
|
+
command = ['node', resolvePath(this.#next, './dist/bin/next'), 'build', this.root]
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
await this.buildWithCommand(command, this.#basePath, { loader, scripts: this.#getChildManagerScripts() })
|
|
122
|
+
|
|
123
|
+
// This is need to avoid Next.js 15.4+ to throw an error as process.cwd() is not the root of the Next.js application
|
|
124
|
+
if (
|
|
125
|
+
config.cache?.adapter &&
|
|
126
|
+
(this.#nextVersion.major > 15 || (this.#nextVersion.major === 15 && this.#nextVersion.minor >= 4))
|
|
127
|
+
) {
|
|
128
|
+
const distDir = resolvePath(this.root, '.next')
|
|
129
|
+
const requiredServerFilesPath = resolvePath(distDir, 'required-server-files.json')
|
|
130
|
+
const requiredServerFiles = JSON.parse(await readFile(requiredServerFilesPath, 'utf-8'))
|
|
131
|
+
|
|
132
|
+
if (requiredServerFiles.config.cacheHandler) {
|
|
133
|
+
requiredServerFiles.config.cacheHandler = resolvePath(distDir, requiredServerFiles.config.cacheHandler)
|
|
134
|
+
await writeFile(requiredServerFilesPath, JSON.stringify(requiredServerFiles, null, 2))
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/* c8 ignore next 5 */
|
|
140
|
+
async getWatchConfig () {
|
|
141
|
+
return {
|
|
142
|
+
enabled: false,
|
|
143
|
+
path: this.root
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
getMeta () {
|
|
148
|
+
const composer = { prefix: this.basePath ?? this.#basePath, wantsAbsoluteUrls: true, needsRootTrailingSlash: false }
|
|
149
|
+
|
|
150
|
+
if (this.url) {
|
|
151
|
+
composer.tcp = true
|
|
152
|
+
composer.url = this.url
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return { composer }
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async #startDevelopment () {
|
|
159
|
+
const config = this.config
|
|
160
|
+
const loaderUrl = new URL('./loader.js', import.meta.url)
|
|
161
|
+
const command = this.config.application.commands.development
|
|
162
|
+
|
|
163
|
+
this.#basePath = config.application?.basePath ? cleanBasePath(config.application?.basePath) : ''
|
|
164
|
+
|
|
165
|
+
if (command) {
|
|
166
|
+
return this.startWithCommand(command, loaderUrl, this.#getChildManagerScripts())
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const { hostname, port } = this.serverConfig ?? {}
|
|
170
|
+
const serverOptions = {
|
|
171
|
+
host: hostname || '127.0.0.1',
|
|
172
|
+
port: port || 0
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const context = await this.getChildManagerContext(this.#basePath)
|
|
176
|
+
|
|
177
|
+
this.childManager = new ChildManager({
|
|
178
|
+
loader: loaderUrl,
|
|
179
|
+
context: {
|
|
180
|
+
...context,
|
|
181
|
+
port: false,
|
|
182
|
+
wantsAbsoluteUrls: true
|
|
183
|
+
},
|
|
184
|
+
scripts: this.#getChildManagerScripts()
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
const promise = once(this.childManager, 'url')
|
|
188
|
+
await this.#startDevelopmentNext(serverOptions)
|
|
189
|
+
const [url, clientWs] = await promise
|
|
190
|
+
this.url = url
|
|
191
|
+
this.clientWs = clientWs
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async #startDevelopmentNext (serverOptions) {
|
|
195
|
+
const { nextDev } = await importFile(resolvePath(this.#next, './dist/cli/next-dev.js'))
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
await this.childManager.inject()
|
|
199
|
+
const childPromise = createChildProcessListener()
|
|
200
|
+
|
|
201
|
+
this.#ensurePipeableStreamsInFork()
|
|
202
|
+
|
|
203
|
+
if (this.#nextVersion.major === 14 && this.#nextVersion.minor < 2) {
|
|
204
|
+
await nextDev({
|
|
205
|
+
'--hostname': serverOptions.host,
|
|
206
|
+
'--port': serverOptions.port,
|
|
207
|
+
_: [this.root]
|
|
208
|
+
})
|
|
209
|
+
} else {
|
|
210
|
+
await nextDev(serverOptions, 'default', this.root)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
this.#child = await childPromise
|
|
214
|
+
this.#child.stdout.setEncoding('utf8')
|
|
215
|
+
this.#child.stderr.setEncoding('utf8')
|
|
216
|
+
|
|
217
|
+
this.#child.stdout.pipe(process.stdout, { end: false })
|
|
218
|
+
this.#child.stderr.pipe(process.stderr, { end: false })
|
|
219
|
+
} finally {
|
|
220
|
+
await this.childManager.eject()
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async #startProduction (listen) {
|
|
225
|
+
const config = this.config
|
|
226
|
+
const loaderUrl = new URL('./loader.js', import.meta.url)
|
|
227
|
+
const command = this.config.application.commands.production
|
|
228
|
+
|
|
229
|
+
this.#basePath = config.application?.basePath ? cleanBasePath(config.application?.basePath) : ''
|
|
230
|
+
|
|
231
|
+
if (command) {
|
|
232
|
+
const childManagerScripts = this.#getChildManagerScripts()
|
|
233
|
+
|
|
234
|
+
if (this.#nextVersion.major < 15 || (this.#nextVersion.major <= 15 && this.#nextVersion.minor < 1)) {
|
|
235
|
+
childManagerScripts.push(new URL('./create-context-patch.js', import.meta.url))
|
|
236
|
+
}
|
|
237
|
+
return this.startWithCommand(command, loaderUrl, childManagerScripts)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
this.childManager = new ChildManager({
|
|
241
|
+
loader: loaderUrl,
|
|
242
|
+
context: {
|
|
243
|
+
config: this.config,
|
|
244
|
+
serviceId: this.serviceId,
|
|
245
|
+
workerId: this.workerId,
|
|
246
|
+
// Always use URL to avoid serialization problem in Windows
|
|
247
|
+
root: pathToFileURL(this.root).toString(),
|
|
248
|
+
basePath: this.#basePath,
|
|
249
|
+
logLevel: this.logger.level,
|
|
250
|
+
isEntrypoint: this.isEntrypoint,
|
|
251
|
+
runtimeBasePath: this.runtimeConfig.basePath,
|
|
252
|
+
wantsAbsoluteUrls: true,
|
|
253
|
+
telemetryConfig: this.telemetryConfig
|
|
254
|
+
},
|
|
255
|
+
scripts: this.#getChildManagerScripts()
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
this.verifyOutputDirectory(resolvePath(this.root, '.next'))
|
|
259
|
+
await this.#startProductionNext()
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async #startProductionNext () {
|
|
263
|
+
try {
|
|
264
|
+
globalThis.platformatic.config = this.config
|
|
265
|
+
await this.childManager.inject()
|
|
266
|
+
const { nextStart } = await importFile(resolvePath(this.#next, './dist/cli/next-start.js'))
|
|
267
|
+
|
|
268
|
+
const { hostname, port } = this.serverConfig ?? {}
|
|
269
|
+
const serverOptions = {
|
|
270
|
+
hostname: hostname || '127.0.0.1',
|
|
271
|
+
port: port || 0
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
this.childManager.register()
|
|
275
|
+
const serverPromise = createServerListener(
|
|
276
|
+
(this.isEntrypoint ? serverOptions?.port : undefined) ?? true,
|
|
277
|
+
(this.isEntrypoint ? serverOptions?.hostname : undefined) ?? true
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
if (this.#nextVersion.major === 14 && this.#nextVersion.minor < 2) {
|
|
281
|
+
await nextStart({
|
|
282
|
+
'--hostname': serverOptions.host,
|
|
283
|
+
'--port': serverOptions.port,
|
|
284
|
+
_: [this.root]
|
|
285
|
+
})
|
|
286
|
+
} else {
|
|
287
|
+
await nextStart(serverOptions, this.root)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
this.#server = await serverPromise
|
|
291
|
+
this.url = getServerUrl(this.#server)
|
|
292
|
+
} finally {
|
|
293
|
+
await this.childManager.eject()
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
#getChildManagerScripts () {
|
|
298
|
+
const scripts = []
|
|
299
|
+
|
|
300
|
+
if (this.#nextVersion.major === 15) {
|
|
301
|
+
scripts.push(new URL('./loader-next-15.cjs', import.meta.url))
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return scripts
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// In development mode, Next.js starts the dev server using child_process.fork with stdio set to 'inherit'.
|
|
308
|
+
// In order to capture the output, we need to ensure that the streams are pipeable and thus we perform a one-time
|
|
309
|
+
// monkey-patch of the ChildProcess.prototype.spawn method to override stdio[1] and stdio[2] to 'pipe'.
|
|
310
|
+
#ensurePipeableStreamsInFork () {
|
|
311
|
+
const originalSpawn = ChildProcess.prototype.spawn
|
|
312
|
+
|
|
313
|
+
// IMPORTANT: If Next.js code changes this might not work anymore. When this gives error, dig into Next.js code
|
|
314
|
+
// to evaluate the new path and/or if this is still necessary.
|
|
315
|
+
const startServerPath = resolvePath(this.#next, './dist/server/lib/start-server.js')
|
|
316
|
+
|
|
317
|
+
ChildProcess.prototype.spawn = function (options) {
|
|
318
|
+
if (options.args?.[1] === startServerPath) {
|
|
319
|
+
options.stdio[1] = 'pipe'
|
|
320
|
+
options.stdio[2] = 'pipe'
|
|
321
|
+
|
|
322
|
+
// Uninstall the patch
|
|
323
|
+
ChildProcess.prototype.spawn = originalSpawn
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return originalSpawn.call(this, options)
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/next",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0-alpha.2",
|
|
4
4
|
"description": "Platformatic Next.js Stackable",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -23,9 +23,8 @@
|
|
|
23
23
|
"iovalkey": "^0.3.0",
|
|
24
24
|
"msgpackr": "^1.11.2",
|
|
25
25
|
"semver": "^7.6.3",
|
|
26
|
-
"@platformatic/basic": "
|
|
27
|
-
"@platformatic/
|
|
28
|
-
"@platformatic/utils": "2.74.3"
|
|
26
|
+
"@platformatic/basic": "3.0.0-alpha.2",
|
|
27
|
+
"@platformatic/foundation": "3.0.0-alpha.2"
|
|
29
28
|
},
|
|
30
29
|
"devDependencies": {
|
|
31
30
|
"@fastify/reply-from": "^12.0.0",
|
|
@@ -39,12 +38,15 @@
|
|
|
39
38
|
"next": "^15.0.0",
|
|
40
39
|
"typescript": "^5.5.4",
|
|
41
40
|
"ws": "^8.18.0",
|
|
42
|
-
"@platformatic/composer": "
|
|
43
|
-
"@platformatic/service": "
|
|
41
|
+
"@platformatic/composer": "3.0.0-alpha.2",
|
|
42
|
+
"@platformatic/service": "3.0.0-alpha.2"
|
|
43
|
+
},
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=22.18.0"
|
|
44
46
|
},
|
|
45
47
|
"scripts": {
|
|
46
|
-
"test": "npm run lint && borp --concurrency=1 --
|
|
47
|
-
"coverage": "npm run lint && borp -C -X test -X test/fixtures --concurrency=1 --
|
|
48
|
+
"test": "npm run lint && borp --concurrency=1 --timeout 1200000",
|
|
49
|
+
"coverage": "npm run lint && borp -C -X test -X test/fixtures --concurrency=1 --timeout 1200000",
|
|
48
50
|
"gen-schema": "node lib/schema.js > schema.json",
|
|
49
51
|
"gen-types": "json2ts > config.d.ts < schema.json",
|
|
50
52
|
"build": "pnpm run gen-schema && pnpm run gen-types",
|
package/schema.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$id": "https://schemas.platformatic.dev/@platformatic/next/
|
|
2
|
+
"$id": "https://schemas.platformatic.dev/@platformatic/next/3.0.0-alpha.2.json",
|
|
3
3
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
4
|
-
"title": "Platformatic Next.js
|
|
4
|
+
"title": "Platformatic Next.js Config",
|
|
5
5
|
"type": "object",
|
|
6
6
|
"properties": {
|
|
7
7
|
"$schema": {
|
|
@@ -362,6 +362,7 @@
|
|
|
362
362
|
}
|
|
363
363
|
},
|
|
364
364
|
"additionalProperties": false,
|
|
365
|
+
"required": [],
|
|
365
366
|
"default": {}
|
|
366
367
|
},
|
|
367
368
|
"runtime": {
|