@platformatic/next 3.4.1 → 3.5.0

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.
@@ -0,0 +1,31 @@
1
+ 'use strict'
2
+
3
+ import { createRequire } from 'node:module'
4
+ import { getGlobalDispatcher } from 'undici'
5
+
6
+ // Next.js runs middlewares in it's own patched vm context. So the global dispatcher in
7
+ // the middleware context is different from an application global dispatcher. This
8
+ // method sets an application global dispatcher after next.js defines it's own version of
9
+ // fetch function.
10
+ export function patchVmCreateContext () {
11
+ const _require = createRequire(process.cwd())
12
+ const vm = _require('node:vm')
13
+
14
+ const originalCreateContext = vm.createContext
15
+ vm.createContext = (contextObject, opts) => {
16
+ const globalDispatcher = getGlobalDispatcher()
17
+ const context = originalCreateContext(contextObject, opts)
18
+ queueMicrotask(() => {
19
+ if (contextObject.fetch === undefined) return
20
+
21
+ const originalFetch = contextObject.fetch
22
+ contextObject.fetch = (input, init = {}) => {
23
+ init.dispatcher = globalDispatcher
24
+ return originalFetch(input, init)
25
+ }
26
+ })
27
+ return context
28
+ }
29
+ }
30
+
31
+ patchVmCreateContext()
@@ -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
@@ -11,12 +11,14 @@ import {
11
11
  variableDeclarator
12
12
  } from '@babel/types'
13
13
  import { readFile, realpath } from 'node:fs/promises'
14
+ import { sep } from 'node:path'
14
15
  import { fileURLToPath, pathToFileURL } from 'node:url'
15
16
 
16
17
  const originalId = '__pltOriginalNextConfig'
17
18
 
19
+ let config
18
20
  let candidates
19
- let basePath
21
+ let basePath = ''
20
22
 
21
23
  function parseSingleExpression (expr) {
22
24
  return parse(expr, { allowAwaitOutsideFunction: true }).program.body[0]
@@ -35,37 +37,63 @@ function parseSingleExpression (expr) {
35
37
  __pltOriginalNextConfig.basePath = basePath
36
38
  }
37
39
 
38
- // This is to send the configuraion when Next is executed in a child process (development)
39
- globalThis[Symbol.for('plt.children.itc')]?.notify('config', __pltOriginalNextConfig)
40
+ if(typeof __pltOriginalNextConfig.cacheHandler === 'undefined') {
41
+ __pltOriginalNextConfig.cacheHandler = $PATH
42
+ __pltOriginalNextConfig.cacheMaxMemorySize = 0
43
+ }
40
44
 
41
- // This is to send the configuraion when Next is executed in the same process (production)
42
- process.emit('plt:next:config', __pltOriginalNextConfig)
45
+ // This is to send the configuraion when Next is executed in a child process
46
+ globalThis.platformatic.notifyConfig(__pltOriginalNextConfig)
43
47
 
44
48
  return __pltOriginalNextConfig;
45
49
  }
46
50
  */
47
51
  function createEvaluatorWrapperFunction (original) {
52
+ const cacheHandler = config?.cache?.adapter
53
+ ? fileURLToPath(new URL(`./caching/${config.cache.adapter}.js`, import.meta.url)).replaceAll(sep, '/')
54
+ : undefined
55
+
56
+ const trailingSlash = config?.next?.trailingSlash
57
+
48
58
  return functionDeclaration(
49
59
  null,
50
60
  [restElement(identifier('args'))],
51
- blockStatement([
52
- variableDeclaration('let', [variableDeclarator(identifier(originalId), original)]),
53
- parseSingleExpression(
54
- `if (typeof ${originalId} === 'function') { ${originalId} = await ${originalId}(...args) }`
55
- ),
56
- parseSingleExpression(
57
- `if (typeof ${originalId}.basePath === 'undefined') { ${originalId}.basePath = "${basePath}" }`
58
- ),
59
- parseSingleExpression(`globalThis[Symbol.for('plt.children.itc')]?.notify('config', ${originalId})`),
60
- parseSingleExpression(`process.emit('plt:next:config', ${originalId})`),
61
- returnStatement(identifier(originalId))
62
- ]),
61
+ blockStatement(
62
+ [
63
+ // This is to avoid https://github.com/vercel/next.js/issues/76981
64
+ parseSingleExpression("Headers.prototype[Symbol.for('nodejs.util.inspect.custom')] = undefined"),
65
+ variableDeclaration('let', [variableDeclarator(identifier(originalId), original)]),
66
+ parseSingleExpression(
67
+ `if (typeof ${originalId} === 'function') { ${originalId} = await ${originalId}(...args) }`
68
+ ),
69
+ parseSingleExpression(
70
+ `if (typeof ${originalId}.basePath === 'undefined') { ${originalId}.basePath = "${basePath}" }`
71
+ ),
72
+ cacheHandler
73
+ ? parseSingleExpression(`
74
+ if (typeof ${originalId}.cacheHandler === 'undefined') {
75
+ ${originalId}.cacheHandler = '${cacheHandler}'
76
+ ${originalId}.cacheMaxMemorySize = 0
77
+ }
78
+ `)
79
+ : undefined,
80
+ trailingSlash
81
+ ? parseSingleExpression(`
82
+ if (typeof ${originalId}.trailingSlash === 'undefined') {
83
+ ${originalId}.trailingSlash = true
84
+ }
85
+ `)
86
+ : undefined,
87
+ parseSingleExpression(`globalThis.platformatic.notifyConfig(${originalId})`),
88
+ returnStatement(identifier(originalId))
89
+ ].filter(e => e)
90
+ ),
63
91
  false,
64
92
  true
65
93
  )
66
94
  }
67
95
 
68
- function transformCJS (source) {
96
+ export function transformCJS (source) {
69
97
  const ast = parse(source.toString(), { sourceType: 'module' })
70
98
 
71
99
  // Manipulate the AST
@@ -84,7 +112,7 @@ function transformCJS (source) {
84
112
  return generate.default(ast).code
85
113
  }
86
114
 
87
- function transformESM (source) {
115
+ export function transformESM (source) {
88
116
  const ast = parse(source.toString(), { sourceType: 'module' })
89
117
 
90
118
  // Manipulate the AST
@@ -125,6 +153,7 @@ export async function initialize (data) {
125
153
  // Keep in sync with https://github.com/vercel/next.js/blob/main/packages/next/src/shared/lib/constants.ts
126
154
  candidates = ['next.config.js', 'next.config.mjs'].map(c => new URL(c, realRoot).toString())
127
155
  basePath = data.basePath ?? ''
156
+ config = data.config
128
157
  }
129
158
 
130
159
  export async function load (url, context, nextLoad) {
package/lib/schema.js CHANGED
@@ -1,13 +1,59 @@
1
- import { schemaComponents } from '@platformatic/basic'
2
- import { schemaComponents as utilsSchemaComponents } from '@platformatic/utils'
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(new URL('../package.json', import.meta.url), 'utf-8'))
6
+ export const packageJson = JSON.parse(readFileSync(resolve(import.meta.dirname, '../package.json'), 'utf8'))
7
+ export const version = packageJson.version
8
+
9
+ export const cache = {
10
+ type: 'object',
11
+ properties: {
12
+ adapter: {
13
+ type: 'string',
14
+ enum: ['redis', 'valkey']
15
+ },
16
+ url: {
17
+ type: 'string'
18
+ },
19
+ prefix: {
20
+ type: 'string'
21
+ },
22
+ maxTTL: {
23
+ default: 86400 * 7, // One week
24
+ anyOf: [
25
+ {
26
+ type: 'number',
27
+ minimum: 0
28
+ },
29
+ {
30
+ type: 'string'
31
+ }
32
+ ]
33
+ }
34
+ },
35
+ required: ['adapter', 'url'],
36
+ additionalProperties: false
37
+ }
38
+
39
+ const next = {
40
+ type: 'object',
41
+ properties: {
42
+ trailingSlash: {
43
+ type: 'boolean',
44
+ default: false
45
+ }
46
+ },
47
+ default: {},
48
+ additionalProperties: false
49
+ }
50
+
51
+ export const schemaComponents = { next }
6
52
 
7
53
  export const schema = {
8
54
  $id: `https://schemas.platformatic.dev/@platformatic/next/${packageJson.version}.json`,
9
55
  $schema: 'http://json-schema.org/draft-07/schema#',
10
- title: 'Platformatic Next.js Stackable',
56
+ title: 'Platformatic Next.js Config',
11
57
  type: 'object',
12
58
  properties: {
13
59
  $schema: {
@@ -15,8 +61,11 @@ export const schema = {
15
61
  },
16
62
  logger: utilsSchemaComponents.logger,
17
63
  server: utilsSchemaComponents.server,
18
- watch: schemaComponents.watch,
19
- application: schemaComponents.application
64
+ watch: basicSchemaComponents.watch,
65
+ application: basicSchemaComponents.buildableApplication,
66
+ runtime: utilsSchemaComponents.wrappedRuntime,
67
+ next,
68
+ cache
20
69
  },
21
70
  additionalProperties: false
22
71
  }
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@platformatic/next",
3
- "version": "3.4.1",
4
- "description": "Platformatic Next.js Stackable",
3
+ "version": "3.5.0",
4
+ "description": "Platformatic Next.js Capability",
5
5
  "main": "index.js",
6
6
  "type": "module",
7
7
  "repository": {
8
8
  "type": "git",
9
9
  "url": "git+https://github.com/platformatic/platformatic.git"
10
10
  },
11
- "author": "Paolo Insogna <paolo@cowtech.it>",
11
+ "author": "Platformatic Inc. <oss@platformatic.dev> (https://platformatic.dev)",
12
12
  "license": "Apache-2.0",
13
13
  "bugs": {
14
14
  "url": "https://github.com/platformatic/platformatic/issues"
@@ -19,32 +19,38 @@
19
19
  "@babel/parser": "^7.25.3",
20
20
  "@babel/traverse": "^7.25.3",
21
21
  "@babel/types": "^7.25.2",
22
+ "amaro": "^0.3.0",
23
+ "iovalkey": "^0.3.0",
24
+ "msgpackr": "^1.11.2",
22
25
  "semver": "^7.6.3",
23
- "@platformatic/basic": "3.4.1",
24
- "@platformatic/config": "3.4.1",
25
- "@platformatic/utils": "3.4.1"
26
+ "@platformatic/basic": "3.5.0",
27
+ "@platformatic/foundation": "3.5.0"
26
28
  },
27
29
  "devDependencies": {
28
- "@fastify/reply-from": "^11.0.0",
29
- "borp": "^0.17.0",
30
+ "@fastify/reply-from": "^12.0.0",
31
+ "@types/node": "^22.5.0",
32
+ "@types/react": "^19.1.11",
33
+ "@types/react-dom": "^19.1.7",
34
+ "cleaner-spec-reporter": "^0.5.0",
30
35
  "eslint": "9",
36
+ "execa": "^9.5.1",
31
37
  "fastify": "^5.0.0",
32
38
  "json-schema-to-typescript": "^15.0.1",
33
- "neostandard": "^0.11.1",
34
- "next": "^14.2.5",
35
- "react": "^18.3.1",
36
- "react-dom": "^18.3.1",
39
+ "neostandard": "^0.12.0",
40
+ "next": "^15.0.0",
37
41
  "typescript": "^5.5.4",
38
42
  "ws": "^8.18.0",
39
- "@platformatic/composer": "3.4.1",
40
- "@platformatic/service": "3.4.1"
43
+ "@platformatic/service": "3.5.0",
44
+ "@platformatic/gateway": "3.5.0"
45
+ },
46
+ "engines": {
47
+ "node": ">=22.19.0"
41
48
  },
42
49
  "scripts": {
43
- "test": "npm run lint && borp --concurrency=1 --no-timeout",
44
- "coverage": "npm run lint && borp -C -X test -X test/fixtures --concurrency=1 --no-timeout",
50
+ "test": "node --test --test-reporter=cleaner-spec-reporter --test-concurrency=1 --test-timeout=2000000 test/*.test.js test/**/*.test.js",
45
51
  "gen-schema": "node lib/schema.js > schema.json",
46
52
  "gen-types": "json2ts > config.d.ts < schema.json",
47
- "build": "pnpm run gen-schema && pnpm run gen-types",
53
+ "build": "npm run gen-schema && npm run gen-types",
48
54
  "lint": "eslint"
49
55
  }
50
56
  }