@platformatic/next 3.72.0 → 4.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 +795 -858
- package/index.d.ts +28 -1
- package/index.js +24 -0
- package/lib/caching/valkey-isr.js +2 -23
- package/lib/capability.js +31 -39
- package/lib/commands/pack.js +10 -1
- package/lib/config.js +6 -9
- package/lib/create-context-patch.js +10 -6
- package/lib/factory.js +7 -0
- package/lib/image-optimizer.js +29 -35
- package/lib/pack.js +6 -51
- package/lib/schema.js +14 -2
- package/package.json +18 -10
- package/schema.js +7 -0
- package/schema.json +186 -393
- package/.gitignore +0 -2
- package/.npmignore +0 -2
package/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BaseCapability, BaseContext, BaseOptions } from '@platformatic/basic'
|
|
1
|
+
import { ApplicationDefinition, BaseCapability, BaseContext, BaseOptions, CapabilityFactoryOptions, ConfigContext, DeferredApplicationDefinition } from '@platformatic/basic'
|
|
2
2
|
import { Configuration, ConfigurationOptions } from '@platformatic/foundation'
|
|
3
3
|
import { JSONSchemaType } from 'ajv'
|
|
4
4
|
import { FastifyError } from 'fastify'
|
|
@@ -6,6 +6,26 @@ import type { PlatformaticNextJsConfig } from './config.d.ts'
|
|
|
6
6
|
|
|
7
7
|
export type { PlatformaticNextJsConfig } from './config.d.ts'
|
|
8
8
|
|
|
9
|
+
/*
|
|
10
|
+
The option types this capability's configuration is built from, generated from the audited schema.
|
|
11
|
+
`NextHttpsOptions` is Next's own and sits next to `AppServerOptions['https']`, which is
|
|
12
|
+
`HttpsOptions`: two adjacent keys spelled almost alike, which is exactly why both have names.
|
|
13
|
+
*/
|
|
14
|
+
export type {
|
|
15
|
+
AppLoggerOptions,
|
|
16
|
+
AppServerOptions,
|
|
17
|
+
ApplicationHealthOptions,
|
|
18
|
+
ApplicationTelemetryOverrides,
|
|
19
|
+
ApplicationWorkersOptions,
|
|
20
|
+
BuildableApplicationOptions,
|
|
21
|
+
HttpsOptions,
|
|
22
|
+
ImageOptimizerOptions,
|
|
23
|
+
NextCacheOptions,
|
|
24
|
+
NextHttpsOptions,
|
|
25
|
+
PermissionsOptions,
|
|
26
|
+
WatchOptions
|
|
27
|
+
} from './config.d.ts'
|
|
28
|
+
|
|
9
29
|
export interface NextContext extends BaseContext {}
|
|
10
30
|
|
|
11
31
|
export type NextConfiguration = Configuration<PlatformaticNextJsConfig>
|
|
@@ -55,3 +75,10 @@ export namespace errors {
|
|
|
55
75
|
export const StandaloneServerNotFound: () => FastifyError
|
|
56
76
|
export const CannotParseStandaloneServer: () => FastifyError
|
|
57
77
|
}
|
|
78
|
+
|
|
79
|
+
export type NextConfigOptions = CapabilityFactoryOptions<PlatformaticNextJsConfig, 'next', never>
|
|
80
|
+
|
|
81
|
+
export declare function createNextConfig (options?: NextConfigOptions): ApplicationDefinition
|
|
82
|
+
export declare function createNextConfig (
|
|
83
|
+
callback: (context: ConfigContext) => NextConfigOptions | Promise<NextConfigOptions>
|
|
84
|
+
): DeferredApplicationDefinition
|
package/index.js
CHANGED
|
@@ -57,6 +57,25 @@ function enhanceNextCacheConfig (nextConfig, modifications) {
|
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
function externalizeGlobals (nextConfig, nextVersion) {
|
|
61
|
+
const packageName = '@platformatic/globals'
|
|
62
|
+
|
|
63
|
+
if (nextVersion.major >= 15) {
|
|
64
|
+
nextConfig.serverExternalPackages ??= []
|
|
65
|
+
|
|
66
|
+
if (!nextConfig.serverExternalPackages.includes(packageName)) {
|
|
67
|
+
nextConfig.serverExternalPackages.push(packageName)
|
|
68
|
+
}
|
|
69
|
+
} else {
|
|
70
|
+
nextConfig.experimental ??= {}
|
|
71
|
+
nextConfig.experimental.serverComponentsExternalPackages ??= []
|
|
72
|
+
|
|
73
|
+
if (!nextConfig.experimental.serverComponentsExternalPackages.includes(packageName)) {
|
|
74
|
+
nextConfig.experimental.serverComponentsExternalPackages.push(packageName)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
60
79
|
export async function enhanceNextConfig (nextConfig, ...args) {
|
|
61
80
|
// This is to avoid https://github.com/vercel/next.js/issues/76981
|
|
62
81
|
Headers.prototype[Symbol.for('nodejs.util.inspect.custom')] = undefined
|
|
@@ -67,8 +86,11 @@ export async function enhanceNextConfig (nextConfig, ...args) {
|
|
|
67
86
|
|
|
68
87
|
const basePath = getBasePath()
|
|
69
88
|
const config = getConfig()
|
|
89
|
+
const nextVersion = getNextVersion()
|
|
70
90
|
const notifyConfig = getNotifyConfig()
|
|
71
91
|
|
|
92
|
+
externalizeGlobals(nextConfig, nextVersion)
|
|
93
|
+
|
|
72
94
|
if (typeof nextConfig.basePath === 'undefined') {
|
|
73
95
|
nextConfig.basePath = basePath
|
|
74
96
|
}
|
|
@@ -109,3 +131,5 @@ export * from './lib/config.js'
|
|
|
109
131
|
export * as errors from './lib/errors.js'
|
|
110
132
|
export * from './lib/image-optimizer.js'
|
|
111
133
|
export { packageJson, schema, schemaComponents, version } from './lib/schema.js'
|
|
134
|
+
|
|
135
|
+
export * from './lib/factory.js'
|
|
@@ -16,8 +16,6 @@ export const CACHE_HIT_METRIC = { name: 'next_cache_valkey_hit_count', help: 'Ne
|
|
|
16
16
|
export const CACHE_MISS_METRIC = { name: 'next_cache_valkey_miss_count', help: 'Next.js Cache (Valkey) Miss Count' }
|
|
17
17
|
export const MAX_BATCH_SIZE = 100
|
|
18
18
|
|
|
19
|
-
const NEXT_CACHE_TAGS_HEADER = 'x-next-cache-tags'
|
|
20
|
-
|
|
21
19
|
export const sections = {
|
|
22
20
|
values: 'values',
|
|
23
21
|
tags: 'tags'
|
|
@@ -101,7 +99,7 @@ export class CacheHandler {
|
|
|
101
99
|
} catch (e) {
|
|
102
100
|
this.#cacheMissMetric?.inc()
|
|
103
101
|
this.#logger.error({ err: ensureLoggableError(e) }, 'Cannot read cache value from Valkey')
|
|
104
|
-
|
|
102
|
+
throw new Error('Cannot read cache value from Valkey', { cause: e })
|
|
105
103
|
}
|
|
106
104
|
|
|
107
105
|
let value
|
|
@@ -134,7 +132,7 @@ export class CacheHandler {
|
|
|
134
132
|
}
|
|
135
133
|
|
|
136
134
|
async set (cacheKey, value, ctx, isRedisKey) {
|
|
137
|
-
const tags =
|
|
135
|
+
const tags = ctx.tags
|
|
138
136
|
const revalidate = ctx.revalidate ?? ctx.cacheControl?.revalidate ?? value.revalidate ?? 0
|
|
139
137
|
|
|
140
138
|
this.#logger.trace({ key: cacheKey, value, tags, revalidate }, 'cache set')
|
|
@@ -269,25 +267,6 @@ export class CacheHandler {
|
|
|
269
267
|
}
|
|
270
268
|
}
|
|
271
269
|
|
|
272
|
-
#getTags (tags, value) {
|
|
273
|
-
// Next.js stores page and route tags in this header instead of ctx.tags.
|
|
274
|
-
const tagsHeader = value?.headers?.[NEXT_CACHE_TAGS_HEADER]
|
|
275
|
-
|
|
276
|
-
if (typeof tagsHeader !== 'string') {
|
|
277
|
-
return tags
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
const combinedTags = new Set(tags)
|
|
281
|
-
|
|
282
|
-
for (const tag of tagsHeader.split(',')) {
|
|
283
|
-
if (tag) {
|
|
284
|
-
combinedTags.add(tag)
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
return [...combinedTags]
|
|
289
|
-
}
|
|
290
|
-
|
|
291
270
|
async #refreshKey (key, value) {
|
|
292
271
|
const life = Math.round((Date.now() - value.lastModified) / 1000)
|
|
293
272
|
const expire = Math.min(value.revalidate - life, this.#maxTTL)
|
package/lib/capability.js
CHANGED
|
@@ -75,13 +75,8 @@ export class NextCapability extends BaseCapability {
|
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
async
|
|
79
|
-
|
|
80
|
-
if (this.url) {
|
|
81
|
-
return this.url
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
await super._start({ listen })
|
|
78
|
+
async _start () {
|
|
79
|
+
await super._start()
|
|
85
80
|
|
|
86
81
|
this.on('config', config => {
|
|
87
82
|
this.#configModified = true
|
|
@@ -89,9 +84,9 @@ export class NextCapability extends BaseCapability {
|
|
|
89
84
|
})
|
|
90
85
|
|
|
91
86
|
if (this.isProduction) {
|
|
92
|
-
await this.#startProduction(
|
|
87
|
+
await this.#startProduction()
|
|
93
88
|
} else {
|
|
94
|
-
await this.#startDevelopment(
|
|
89
|
+
await this.#startDevelopment()
|
|
95
90
|
}
|
|
96
91
|
|
|
97
92
|
await this._collectMetrics()
|
|
@@ -109,8 +104,8 @@ export class NextCapability extends BaseCapability {
|
|
|
109
104
|
}
|
|
110
105
|
}
|
|
111
106
|
|
|
112
|
-
async
|
|
113
|
-
await super.
|
|
107
|
+
async _stop () {
|
|
108
|
+
await super._stop()
|
|
114
109
|
|
|
115
110
|
if (this.subprocess) {
|
|
116
111
|
return this.stopCommand()
|
|
@@ -179,14 +174,12 @@ export class NextCapability extends BaseCapability {
|
|
|
179
174
|
}
|
|
180
175
|
|
|
181
176
|
getMeta () {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
return { gateway }
|
|
177
|
+
return super.getMeta({
|
|
178
|
+
includeConnection: true,
|
|
179
|
+
prefix: this.basePath ?? this.#basePath,
|
|
180
|
+
wantsAbsoluteUrls: true,
|
|
181
|
+
needsRootTrailingSlash: false
|
|
182
|
+
})
|
|
190
183
|
}
|
|
191
184
|
|
|
192
185
|
async getChildManagerContext (basePath) {
|
|
@@ -211,7 +204,11 @@ export class NextCapability extends BaseCapability {
|
|
|
211
204
|
return this.startWithCommand(command, loaderUrl, this.#getChildManagerScripts())
|
|
212
205
|
}
|
|
213
206
|
|
|
214
|
-
|
|
207
|
+
if (typeof this.serverConfig?.port === 'undefined') {
|
|
208
|
+
return
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const { hostname, port } = this.serverConfig
|
|
215
212
|
const serverOptions = {
|
|
216
213
|
host: hostname || '127.0.0.1',
|
|
217
214
|
port: port || 0
|
|
@@ -317,13 +314,13 @@ export class NextCapability extends BaseCapability {
|
|
|
317
314
|
|
|
318
315
|
if (command) {
|
|
319
316
|
const childManagerScripts = this.#getChildManagerScripts()
|
|
320
|
-
|
|
321
|
-
if (this.#nextVersion.major < 15 || (this.#nextVersion.major <= 15 && this.#nextVersion.minor < 1)) {
|
|
322
|
-
childManagerScripts.push(new URL('./create-context-patch.js', import.meta.url))
|
|
323
|
-
}
|
|
324
317
|
return this.startWithCommand(command, loaderUrl, childManagerScripts)
|
|
325
318
|
}
|
|
326
319
|
|
|
320
|
+
if (typeof this.serverConfig?.port === 'undefined') {
|
|
321
|
+
return
|
|
322
|
+
}
|
|
323
|
+
|
|
327
324
|
this.childManager = this.#createChildManager(
|
|
328
325
|
loaderUrl,
|
|
329
326
|
await this.getChildManagerContext(this.#basePath),
|
|
@@ -342,25 +339,20 @@ export class NextCapability extends BaseCapability {
|
|
|
342
339
|
try {
|
|
343
340
|
updateGlobals({ config: this.config })
|
|
344
341
|
await this.childManager.inject()
|
|
342
|
+
await this.childManager.register()
|
|
345
343
|
const { nextStart } = await importFile(resolvePath(this.#next, './dist/cli/next-start.js'))
|
|
346
344
|
|
|
347
|
-
const { hostname, port
|
|
345
|
+
const { hostname, port } = this.serverConfig ?? {}
|
|
348
346
|
const serverOptions = {
|
|
349
347
|
hostname: hostname || '127.0.0.1',
|
|
350
348
|
port: port || 0
|
|
351
349
|
}
|
|
352
350
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
const serverPromise = createServerListener(
|
|
356
|
-
(this.isEntrypoint ? serverOptions?.port : undefined) ?? true,
|
|
357
|
-
(this.isEntrypoint ? serverOptions?.hostname : undefined) ?? true,
|
|
358
|
-
typeof backlog === 'number' ? { backlog } : {}
|
|
359
|
-
)
|
|
351
|
+
const serverPromise = createServerListener()
|
|
360
352
|
|
|
361
353
|
if (this.#nextVersion.major === 14 && this.#nextVersion.minor < 2) {
|
|
362
354
|
await nextStart({
|
|
363
|
-
'--hostname': serverOptions.
|
|
355
|
+
'--hostname': serverOptions.hostname,
|
|
364
356
|
'--port': serverOptions.port,
|
|
365
357
|
_: [this.root]
|
|
366
358
|
})
|
|
@@ -403,17 +395,13 @@ export class NextCapability extends BaseCapability {
|
|
|
403
395
|
await this.childManager.inject()
|
|
404
396
|
await this.childManager.register()
|
|
405
397
|
|
|
406
|
-
const { hostname, port
|
|
398
|
+
const { hostname, port } = this.serverConfig ?? {}
|
|
407
399
|
const serverOptions = {
|
|
408
400
|
hostname: hostname || '127.0.0.1',
|
|
409
401
|
port: port || 0
|
|
410
402
|
}
|
|
411
403
|
|
|
412
|
-
const serverPromise = createServerListener(
|
|
413
|
-
(this.isEntrypoint ? serverOptions?.port : undefined) ?? true,
|
|
414
|
-
(this.isEntrypoint ? serverOptions?.hostname : undefined) ?? true,
|
|
415
|
-
typeof backlog === 'number' ? { backlog } : {}
|
|
416
|
-
)
|
|
404
|
+
const serverPromise = createServerListener()
|
|
417
405
|
|
|
418
406
|
let keepAliveTimeout = parseInt(process.env.KEEP_ALIVE_TIMEOUT, 10)
|
|
419
407
|
if (Number.isNaN(keepAliveTimeout) || !Number.isFinite(keepAliveTimeout) || keepAliveTimeout < 0) {
|
|
@@ -448,6 +436,10 @@ export class NextCapability extends BaseCapability {
|
|
|
448
436
|
scripts.push(new URL('./loader-next-15.cjs', import.meta.url))
|
|
449
437
|
}
|
|
450
438
|
|
|
439
|
+
if (this.#nextVersion.major < 15 || (this.#nextVersion.major === 15 && this.#nextVersion.minor < 1)) {
|
|
440
|
+
scripts.push(new URL('./create-context-patch.js', import.meta.url))
|
|
441
|
+
}
|
|
442
|
+
|
|
451
443
|
return scripts
|
|
452
444
|
}
|
|
453
445
|
|
package/lib/commands/pack.js
CHANGED
|
@@ -37,7 +37,16 @@ export async function packCommand (logger, configFile, args, context) {
|
|
|
37
37
|
|
|
38
38
|
let config
|
|
39
39
|
try {
|
|
40
|
-
|
|
40
|
+
/*
|
|
41
|
+
A command receives the application's already-resolved configuration as data -- the loader
|
|
42
|
+
evaluated it once, main-side, and validated it against this capability's schema -- so
|
|
43
|
+
`resolved` says the reading is done. A path is still accepted, which is what a caller outside
|
|
44
|
+
a running project supplies, and it is loaded the way a boot loads one.
|
|
45
|
+
*/
|
|
46
|
+
config =
|
|
47
|
+
typeof configFile === 'string'
|
|
48
|
+
? await loadConfiguration(configFile)
|
|
49
|
+
: await loadConfiguration(context.application?.path ?? process.cwd(), configFile, { resolved: true })
|
|
41
50
|
} catch (error) {
|
|
42
51
|
return logFatalError(
|
|
43
52
|
logger,
|
package/lib/config.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { transform as basicTransform,
|
|
2
|
-
import { loadConfiguration as utilsLoadConfiguration } from '@platformatic/foundation'
|
|
1
|
+
import { transform as basicTransform, loadCapabilityConfiguration, validationOptions } from '@platformatic/basic'
|
|
3
2
|
import { schema } from './schema.js'
|
|
4
3
|
|
|
5
4
|
export async function transform (config, schema, options) {
|
|
@@ -14,13 +13,11 @@ export async function transform (config, schema, options) {
|
|
|
14
13
|
}
|
|
15
14
|
|
|
16
15
|
export async function loadConfiguration (configOrRoot, sourceOrConfig, context) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
return loadCapabilityConfiguration(configOrRoot, sourceOrConfig, context, {
|
|
17
|
+
schema,
|
|
18
|
+
scope: import.meta.filename,
|
|
19
|
+
suffixes: 'application',
|
|
20
20
|
validationOptions,
|
|
21
|
-
transform
|
|
22
|
-
replaceEnv: true,
|
|
23
|
-
root,
|
|
24
|
-
...context
|
|
21
|
+
transform
|
|
25
22
|
})
|
|
26
23
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
import { createRequire } from 'node:module'
|
|
4
|
-
import { getGlobalDispatcher } from 'undici'
|
|
4
|
+
import { fetch, getGlobalDispatcher } from 'undici'
|
|
5
5
|
|
|
6
6
|
// Next.js runs middlewares in it's own patched vm context. So the global dispatcher in
|
|
7
7
|
// the middleware context is different from an application global dispatcher. This
|
|
@@ -16,12 +16,16 @@ export function patchVmCreateContext () {
|
|
|
16
16
|
const globalDispatcher = getGlobalDispatcher()
|
|
17
17
|
const context = originalCreateContext(contextObject, opts)
|
|
18
18
|
queueMicrotask(() => {
|
|
19
|
-
if (
|
|
19
|
+
if (context.fetch === undefined) return
|
|
20
20
|
|
|
21
|
-
const originalFetch =
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
const originalFetch = context.fetch
|
|
22
|
+
context.fetch = (input, init = {}) => {
|
|
23
|
+
const url = typeof input === 'string' ? input : input?.url
|
|
24
|
+
if (!url?.startsWith('http://') && !url?.startsWith('https://')) {
|
|
25
|
+
return originalFetch(input, init)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return fetch(input, { ...init, dispatcher: globalDispatcher })
|
|
25
29
|
}
|
|
26
30
|
})
|
|
27
31
|
return context
|
package/lib/factory.js
ADDED
package/lib/image-optimizer.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
BaseCapability,
|
|
3
3
|
buildListenOptions,
|
|
4
|
-
errors as basicErrors,
|
|
5
4
|
createServerListener,
|
|
5
|
+
errors as basicErrors,
|
|
6
6
|
getServerUrl,
|
|
7
7
|
importFile,
|
|
8
|
-
resolvePackageViaCJS
|
|
8
|
+
resolvePackageViaCJS, cleanBasePath
|
|
9
9
|
} from '@platformatic/basic'
|
|
10
|
-
import {
|
|
11
|
-
import { ensureLoggableError } from '@platformatic/foundation/lib/errors.js'
|
|
10
|
+
import { ensureLoggableError } from '@platformatic/foundation'
|
|
12
11
|
import { getEvents } from '@platformatic/globals'
|
|
13
12
|
import { createQueue } from '@platformatic/image-optimizer'
|
|
14
13
|
import { FileStorage, MemoryStorage, RedisStorage } from '@platformatic/job-queue'
|
|
@@ -72,37 +71,10 @@ export class NextImageOptimizerCapability extends BaseCapability {
|
|
|
72
71
|
this.#queue = await this.#createQueue(imageOptimizerConfig)
|
|
73
72
|
}
|
|
74
73
|
|
|
75
|
-
async
|
|
76
|
-
// Make this idempotent
|
|
77
|
-
if (this.url) {
|
|
78
|
-
return this.url
|
|
79
|
-
}
|
|
80
|
-
|
|
74
|
+
async _start () {
|
|
81
75
|
const config = this.config
|
|
82
76
|
this.#basePath = config.application?.basePath ? cleanBasePath(config.application?.basePath) : ''
|
|
83
|
-
await super._start(
|
|
84
|
-
|
|
85
|
-
if (this.#app && listen) {
|
|
86
|
-
const serverOptions = this.serverConfig
|
|
87
|
-
const listenOptions = buildListenOptions(serverOptions)
|
|
88
|
-
|
|
89
|
-
if (typeof serverOptions?.backlog === 'number') {
|
|
90
|
-
createServerListener(false, false, { backlog: serverOptions.backlog })
|
|
91
|
-
listenOptions.backlog = serverOptions.backlog
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
this.#server = await new Promise((resolve, reject) => {
|
|
95
|
-
return this.#app
|
|
96
|
-
.listen(listenOptions, function () {
|
|
97
|
-
resolve(this)
|
|
98
|
-
})
|
|
99
|
-
.on('error', reject)
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
this.url = getServerUrl(this.#server)
|
|
103
|
-
|
|
104
|
-
return this.url
|
|
105
|
-
}
|
|
77
|
+
await super._start()
|
|
106
78
|
|
|
107
79
|
const { ImageOptimizerCache } = await importFile(resolvePath(this.#next, './dist/server/image-optimizer.js'))
|
|
108
80
|
this.#validateParams = ImageOptimizerCache.validateParams.bind(ImageOptimizerCache)
|
|
@@ -118,10 +90,32 @@ export class NextImageOptimizerCapability extends BaseCapability {
|
|
|
118
90
|
this.#app = createServer(this.#handleRequest.bind(this))
|
|
119
91
|
this.#dispatcher = this.#app.listeners('request')[0]
|
|
120
92
|
await this.#queue.start()
|
|
93
|
+
|
|
94
|
+
if (typeof this.serverConfig?.port === 'undefined') {
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const serverOptions = this.serverConfig
|
|
99
|
+
const listenOptions = buildListenOptions(serverOptions)
|
|
100
|
+
const serverPromise = createServerListener()
|
|
101
|
+
|
|
102
|
+
const [server] = await Promise.all([
|
|
103
|
+
new Promise((resolve, reject) => {
|
|
104
|
+
return this.#app
|
|
105
|
+
.listen(listenOptions, function () {
|
|
106
|
+
resolve(this)
|
|
107
|
+
})
|
|
108
|
+
.on('error', reject)
|
|
109
|
+
}),
|
|
110
|
+
serverPromise
|
|
111
|
+
])
|
|
112
|
+
this.#server = server
|
|
113
|
+
|
|
114
|
+
this.url = getServerUrl(this.#server)
|
|
121
115
|
}
|
|
122
116
|
|
|
123
|
-
async
|
|
124
|
-
await super.
|
|
117
|
+
async _stop () {
|
|
118
|
+
await super._stop()
|
|
125
119
|
await this.#queue?.stop()
|
|
126
120
|
|
|
127
121
|
const events = getEvents()
|
package/lib/pack.js
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
symlink,
|
|
16
16
|
writeFile
|
|
17
17
|
} from 'node:fs/promises'
|
|
18
|
-
import {
|
|
18
|
+
import { findPackageJSON } from 'node:module'
|
|
19
19
|
import { tmpdir } from 'node:os'
|
|
20
20
|
import { dirname, isAbsolute, relative, resolve as resolvePath } from 'node:path'
|
|
21
21
|
import { version as platformaticVersion } from './schema.js'
|
|
@@ -169,60 +169,15 @@ async function loadPackageFromRoot (root, fallbackName) {
|
|
|
169
169
|
}
|
|
170
170
|
|
|
171
171
|
async function resolvePackageRoot (name, from) {
|
|
172
|
-
const require = createRequire(from)
|
|
173
|
-
|
|
174
172
|
try {
|
|
175
|
-
|
|
176
|
-
return dirname(packageJsonPath)
|
|
173
|
+
return dirname(findPackageJSON(name, from))
|
|
177
174
|
} catch (error) {
|
|
178
|
-
const
|
|
179
|
-
if (
|
|
180
|
-
return
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
try {
|
|
184
|
-
const resolvedEntry = require.resolve(name)
|
|
185
|
-
return await findPackageRoot(dirname(resolvedEntry))
|
|
186
|
-
} catch (resolveError) {
|
|
187
|
-
const workspacePackage = resolveWorkspacePackageRoot(name)
|
|
188
|
-
if (workspacePackage) {
|
|
189
|
-
return workspacePackage
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
throw resolveError
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
function resolvePackageJsonFromSearchPaths (require, name) {
|
|
198
|
-
const searchPaths = require.resolve.paths(name) ?? []
|
|
199
|
-
const packageSegments = name.split('/')
|
|
200
|
-
|
|
201
|
-
for (const searchPath of searchPaths) {
|
|
202
|
-
const candidate = resolvePath(searchPath, ...packageSegments, 'package.json')
|
|
203
|
-
if (existsSync(candidate)) {
|
|
204
|
-
return candidate
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
return null
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
async function findPackageRoot (directory) {
|
|
212
|
-
let current = directory
|
|
213
|
-
|
|
214
|
-
while (true) {
|
|
215
|
-
const candidate = resolvePath(current, 'package.json')
|
|
216
|
-
if (existsSync(candidate)) {
|
|
217
|
-
return current
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
const parent = resolvePath(current, '..')
|
|
221
|
-
if (parent === current) {
|
|
222
|
-
throw new Error(`Cannot determine package root for ${directory}.`)
|
|
175
|
+
const workspacePackage = resolveWorkspacePackageRoot(name)
|
|
176
|
+
if (workspacePackage) {
|
|
177
|
+
return workspacePackage
|
|
223
178
|
}
|
|
224
179
|
|
|
225
|
-
|
|
180
|
+
throw error
|
|
226
181
|
}
|
|
227
182
|
}
|
|
228
183
|
|
package/lib/schema.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import { schemaComponents as basicSchemaComponents } from '@platformatic/basic'
|
|
2
|
-
import { schemaComponents as utilsSchemaComponents } from '@platformatic/foundation'
|
|
1
|
+
import { schemaComponents as basicSchemaComponents } from '@platformatic/basic/schema'
|
|
2
|
+
import { schemaComponents as utilsSchemaComponents } from '@platformatic/foundation/schema'
|
|
3
3
|
import { readFileSync } from 'node:fs'
|
|
4
4
|
import { resolve } from 'node:path'
|
|
5
5
|
|
|
6
6
|
export const packageJson = JSON.parse(readFileSync(resolve(import.meta.dirname, '../package.json'), 'utf8'))
|
|
7
7
|
export const version = packageJson.version
|
|
8
8
|
|
|
9
|
+
// The port is checked before any startup path is selected, so no port means no start.
|
|
10
|
+
export const servesWithoutPort = { development: false, production: false }
|
|
11
|
+
|
|
9
12
|
export const cache = {
|
|
10
13
|
type: 'object',
|
|
11
14
|
properties: {
|
|
@@ -240,6 +243,15 @@ const next = {
|
|
|
240
243
|
additionalProperties: false
|
|
241
244
|
}
|
|
242
245
|
|
|
246
|
+
/*
|
|
247
|
+
The names the generated types carry -- see the table in `foundation/lib/schema.js` for why they
|
|
248
|
+
are assigned rather than written at each site. `https` is Next's own, distinct from `server.https`
|
|
249
|
+
next to it, and the two names are what keeps that adjacency legible.
|
|
250
|
+
*/
|
|
251
|
+
next.properties.https.title = 'NextHttpsOptions'
|
|
252
|
+
next.properties.imageOptimizer.title = 'ImageOptimizerOptions'
|
|
253
|
+
cache.title = 'NextCacheOptions'
|
|
254
|
+
|
|
243
255
|
export const schemaComponents = { next }
|
|
244
256
|
|
|
245
257
|
export const schema = {
|
package/package.json
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/next",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0-alpha.2",
|
|
4
4
|
"description": "Platformatic Next.js Capability",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./index.js",
|
|
9
|
+
"./schema": "./schema.js",
|
|
10
|
+
"./package.json": "./package.json",
|
|
11
|
+
"./*": "./*"
|
|
12
|
+
},
|
|
7
13
|
"repository": {
|
|
8
14
|
"type": "git",
|
|
9
15
|
"url": "git+https://github.com/platformatic/platformatic.git"
|
|
@@ -19,21 +25,19 @@
|
|
|
19
25
|
"@babel/parser": "^7.25.3",
|
|
20
26
|
"@babel/traverse": "^7.25.3",
|
|
21
27
|
"@babel/types": "^7.25.2",
|
|
22
|
-
"@platformatic/basic": "3.72.0",
|
|
23
|
-
"@platformatic/foundation": "3.72.0",
|
|
24
|
-
"@platformatic/globals": "3.72.0",
|
|
25
28
|
"@platformatic/image-optimizer": "^0.2.1",
|
|
26
29
|
"amaro": "^1.0.0",
|
|
27
30
|
"iovalkey": "^0.3.0",
|
|
28
31
|
"light-my-request": "^6.0.0",
|
|
29
32
|
"msgpackr": "^1.11.2",
|
|
30
|
-
"semver": "^7.6.3"
|
|
33
|
+
"semver": "^7.6.3",
|
|
34
|
+
"@platformatic/foundation": "4.0.0-alpha.2",
|
|
35
|
+
"@platformatic/globals": "4.0.0-alpha.2",
|
|
36
|
+
"@platformatic/basic": "4.0.0-alpha.2"
|
|
31
37
|
},
|
|
32
38
|
"devDependencies": {
|
|
33
39
|
"@fastify/reply-from": "^12.0.0",
|
|
34
|
-
"@
|
|
35
|
-
"@platformatic/service": "3.72.0",
|
|
36
|
-
"@types/node": "^22.5.0",
|
|
40
|
+
"@types/node": "^24.0.0",
|
|
37
41
|
"@types/react": "^19.1.11",
|
|
38
42
|
"@types/react-dom": "^19.1.7",
|
|
39
43
|
"cleaner-spec-reporter": "^0.5.0",
|
|
@@ -45,10 +49,12 @@
|
|
|
45
49
|
"neostandard": "^0.12.0",
|
|
46
50
|
"next": "^16.1.0",
|
|
47
51
|
"typescript": "~6.0.3",
|
|
48
|
-
"ws": "^8.18.0"
|
|
52
|
+
"ws": "^8.18.0",
|
|
53
|
+
"@platformatic/service": "4.0.0-alpha.2",
|
|
54
|
+
"@platformatic/gateway": "4.0.0-alpha.2"
|
|
49
55
|
},
|
|
50
56
|
"engines": {
|
|
51
|
-
"node": ">=
|
|
57
|
+
"node": ">=24.20.0"
|
|
52
58
|
},
|
|
53
59
|
"scripts": {
|
|
54
60
|
"test": "npm run test:types && npm run test:main && npm run test:caching && npm run test:compatibility && npm run test:integration",
|
|
@@ -56,6 +62,8 @@
|
|
|
56
62
|
"test:main": "node --test --test-reporter=cleaner-spec-reporter --test-concurrency=1 --test-timeout=2000000 test/*.test.js",
|
|
57
63
|
"test:caching": "node --test --test-reporter=cleaner-spec-reporter --test-concurrency=1 --test-timeout=2000000 test/caching/*.test.js",
|
|
58
64
|
"test:compatibility": "node --test --test-reporter=cleaner-spec-reporter --test-concurrency=1 --test-timeout=2000000 test/compatibility/*.test.js",
|
|
65
|
+
"test:compatibility-development": "PLATFORMATIC_NEXT_COMPATIBILITY_MODE=development node --test --test-reporter=cleaner-spec-reporter --test-concurrency=1 --test-timeout=2000000 test/compatibility/*.test.js",
|
|
66
|
+
"test:compatibility-production": "PLATFORMATIC_NEXT_COMPATIBILITY_MODE=production node --test --test-reporter=cleaner-spec-reporter --test-concurrency=1 --test-timeout=2000000 test/compatibility/*.test.js",
|
|
59
67
|
"test:integration": "node --test --test-reporter=cleaner-spec-reporter --test-concurrency=1 --test-timeout=2000000 test/integration/*.test.js",
|
|
60
68
|
"gen-schema": "node lib/schema.js > schema.json",
|
|
61
69
|
"gen-types": "json2ts > config.d.ts < schema.json",
|
package/schema.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// The light /schema subpath, part of the capability contract. It carries the schema and the
|
|
2
|
+
// package-level metadata main-side preparation needs, and imports nothing that would pull the
|
|
3
|
+
// capability's implementation in with it — boot resolves this rather than the package entry.
|
|
4
|
+
//
|
|
5
|
+
// Light is a statement about import cost, never about safety: this executes in the main process
|
|
6
|
+
// with full privileges, like any capability code.
|
|
7
|
+
export * from './lib/schema.js'
|