@platformatic/next 3.32.0 → 3.33.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 CHANGED
@@ -619,6 +619,7 @@ export interface PlatformaticNextJsConfig {
619
619
  };
620
620
  };
621
621
  next?: {
622
+ standalone?: boolean;
622
623
  trailingSlash?: boolean;
623
624
  useExperimentalAdapter?: boolean;
624
625
  };
package/lib/capability.js CHANGED
@@ -13,6 +13,7 @@ import { ChildProcess } from 'node:child_process'
13
13
  import { once } from 'node:events'
14
14
  import { existsSync } from 'node:fs'
15
15
  import { glob, readFile, writeFile } from 'node:fs/promises'
16
+ import { createRequire } from 'node:module'
16
17
  import { dirname, resolve as resolvePath, sep } from 'node:path'
17
18
  import { fileURLToPath } from 'node:url'
18
19
  import { parse, satisfies } from 'semver'
@@ -32,6 +33,7 @@ export class NextCapability extends BaseCapability {
32
33
  #child
33
34
  #server
34
35
  #configModified
36
+ #standaloneEntrypoint
35
37
 
36
38
  constructor (root, config, context) {
37
39
  super('next', version, root, config, context)
@@ -39,7 +41,7 @@ export class NextCapability extends BaseCapability {
39
41
  this.exitOnUnhandledErrors = false
40
42
  }
41
43
 
42
- async init () {
44
+ async init (building = false) {
43
45
  await super.init()
44
46
 
45
47
  // This is needed to avoid Next.js to throw an error when the lockfile is not correct
@@ -50,7 +52,13 @@ export class NextCapability extends BaseCapability {
50
52
  // PS by Paolo: Sob.
51
53
  process.env.NEXT_IGNORE_INCORRECT_LOCKFILE = 'true'
52
54
 
53
- this.#next = resolvePath(dirname(await resolvePackageViaCJS(this.root, 'next')), '../..')
55
+ if (!building && this.isProduction && this.config.next?.standalone) {
56
+ this.#standaloneEntrypoint = await this.#resolveStandaloneEntrypoint()
57
+ this.#next = resolvePath(dirname(await resolvePackageViaCJS(this.#standaloneEntrypoint, 'next')), '../..')
58
+ } else {
59
+ this.#next = resolvePath(dirname(await resolvePackageViaCJS(this.root, 'next')), '../..')
60
+ }
61
+
54
62
  const nextPackage = JSON.parse(await readFile(resolvePath(this.#next, 'package.json'), 'utf-8'))
55
63
  this.#nextVersion = parse(nextPackage.version)
56
64
 
@@ -62,6 +70,10 @@ export class NextCapability extends BaseCapability {
62
70
  this.config.next.useExperimentalAdapter = false
63
71
  }
64
72
 
73
+ if (this.isProduction) {
74
+ return
75
+ }
76
+
65
77
  /* c8 ignore next 3 */
66
78
  if (!supportedVersions.some(v => satisfies(nextPackage.version, v))) {
67
79
  throw new basicErrors.UnsupportedVersion('next', nextPackage.version, supportedVersions)
@@ -123,10 +135,6 @@ export class NextCapability extends BaseCapability {
123
135
  }
124
136
 
125
137
  async build () {
126
- if (!this.#nextVersion) {
127
- await this.init()
128
- }
129
-
130
138
  const config = this.config
131
139
  const loader = new URL('./loader.js', import.meta.url)
132
140
  this.#basePath = config.application?.basePath ? cleanBasePath(config.application?.basePath) : ''
@@ -134,7 +142,7 @@ export class NextCapability extends BaseCapability {
134
142
  let command = config.application.commands.build
135
143
 
136
144
  if (!command) {
137
- await this.init()
145
+ await this.init(true)
138
146
  command = ['node', resolvePath(this.#next, './dist/bin/next'), 'build', this.root]
139
147
  }
140
148
 
@@ -253,11 +261,10 @@ export class NextCapability extends BaseCapability {
253
261
  this.#getChildManagerScripts()
254
262
  )
255
263
 
256
- this.verifyOutputDirectory(resolvePath(this.root, '.next'))
257
-
258
- if (existsSync(resolvePath(this.root, '.next/standalone'))) {
264
+ if (this.#standaloneEntrypoint) {
259
265
  return this.#startProductionStandaloneNext()
260
266
  } else {
267
+ this.verifyOutputDirectory(resolvePath(this.root, '.next'))
261
268
  return this.#startProductionNext()
262
269
  }
263
270
  }
@@ -300,27 +307,6 @@ export class NextCapability extends BaseCapability {
300
307
  }
301
308
 
302
309
  async #startProductionStandaloneNext () {
303
- const rootDir = resolvePath(this.root, '.next', 'standalone')
304
-
305
- // If built in standalone mode, the generated standalone directory is not on the root of the project but somewhere
306
- // inside .next/standalone due to turbopack limitations in determining the root of the project.
307
- // In that case we search a server.js next to a .next folder inside the .next /standalone folder.
308
- const serverEntrypoints = await Array.fromAsync(
309
- glob(['**/server.js'], { cwd: rootDir, ignore: ['node_modules', '**/node_modules/**'] })
310
- )
311
-
312
- let serverEntrypoint
313
- for (const entrypoint of serverEntrypoints) {
314
- if (existsSync(resolvePath(rootDir, dirname(entrypoint), '.next'))) {
315
- serverEntrypoint = resolvePath(rootDir, entrypoint)
316
- break
317
- }
318
- }
319
-
320
- if (!serverEntrypoint) {
321
- throw new errors.StandaloneServerNotFound()
322
- }
323
-
324
310
  // The default Next.js standalone server uses chdir, which is not supported in worker threads.
325
311
  // Therefore we need to reproduce the server.js logic here, which what we do in the rest of this method.
326
312
 
@@ -328,7 +314,7 @@ export class NextCapability extends BaseCapability {
328
314
  // For now we use simple regex parsing, if it breaks, we can switch to proper AST parsing.
329
315
  let nextConfig
330
316
  try {
331
- const serverJsContent = await readFile(serverEntrypoint, 'utf-8')
317
+ const serverJsContent = await readFile(this.#standaloneEntrypoint, 'utf-8')
332
318
  const nextConfigMatch = serverJsContent.match(/(?:const|let)\s*nextConfig\s*=\s*(\{.+)/)
333
319
  nextConfig = JSON.parse(nextConfigMatch[1])
334
320
  } catch (e) {
@@ -370,10 +356,10 @@ export class NextCapability extends BaseCapability {
370
356
 
371
357
  // This is needed by Next.js standalone server to pick up the correct configuration
372
358
  process.env.__NEXT_PRIVATE_STANDALONE_CONFIG = JSON.stringify(nextConfig)
373
- const { startServer } = await importFile(resolvePath(this.#next, './dist/server/lib/start-server.js'))
359
+ const { startServer } = this.#requireStandaloneEntrypoint(this.#standaloneEntrypoint)
374
360
 
375
361
  await startServer({
376
- dir: dirname(serverEntrypoint),
362
+ dir: dirname(this.#standaloneEntrypoint),
377
363
  isDev: false,
378
364
  config: nextConfig,
379
365
  hostname: serverOptions.hostname,
@@ -450,6 +436,47 @@ export class NextCapability extends BaseCapability {
450
436
  await writeFile(requiredServerFilesPath, JSON.stringify(requiredServerFiles, null, 2))
451
437
  }
452
438
  }
439
+
453
440
  return distDir
454
441
  }
442
+
443
+ async #resolveStandaloneEntrypoint () {
444
+ // If built in standalone mode, the generated standalone directory is not on the root of the project but somewhere
445
+ // inside .next/standalone due to turbopack limitations in determining the root of the project.
446
+ // In that case we search a server.js next to a .next folder inside the .next /standalone folder.
447
+ const serverEntrypoints = await Array.fromAsync(
448
+ glob(['**/server.js'], { cwd: this.root, ignore: ['node_modules', '**/node_modules/**'] })
449
+ )
450
+
451
+ let serverEntrypoint
452
+ for (const entrypoint of serverEntrypoints) {
453
+ if (existsSync(resolvePath(this.root, dirname(entrypoint), '.next'))) {
454
+ const candidate = resolvePath(this.root, entrypoint)
455
+ const contents = await readFile(candidate, 'utf-8')
456
+
457
+ if (contents.includes('process.env.__NEXT_PRIVATE_STANDALONE_CONFIG =')) {
458
+ serverEntrypoint = candidate
459
+ break
460
+ }
461
+ }
462
+ }
463
+
464
+ if (!serverEntrypoint) {
465
+ throw new errors.StandaloneServerNotFound()
466
+ }
467
+
468
+ return serverEntrypoint
469
+ }
470
+
471
+ #requireStandaloneEntrypoint (serverEntrypoint) {
472
+ let serverModule
473
+
474
+ try {
475
+ serverModule = createRequire(serverEntrypoint)('next/dist/server/lib/start-server.js')
476
+ } catch (e) { // Fallback to bundled capability
477
+ serverModule = createRequire(import.meta.file)('next/dist/server/lib/start-server.js')
478
+ }
479
+
480
+ return serverModule.default ?? serverModule
481
+ }
455
482
  }
package/lib/schema.js CHANGED
@@ -42,6 +42,9 @@ export const cache = {
42
42
  const next = {
43
43
  type: 'object',
44
44
  properties: {
45
+ standalone: {
46
+ type: 'boolean',
47
+ },
45
48
  trailingSlash: {
46
49
  type: 'boolean',
47
50
  default: false
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/next",
3
- "version": "3.32.0",
3
+ "version": "3.33.0-alpha.2",
4
4
  "description": "Platformatic Next.js Capability",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -23,8 +23,8 @@
23
23
  "iovalkey": "^0.3.0",
24
24
  "msgpackr": "^1.11.2",
25
25
  "semver": "^7.6.3",
26
- "@platformatic/basic": "3.32.0",
27
- "@platformatic/foundation": "3.32.0"
26
+ "@platformatic/basic": "3.33.0-alpha.2",
27
+ "@platformatic/foundation": "3.33.0-alpha.2"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@fastify/reply-from": "^12.0.0",
@@ -40,8 +40,8 @@
40
40
  "next": "^16.0.0",
41
41
  "typescript": "^5.5.4",
42
42
  "ws": "^8.18.0",
43
- "@platformatic/gateway": "3.32.0",
44
- "@platformatic/service": "3.32.0"
43
+ "@platformatic/gateway": "3.33.0-alpha.2",
44
+ "@platformatic/service": "3.33.0-alpha.2"
45
45
  },
46
46
  "engines": {
47
47
  "node": ">=22.19.0"
package/schema.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://schemas.platformatic.dev/@platformatic/next/3.32.0.json",
2
+ "$id": "https://schemas.platformatic.dev/@platformatic/next/3.33.0-alpha.2.json",
3
3
  "$schema": "http://json-schema.org/draft-07/schema#",
4
4
  "title": "Platformatic Next.js Config",
5
5
  "type": "object",
@@ -2316,6 +2316,9 @@
2316
2316
  "next": {
2317
2317
  "type": "object",
2318
2318
  "properties": {
2319
+ "standalone": {
2320
+ "type": "boolean"
2321
+ },
2319
2322
  "trailingSlash": {
2320
2323
  "type": "boolean",
2321
2324
  "default": false