@platformatic/next 2.19.0-alpha.1 → 2.19.0-alpha.10
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/index.js +46 -10
- package/lib/caching/valkey.js +35 -16
- package/lib/loader-next-15.cjs +52 -0
- package/lib/loader.js +3 -3
- package/package.json +10 -7
- package/schema.json +1 -1
package/index.js
CHANGED
|
@@ -16,14 +16,15 @@ import { once } from 'node:events'
|
|
|
16
16
|
import { readFile } from 'node:fs/promises'
|
|
17
17
|
import { dirname, resolve as pathResolve } from 'node:path'
|
|
18
18
|
import { pathToFileURL } from 'node:url'
|
|
19
|
-
import { satisfies } from 'semver'
|
|
19
|
+
import { parse, satisfies } from 'semver'
|
|
20
20
|
import { packageJson, schema } from './lib/schema.js'
|
|
21
21
|
|
|
22
|
-
const supportedVersions = '^14.0.0'
|
|
22
|
+
const supportedVersions = ['^14.0.0', '^15.0.0']
|
|
23
23
|
|
|
24
24
|
export class NextStackable extends BaseStackable {
|
|
25
25
|
#basePath
|
|
26
26
|
#next
|
|
27
|
+
#nextVersion
|
|
27
28
|
#child
|
|
28
29
|
#server
|
|
29
30
|
|
|
@@ -34,9 +35,10 @@ export class NextStackable extends BaseStackable {
|
|
|
34
35
|
async init () {
|
|
35
36
|
this.#next = pathResolve(dirname(resolvePackage(this.root, 'next')), '../..')
|
|
36
37
|
const nextPackage = JSON.parse(await readFile(pathResolve(this.#next, 'package.json'), 'utf-8'))
|
|
38
|
+
this.#nextVersion = parse(nextPackage.version)
|
|
37
39
|
|
|
38
40
|
/* c8 ignore next 3 */
|
|
39
|
-
if (!satisfies(nextPackage.version,
|
|
41
|
+
if (!supportedVersions.some(v => satisfies(nextPackage.version, v))) {
|
|
40
42
|
throw new errors.UnsupportedVersion('next', nextPackage.version, supportedVersions)
|
|
41
43
|
}
|
|
42
44
|
}
|
|
@@ -83,6 +85,10 @@ export class NextStackable extends BaseStackable {
|
|
|
83
85
|
}
|
|
84
86
|
|
|
85
87
|
async build () {
|
|
88
|
+
if (!this.#nextVersion) {
|
|
89
|
+
await this.init()
|
|
90
|
+
}
|
|
91
|
+
|
|
86
92
|
const config = this.configManager.current
|
|
87
93
|
const loader = new URL('./lib/loader.js', import.meta.url)
|
|
88
94
|
this.#basePath = config.application?.basePath ? cleanBasePath(config.application?.basePath) : ''
|
|
@@ -94,7 +100,7 @@ export class NextStackable extends BaseStackable {
|
|
|
94
100
|
command = ['node', pathResolve(this.#next, './dist/bin/next'), 'build', this.root]
|
|
95
101
|
}
|
|
96
102
|
|
|
97
|
-
return this.buildWithCommand(command, this.#basePath, loader)
|
|
103
|
+
return this.buildWithCommand(command, this.#basePath, loader, this.#getChildManagerScripts())
|
|
98
104
|
}
|
|
99
105
|
|
|
100
106
|
/* c8 ignore next 5 */
|
|
@@ -124,7 +130,7 @@ export class NextStackable extends BaseStackable {
|
|
|
124
130
|
this.#basePath = config.application?.basePath ? cleanBasePath(config.application?.basePath) : ''
|
|
125
131
|
|
|
126
132
|
if (command) {
|
|
127
|
-
return this.startWithCommand(command, loaderUrl)
|
|
133
|
+
return this.startWithCommand(command, loaderUrl, this.#getChildManagerScripts())
|
|
128
134
|
}
|
|
129
135
|
|
|
130
136
|
const { hostname, port } = this.serverConfig ?? {}
|
|
@@ -148,7 +154,8 @@ export class NextStackable extends BaseStackable {
|
|
|
148
154
|
runtimeBasePath: this.runtimeConfig.basePath,
|
|
149
155
|
wantsAbsoluteUrls: true,
|
|
150
156
|
telemetryConfig: this.telemetryConfig
|
|
151
|
-
}
|
|
157
|
+
},
|
|
158
|
+
scripts: this.#getChildManagerScripts()
|
|
152
159
|
})
|
|
153
160
|
|
|
154
161
|
const promise = once(this.childManager, 'url')
|
|
@@ -168,7 +175,17 @@ export class NextStackable extends BaseStackable {
|
|
|
168
175
|
try {
|
|
169
176
|
await this.childManager.inject()
|
|
170
177
|
const childPromise = createChildProcessListener()
|
|
171
|
-
|
|
178
|
+
|
|
179
|
+
if (this.#nextVersion.major === 14 && this.#nextVersion.minor < 2) {
|
|
180
|
+
await nextDev({
|
|
181
|
+
'--hostname': serverOptions.host,
|
|
182
|
+
'--port': serverOptions.port,
|
|
183
|
+
_: [this.root]
|
|
184
|
+
})
|
|
185
|
+
} else {
|
|
186
|
+
await nextDev(serverOptions, 'default', this.root)
|
|
187
|
+
}
|
|
188
|
+
|
|
172
189
|
this.#child = await childPromise
|
|
173
190
|
} finally {
|
|
174
191
|
await this.childManager.eject()
|
|
@@ -183,7 +200,7 @@ export class NextStackable extends BaseStackable {
|
|
|
183
200
|
this.#basePath = config.application?.basePath ? cleanBasePath(config.application?.basePath) : ''
|
|
184
201
|
|
|
185
202
|
if (command) {
|
|
186
|
-
return this.startWithCommand(command, loaderUrl)
|
|
203
|
+
return this.startWithCommand(command, loaderUrl, this.#getChildManagerScripts())
|
|
187
204
|
}
|
|
188
205
|
|
|
189
206
|
this.childManager = new ChildManager({
|
|
@@ -200,7 +217,8 @@ export class NextStackable extends BaseStackable {
|
|
|
200
217
|
runtimeBasePath: this.runtimeConfig.basePath,
|
|
201
218
|
wantsAbsoluteUrls: true,
|
|
202
219
|
telemetryConfig: this.telemetryConfig
|
|
203
|
-
}
|
|
220
|
+
},
|
|
221
|
+
scripts: this.#getChildManagerScripts()
|
|
204
222
|
})
|
|
205
223
|
|
|
206
224
|
this.verifyOutputDirectory(pathResolve(this.root, '.next'))
|
|
@@ -230,7 +248,15 @@ export class NextStackable extends BaseStackable {
|
|
|
230
248
|
(this.isEntrypoint ? serverOptions?.hostname : undefined) ?? true
|
|
231
249
|
)
|
|
232
250
|
|
|
233
|
-
|
|
251
|
+
if (this.#nextVersion.major === 14 && this.#nextVersion.minor < 2) {
|
|
252
|
+
await nextStart({
|
|
253
|
+
'--hostname': serverOptions.host,
|
|
254
|
+
'--port': serverOptions.port,
|
|
255
|
+
_: [this.root]
|
|
256
|
+
})
|
|
257
|
+
} else {
|
|
258
|
+
await nextStart(serverOptions, this.root)
|
|
259
|
+
}
|
|
234
260
|
|
|
235
261
|
this.#server = await serverPromise
|
|
236
262
|
this.url = getServerUrl(this.#server)
|
|
@@ -238,6 +264,16 @@ export class NextStackable extends BaseStackable {
|
|
|
238
264
|
await this.childManager.eject()
|
|
239
265
|
}
|
|
240
266
|
}
|
|
267
|
+
|
|
268
|
+
#getChildManagerScripts () {
|
|
269
|
+
const scripts = []
|
|
270
|
+
|
|
271
|
+
if (this.#nextVersion.major === 15) {
|
|
272
|
+
scripts.push(new URL('./lib/loader-next-15.cjs', import.meta.url))
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return scripts
|
|
276
|
+
}
|
|
241
277
|
}
|
|
242
278
|
|
|
243
279
|
/* c8 ignore next 9 */
|
package/lib/caching/valkey.js
CHANGED
|
@@ -14,12 +14,29 @@ const sections = {
|
|
|
14
14
|
tags: 'tags'
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
const clients = new Map()
|
|
18
|
+
|
|
17
19
|
export function keyFor (prefix, subprefix, section, key) {
|
|
18
20
|
return [prefix, 'cache:next', subprefix, section, key ? Buffer.from(key).toString('base64url') : undefined]
|
|
19
21
|
.filter(c => c)
|
|
20
22
|
.join(':')
|
|
21
23
|
}
|
|
22
24
|
|
|
25
|
+
export function getConnection (url) {
|
|
26
|
+
let client = clients.get(url)
|
|
27
|
+
|
|
28
|
+
if (!client) {
|
|
29
|
+
client = new Redis(url, { enableAutoPipelining: true })
|
|
30
|
+
clients.set(url, client)
|
|
31
|
+
|
|
32
|
+
globalThis.platformatic.events.on('plt:next:close', () => {
|
|
33
|
+
client.disconnect(false)
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return client
|
|
38
|
+
}
|
|
39
|
+
|
|
23
40
|
export class CacheHandler {
|
|
24
41
|
#config
|
|
25
42
|
#logger
|
|
@@ -30,14 +47,9 @@ export class CacheHandler {
|
|
|
30
47
|
constructor () {
|
|
31
48
|
this.#logger = this.#createLogger()
|
|
32
49
|
this.#config = globalThis.platformatic.config.cache
|
|
33
|
-
this.#store =
|
|
50
|
+
this.#store = getConnection(this.#config.url)
|
|
34
51
|
this.#maxTTL = this.#config.maxTTL
|
|
35
52
|
this.#subprefix = this.#getSubprefix()
|
|
36
|
-
|
|
37
|
-
// Handle disconnection not to hang the process on exit
|
|
38
|
-
globalThis.platformatic.events.on('plt:next:close', () => {
|
|
39
|
-
this.#store.disconnect(false)
|
|
40
|
-
})
|
|
41
53
|
}
|
|
42
54
|
|
|
43
55
|
async get (cacheKey) {
|
|
@@ -93,7 +105,12 @@ export class CacheHandler {
|
|
|
93
105
|
const data = this.#serialize({ value, tags, lastModified: Date.now(), revalidate, maxTTL: this.#maxTTL })
|
|
94
106
|
const expire = Math.min(revalidate, this.#maxTTL)
|
|
95
107
|
|
|
108
|
+
if (expire < 1) {
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
|
|
96
112
|
// Enqueue all the operations to perform in Valkey
|
|
113
|
+
|
|
97
114
|
const promises = []
|
|
98
115
|
promises.push(this.#store.set(key, data, 'EX', expire))
|
|
99
116
|
|
|
@@ -155,19 +172,21 @@ export class CacheHandler {
|
|
|
155
172
|
const life = Math.round((Date.now() - value.lastModified) / 1000)
|
|
156
173
|
const expire = Math.min(value.revalidate - life, this.#maxTTL)
|
|
157
174
|
|
|
158
|
-
if (expire
|
|
159
|
-
|
|
160
|
-
|
|
175
|
+
if (expire < 1) {
|
|
176
|
+
return
|
|
177
|
+
}
|
|
161
178
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const tagsKey = this.#keyFor(tag, sections.tags)
|
|
165
|
-
promises.push(this.#store.expire(tagsKey, expire, 'gt'))
|
|
166
|
-
}
|
|
167
|
-
}
|
|
179
|
+
const promises = []
|
|
180
|
+
promises.push(this.#store.expire(key, expire, 'gt'))
|
|
168
181
|
|
|
169
|
-
|
|
182
|
+
if (Array.isArray(value.tags)) {
|
|
183
|
+
for (const tag of value.tags) {
|
|
184
|
+
const tagsKey = this.#keyFor(tag, sections.tags)
|
|
185
|
+
promises.push(this.#store.expire(tagsKey, expire, 'gt'))
|
|
186
|
+
}
|
|
170
187
|
}
|
|
188
|
+
|
|
189
|
+
await Promise.all(promises)
|
|
171
190
|
}
|
|
172
191
|
|
|
173
192
|
#createLogger () {
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const { resolve } = require('node:path')
|
|
2
|
+
const { fileURLToPath } = require('node:url')
|
|
3
|
+
const fsPromises = require('node:fs').promises
|
|
4
|
+
const { transformSync } = require('amaro')
|
|
5
|
+
const { parse } = require('@babel/parser')
|
|
6
|
+
const traverse = require('@babel/traverse')
|
|
7
|
+
|
|
8
|
+
const originalReadFile = fsPromises.readFile
|
|
9
|
+
const targetFile = resolve(fileURLToPath(globalThis.platformatic.root), 'next.config.ts')
|
|
10
|
+
|
|
11
|
+
function detectFormat (code) {
|
|
12
|
+
let format = 'esm'
|
|
13
|
+
|
|
14
|
+
const ast = parse(code, { sourceType: 'module' })
|
|
15
|
+
|
|
16
|
+
// Manipulate the AST
|
|
17
|
+
traverse.default(ast, {
|
|
18
|
+
AssignmentExpression (path) {
|
|
19
|
+
const { left } = path.node
|
|
20
|
+
|
|
21
|
+
// module.exports = $EXPRESSION
|
|
22
|
+
if (left.object.name === 'module' && left.property.name === 'exports') {
|
|
23
|
+
format = 'cjs'
|
|
24
|
+
path.stop()
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
return format
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
fsPromises.readFile = async function WTF (url, options) {
|
|
33
|
+
if (url.startsWith('file://')) {
|
|
34
|
+
url = fileURLToPath(url)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const contents = await originalReadFile(url, options)
|
|
38
|
+
|
|
39
|
+
if (url !== targetFile) {
|
|
40
|
+
return contents
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const { code } = transformSync(contents.toString('utf-8'), { mode: 'strip-only' })
|
|
44
|
+
|
|
45
|
+
const { transformESM, transformCJS } = await import('./loader.js')
|
|
46
|
+
const transformer = detectFormat(code) === 'esm' ? transformESM : transformCJS
|
|
47
|
+
const transformed = transformer(code)
|
|
48
|
+
|
|
49
|
+
// Restore the original method
|
|
50
|
+
fsPromises.readFile = originalReadFile
|
|
51
|
+
return transformed
|
|
52
|
+
}
|
package/lib/loader.js
CHANGED
|
@@ -18,7 +18,7 @@ const originalId = '__pltOriginalNextConfig'
|
|
|
18
18
|
|
|
19
19
|
let config
|
|
20
20
|
let candidates
|
|
21
|
-
let basePath
|
|
21
|
+
let basePath = ''
|
|
22
22
|
|
|
23
23
|
function parseSingleExpression (expr) {
|
|
24
24
|
return parse(expr, { allowAwaitOutsideFunction: true }).program.body[0]
|
|
@@ -86,7 +86,7 @@ function createEvaluatorWrapperFunction (original) {
|
|
|
86
86
|
)
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
function transformCJS (source) {
|
|
89
|
+
export function transformCJS (source) {
|
|
90
90
|
const ast = parse(source.toString(), { sourceType: 'module' })
|
|
91
91
|
|
|
92
92
|
// Manipulate the AST
|
|
@@ -105,7 +105,7 @@ function transformCJS (source) {
|
|
|
105
105
|
return generate.default(ast).code
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
function transformESM (source) {
|
|
108
|
+
export function transformESM (source) {
|
|
109
109
|
const ast = parse(source.toString(), { sourceType: 'module' })
|
|
110
110
|
|
|
111
111
|
// Manipulate the AST
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/next",
|
|
3
|
-
"version": "2.19.0-alpha.
|
|
3
|
+
"version": "2.19.0-alpha.10",
|
|
4
4
|
"description": "Platformatic Next.js Stackable",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -21,25 +21,28 @@
|
|
|
21
21
|
"@babel/types": "^7.25.2",
|
|
22
22
|
"iovalkey": "^0.2.1",
|
|
23
23
|
"msgpackr": "^1.11.2",
|
|
24
|
+
"amaro": "^0.2.0",
|
|
24
25
|
"semver": "^7.6.3",
|
|
25
|
-
"@platformatic/basic": "2.19.0-alpha.
|
|
26
|
-
"@platformatic/config": "2.19.0-alpha.
|
|
27
|
-
"@platformatic/utils": "2.19.0-alpha.
|
|
26
|
+
"@platformatic/basic": "2.19.0-alpha.10",
|
|
27
|
+
"@platformatic/config": "2.19.0-alpha.10",
|
|
28
|
+
"@platformatic/utils": "2.19.0-alpha.10"
|
|
28
29
|
},
|
|
29
30
|
"devDependencies": {
|
|
30
31
|
"@fastify/reply-from": "^11.0.0",
|
|
32
|
+
"@types/node": "^22.5.0",
|
|
31
33
|
"borp": "^0.19.0",
|
|
32
34
|
"eslint": "9",
|
|
35
|
+
"execa": "^9.5.1",
|
|
33
36
|
"fastify": "^5.0.0",
|
|
34
37
|
"json-schema-to-typescript": "^15.0.1",
|
|
35
38
|
"neostandard": "^0.11.1",
|
|
36
|
-
"next": "^
|
|
39
|
+
"next": "^15.0.0",
|
|
37
40
|
"react": "^18.3.1",
|
|
38
41
|
"react-dom": "^18.3.1",
|
|
39
42
|
"typescript": "^5.5.4",
|
|
40
43
|
"ws": "^8.18.0",
|
|
41
|
-
"@platformatic/composer": "2.19.0-alpha.
|
|
42
|
-
"@platformatic/service": "2.19.0-alpha.
|
|
44
|
+
"@platformatic/composer": "2.19.0-alpha.10",
|
|
45
|
+
"@platformatic/service": "2.19.0-alpha.10"
|
|
43
46
|
},
|
|
44
47
|
"scripts": {
|
|
45
48
|
"test": "npm run lint && borp --concurrency=1 --no-timeout",
|
package/schema.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$id": "https://schemas.platformatic.dev/@platformatic/next/2.19.0-alpha.
|
|
2
|
+
"$id": "https://schemas.platformatic.dev/@platformatic/next/2.19.0-alpha.10.json",
|
|
3
3
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
4
4
|
"title": "Platformatic Next.js Stackable",
|
|
5
5
|
"type": "object",
|