@platformatic/next 2.0.0-alpha.6 → 2.0.0-alpha.8

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.
Files changed (43) hide show
  1. package/config.d.ts +8 -0
  2. package/eslint.config.js +1 -1
  3. package/index.js +168 -45
  4. package/lib/loader.js +27 -12
  5. package/lib/schema.js +4 -4
  6. package/package.json +9 -8
  7. package/schema.json +36 -2
  8. package/test/fixtures/next/composer-autodetect-prefix/next.config.mjs +0 -5
  9. package/test/fixtures/next/composer-autodetect-prefix/package.json +0 -15
  10. package/test/fixtures/next/composer-autodetect-prefix/platformatic.application.json +0 -8
  11. package/test/fixtures/next/composer-autodetect-prefix/platformatic.runtime.json +0 -20
  12. package/test/fixtures/next/composer-autodetect-prefix/src/app/layout.js +0 -7
  13. package/test/fixtures/next/composer-autodetect-prefix/src/app/page.js +0 -5
  14. package/test/fixtures/next/composer-with-prefix/next.config.js +0 -1
  15. package/test/fixtures/next/composer-with-prefix/package.json +0 -15
  16. package/test/fixtures/next/composer-with-prefix/platformatic.application.json +0 -11
  17. package/test/fixtures/next/composer-with-prefix/platformatic.runtime.json +0 -21
  18. package/test/fixtures/next/composer-with-prefix/src/app/layout.js +0 -7
  19. package/test/fixtures/next/composer-with-prefix/src/app/page.js +0 -5
  20. package/test/fixtures/next/composer-without-prefix/next.config.mjs +0 -3
  21. package/test/fixtures/next/composer-without-prefix/package.json +0 -15
  22. package/test/fixtures/next/composer-without-prefix/platformatic.application.json +0 -8
  23. package/test/fixtures/next/composer-without-prefix/platformatic.runtime.json +0 -21
  24. package/test/fixtures/next/composer-without-prefix/src/app/layout.js +0 -7
  25. package/test/fixtures/next/composer-without-prefix/src/app/page.js +0 -5
  26. package/test/fixtures/next/server-side/next.config.js +0 -1
  27. package/test/fixtures/next/server-side/package.json +0 -15
  28. package/test/fixtures/next/server-side/platformatic.application.json +0 -11
  29. package/test/fixtures/next/server-side/platformatic.runtime.json +0 -21
  30. package/test/fixtures/next/server-side/src/app/direct/route.js +0 -3
  31. package/test/fixtures/next/server-side/src/app/layout.js +0 -7
  32. package/test/fixtures/next/server-side/src/app/page.js +0 -12
  33. package/test/fixtures/next/standalone/next.config.mjs +0 -3
  34. package/test/fixtures/next/standalone/package.json +0 -15
  35. package/test/fixtures/next/standalone/platformatic.runtime.json +0 -18
  36. package/test/fixtures/next/standalone/src/app/layout.js +0 -7
  37. package/test/fixtures/next/standalone/src/app/page.js +0 -5
  38. package/test/fixtures/platformatic-composer/platformatic.composer.json +0 -20
  39. package/test/fixtures/platformatic-composer/platformatic.no-prefix.composer.json +0 -23
  40. package/test/fixtures/platformatic-composer/plugin.js +0 -9
  41. package/test/fixtures/platformatic-service/platformatic.service.json +0 -15
  42. package/test/fixtures/platformatic-service/plugin.js +0 -19
  43. package/test/index.test.js +0 -132
package/config.d.ts CHANGED
@@ -152,5 +152,13 @@ export interface PlatformaticNextJsStackable {
152
152
  | string;
153
153
  application?: {
154
154
  basePath?: string;
155
+ outputDirectory?: string;
156
+ include?: string[];
157
+ commands?: {
158
+ install?: string;
159
+ build?: string;
160
+ development?: string;
161
+ production?: string;
162
+ };
155
163
  };
156
164
  }
package/eslint.config.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import neostandard from 'neostandard'
2
2
 
3
3
  export default neostandard({
4
- ignores: ['test/tmp/version.js', '**/.next'],
4
+ ignores: ['**/.next', '**/dist', '**/tmp', 'test/fixtures/**'],
5
5
  })
package/index.js CHANGED
@@ -1,8 +1,19 @@
1
- import { BaseStackable, ChildManager, errors, importFile } from '@platformatic/basic'
1
+ import {
2
+ BaseStackable,
3
+ transformConfig as basicTransformConfig,
4
+ ChildManager,
5
+ cleanBasePath,
6
+ createChildProcessListener,
7
+ createServerListener,
8
+ errors,
9
+ getServerUrl,
10
+ importFile,
11
+ resolvePackage,
12
+ schemaOptions
13
+ } from '@platformatic/basic'
2
14
  import { ConfigManager } from '@platformatic/config'
3
15
  import { once } from 'node:events'
4
16
  import { readFile } from 'node:fs/promises'
5
- import { createRequire } from 'node:module'
6
17
  import { dirname, resolve as pathResolve } from 'node:path'
7
18
  import { pathToFileURL } from 'node:url'
8
19
  import { satisfies } from 'semver'
@@ -14,13 +25,15 @@ export class NextStackable extends BaseStackable {
14
25
  #basePath
15
26
  #next
16
27
  #manager
28
+ #child
29
+ #server
17
30
 
18
31
  constructor (options, root, configManager) {
19
32
  super('next', packageJson.version, options, root, configManager)
20
33
  }
21
34
 
22
35
  async init () {
23
- this.#next = pathResolve(dirname(createRequire(this.root).resolve('next')), '../..')
36
+ this.#next = pathResolve(dirname(resolvePackage(this.root, 'next')), '../..')
24
37
  const nextPackage = JSON.parse(await readFile(pathResolve(this.#next, 'package.json'), 'utf-8'))
25
38
 
26
39
  /* c8 ignore next 3 */
@@ -29,76 +42,183 @@ export class NextStackable extends BaseStackable {
29
42
  }
30
43
  }
31
44
 
32
- async start () {
45
+ async start ({ listen }) {
33
46
  // Make this idempotent
34
47
  if (this.url) {
35
48
  return this.url
36
49
  }
37
50
 
51
+ if (this.isProduction) {
52
+ await this.#startProduction(listen)
53
+ } else {
54
+ await this.#startDevelopment(listen)
55
+ }
56
+ }
57
+
58
+ async stop () {
59
+ if (this.subprocess) {
60
+ return this.stopCommand()
61
+ }
62
+
63
+ if (this.isProduction) {
64
+ return new Promise((resolve, reject) => {
65
+ this.#server.close(error => {
66
+ /* c8 ignore next 3 */
67
+ if (error) {
68
+ return reject(error)
69
+ }
70
+
71
+ resolve()
72
+ })
73
+ })
74
+ } else {
75
+ const exitPromise = once(this.#child, 'exit')
76
+ await this.#manager.close()
77
+ process.kill(this.#child.pid, 'SIGKILL')
78
+ await exitPromise
79
+ }
80
+ }
81
+
82
+ async build () {
83
+ const config = this.configManager.current
84
+ const loader = new URL('./lib/loader.js', import.meta.url)
85
+ this.#basePath = config.application?.basePath ? cleanBasePath(config.application?.basePath) : ''
86
+
87
+ let command = config.application.commands.build
88
+
89
+ if (!command) {
90
+ await this.init()
91
+ command = ['node', pathResolve(this.#next, './dist/bin/next'), 'build', this.root]
92
+ }
93
+
94
+ return this.buildWithCommand(command, this.#basePath, loader)
95
+ }
96
+
97
+ /* c8 ignore next 5 */
98
+ async getWatchConfig () {
99
+ return {
100
+ enabled: false
101
+ }
102
+ }
103
+
104
+ getMeta () {
105
+ let composer = { prefix: this.servicePrefix, wantsAbsoluteUrls: true, needsRootRedirect: false }
106
+
107
+ if (this.url) {
108
+ composer = {
109
+ tcp: true,
110
+ url: this.url,
111
+ prefix: this.#basePath ?? this.servicePrefix,
112
+ wantsAbsoluteUrls: true,
113
+ needsRootRedirect: false
114
+ }
115
+ }
116
+
117
+ return { composer }
118
+ }
119
+
120
+ async #startDevelopment () {
38
121
  const config = this.configManager.current
39
- const require = createRequire(this.root)
40
- const nextRoot = require.resolve('next')
122
+ const loaderUrl = new URL('./lib/loader.js', import.meta.url)
123
+ const command = this.configManager.current.application.commands.development
124
+
125
+ this.#basePath = config.application?.basePath ? cleanBasePath(config.application?.basePath) : ''
126
+
127
+ if (command) {
128
+ return this.startWithCommand(command, loaderUrl)
129
+ }
41
130
 
42
131
  const { hostname, port } = this.serverConfig ?? {}
43
132
  const serverOptions = {
44
133
  host: hostname || '127.0.0.1',
45
- port: port || 0,
134
+ port: port || 0
46
135
  }
47
136
 
48
- this.#basePath = config.application?.basePath
49
- ? `/${config.application?.basePath}`.replaceAll(/\/+/g, '/').replace(/\/$/, '')
50
- : ''
51
-
52
137
  this.#manager = new ChildManager({
53
- loader: new URL('./lib/loader.js', import.meta.url),
138
+ loader: loaderUrl,
54
139
  context: {
140
+ id: this.id,
55
141
  // Always use URL to avoid serialization problem in Windows
56
- root: pathToFileURL(this.root),
142
+ root: pathToFileURL(this.root).toString(),
57
143
  basePath: this.#basePath,
58
- logger: { id: this.id, level: this.logger.level },
59
- },
60
- })
61
-
62
- this.#manager.on('config', config => {
63
- this.#basePath = config.basePath.replace(/(^\/)|(\/$)/g, '')
144
+ logLevel: this.logger.level
145
+ }
64
146
  })
65
147
 
66
148
  const promise = once(this.#manager, 'url')
67
- await this.#startNext(nextRoot, serverOptions)
149
+ await this.#startDevelopmentNext(serverOptions)
68
150
  this.url = (await promise)[0]
69
151
  }
70
152
 
71
- async stop () {
72
- const exitPromise = once(this.#manager, 'exit')
153
+ async #startDevelopmentNext (serverOptions) {
154
+ const { nextDev } = await importFile(pathResolve(this.#next, './dist/cli/next-dev.js'))
73
155
 
74
- this.#manager.close()
75
- await exitPromise
76
- }
156
+ this.#manager.on('config', config => {
157
+ this.#basePath = config.basePath.replace(/(^\/)|(\/$)/g, '')
158
+ })
77
159
 
78
- /* c8 ignore next 5 */
79
- async getWatchConfig () {
80
- return {
81
- enabled: false,
160
+ try {
161
+ await this.#manager.inject()
162
+ const childPromise = createChildProcessListener()
163
+ await nextDev(serverOptions, 'default', this.root)
164
+ this.#child = await childPromise
165
+ } finally {
166
+ await this.#manager.eject()
82
167
  }
83
168
  }
84
169
 
85
- getMeta () {
86
- return {
87
- composer: {
88
- tcp: true,
89
- url: this.url,
90
- prefix: this.#basePath,
91
- wantsAbsoluteUrls: true,
92
- },
170
+ async #startProduction (listen) {
171
+ const config = this.configManager.current
172
+ const loaderUrl = new URL('./lib/loader.js', import.meta.url)
173
+ const command = this.configManager.current.application.commands.production
174
+
175
+ this.#basePath = config.application?.basePath ? cleanBasePath(config.application?.basePath) : ''
176
+
177
+ if (command) {
178
+ return this.startWithCommand(command, loaderUrl)
93
179
  }
180
+
181
+ this.#manager = new ChildManager({
182
+ loader: loaderUrl,
183
+ context: {
184
+ id: this.id,
185
+ // Always use URL to avoid serialization problem in Windows
186
+ root: pathToFileURL(this.root).toString(),
187
+ basePath: this.#basePath,
188
+ logLevel: this.logger.level
189
+ }
190
+ })
191
+
192
+ this.verifyOutputDirectory(pathResolve(this.root, '.next'))
193
+ await this.#startProductionNext()
94
194
  }
95
195
 
96
- async #startNext (nextRoot, serverOptions) {
97
- const { nextDev } = await importFile(pathResolve(this.#next, './dist/cli/next-dev.js'))
196
+ async #startProductionNext () {
197
+ try {
198
+ await this.#manager.inject()
199
+ const { nextStart } = await importFile(pathResolve(this.#next, './dist/cli/next-start.js'))
200
+
201
+ const { hostname, port } = this.serverConfig ?? {}
202
+ const serverOptions = {
203
+ hostname: hostname || '127.0.0.1',
204
+ port: port || 0
205
+ }
206
+
207
+ // Since we are in the same process
208
+ process.once('plt:next:config', config => {
209
+ this.#basePath = config.basePath.replace(/(^\/)|(\/$)/g, '')
210
+ })
98
211
 
99
- this.#manager.inject()
100
- await nextDev(serverOptions, 'default', this.root)
101
- this.#manager.eject()
212
+ this.#manager.register()
213
+ const serverPromise = createServerListener((this.isEntrypoint ? serverOptions?.port : undefined) ?? true)
214
+
215
+ await nextStart(serverOptions, this.root)
216
+
217
+ this.#server = await serverPromise
218
+ this.url = getServerUrl(this.#server)
219
+ } finally {
220
+ await this.#manager.eject()
221
+ }
102
222
  }
103
223
  }
104
224
 
@@ -111,12 +231,14 @@ function transformConfig () {
111
231
  if (typeof this.current.watch !== 'object') {
112
232
  this.current.watch = { enabled: this.current.watch || false }
113
233
  }
234
+
235
+ basicTransformConfig.call(this)
114
236
  }
115
237
 
116
238
  export async function buildStackable (opts) {
117
239
  const root = opts.context.directory
118
240
 
119
- const configManager = new ConfigManager({ schema, source: opts.config ?? {}, transformConfig })
241
+ const configManager = new ConfigManager({ schema, source: opts.config ?? {}, schemaOptions, transformConfig })
120
242
  await configManager.parseAndValidate()
121
243
 
122
244
  return new NextStackable(opts, root, configManager)
@@ -125,9 +247,10 @@ export async function buildStackable (opts) {
125
247
  export default {
126
248
  configType: 'next',
127
249
  configManagerConfig: {
128
- transformConfig,
250
+ schemaOptions,
251
+ transformConfig
129
252
  },
130
253
  buildStackable,
131
254
  schema,
132
- version: packageJson.version,
255
+ version: packageJson.version
133
256
  }
package/lib/loader.js CHANGED
@@ -8,9 +8,10 @@ import {
8
8
  restElement,
9
9
  returnStatement,
10
10
  variableDeclaration,
11
- variableDeclarator,
11
+ variableDeclarator
12
12
  } from '@babel/types'
13
- import { readFile } from 'node:fs/promises'
13
+ import { readFile, realpath } from 'node:fs/promises'
14
+ import { fileURLToPath, pathToFileURL } from 'node:url'
14
15
 
15
16
  const originalId = '__pltOriginalNextConfig'
16
17
 
@@ -30,13 +31,17 @@ function parseSingleExpression (expr) {
30
31
  __pltOriginalNextConfig = await __pltOriginalNextConfig(...args);
31
32
  }
32
33
 
33
- if(typeof __pltOriginalNextConfig === 'undefined') {
34
+ if(typeof __pltOriginalNextConfig.basePath === 'undefined') {
34
35
  __pltOriginalNextConfig.basePath = basePath
35
36
  }
36
37
 
37
- return __pltOriginalNextConfig,
38
- basePath: $BASEPATH
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
+
41
+ // This is to send the configuraion when Next is executed in the same process (production)
42
+ process.emit('plt:next:config', __pltOriginalNextConfig)
43
+
44
+ return __pltOriginalNextConfig;
40
45
  }
41
46
  */
42
47
  function createEvaluatorWrapperFunction (original) {
@@ -51,8 +56,9 @@ function createEvaluatorWrapperFunction (original) {
51
56
  parseSingleExpression(
52
57
  `if (typeof ${originalId}.basePath === 'undefined') { ${originalId}.basePath = "${basePath}" }`
53
58
  ),
54
- parseSingleExpression(`globalThis[Symbol.for('plt.children.itc')].notify('config', ${originalId})`),
55
- returnStatement(identifier(originalId)),
59
+ parseSingleExpression(`globalThis[Symbol.for('plt.children.itc')]?.notify('config', ${originalId})`),
60
+ parseSingleExpression(`process.emit('plt:next:config', ${originalId})`),
61
+ returnStatement(identifier(originalId))
56
62
  ]),
57
63
  false,
58
64
  true
@@ -72,7 +78,7 @@ function transformCJS (source) {
72
78
  path.node.right = createEvaluatorWrapperFunction(right)
73
79
  path.skip()
74
80
  }
75
- },
81
+ }
76
82
  })
77
83
 
78
84
  return generate.default(ast).code
@@ -103,15 +109,21 @@ function transformESM (source) {
103
109
  // export default $EXPRESSION
104
110
  path.node.declaration = createEvaluatorWrapperFunction(declaration)
105
111
  }
106
- },
112
+ }
107
113
  })
108
114
 
109
115
  return generate.default(ast).code
110
116
  }
111
117
 
112
- export function initialize (data) {
118
+ export async function initialize (data) {
119
+ const realRoot = pathToFileURL(await realpath(fileURLToPath(data.root)))
120
+
121
+ if (!realRoot.pathname.endsWith('/')) {
122
+ realRoot.pathname += '/'
123
+ }
124
+
113
125
  // Keep in sync with https://github.com/vercel/next.js/blob/main/packages/next/src/shared/lib/constants.ts
114
- candidates = ['./next.config.js', './next.config.mjs'].map(c => new URL(c, data.root + '/').toString())
126
+ candidates = ['next.config.js', 'next.config.mjs'].map(c => new URL(c, realRoot).toString())
115
127
  basePath = data.basePath ?? ''
116
128
  }
117
129
 
@@ -123,11 +135,14 @@ export async function load (url, context, nextLoad) {
123
135
  return result
124
136
  }
125
137
 
138
+ url = pathToFileURL(await realpath(fileURLToPath(url))).toString()
139
+
126
140
  if (!candidates.includes(url)) {
127
141
  return result
128
142
  }
129
143
 
130
144
  if (result.format === 'commonjs') {
145
+ await readFile(new URL(result.responseURL ?? url), 'utf-8')
131
146
  result.source = transformCJS(result.source ?? (await readFile(new URL(result.responseURL ?? url), 'utf-8')))
132
147
  } else {
133
148
  result.source = transformESM(result.source)
package/lib/schema.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { schemaComponents } from '@platformatic/basic'
2
- import { schemas as utilsSchema } from '@platformatic/utils'
2
+ import { schemaComponents as utilsSchemaComponents } from '@platformatic/utils'
3
3
  import { readFileSync } from 'node:fs'
4
4
 
5
5
  export const packageJson = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf-8'))
@@ -11,13 +11,13 @@ export const schema = {
11
11
  type: 'object',
12
12
  properties: {
13
13
  $schema: {
14
- type: 'string',
14
+ type: 'string'
15
15
  },
16
- server: utilsSchema.server,
16
+ server: utilsSchemaComponents.server,
17
17
  watch: schemaComponents.watch,
18
18
  application: schemaComponents.application,
19
19
  },
20
- additionalProperties: false,
20
+ additionalProperties: false
21
21
  }
22
22
 
23
23
  /* c8 ignore next 3 */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/next",
3
- "version": "2.0.0-alpha.6",
3
+ "version": "2.0.0-alpha.8",
4
4
  "description": "Platformatic Next.js Stackable",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -20,13 +20,14 @@
20
20
  "@babel/traverse": "^7.25.3",
21
21
  "@babel/types": "^7.25.2",
22
22
  "semver": "^7.6.3",
23
- "@platformatic/basic": "2.0.0-alpha.6",
24
- "@platformatic/config": "2.0.0-alpha.6",
25
- "@platformatic/utils": "2.0.0-alpha.6"
23
+ "@platformatic/basic": "2.0.0-alpha.8",
24
+ "@platformatic/config": "2.0.0-alpha.8",
25
+ "@platformatic/utils": "2.0.0-alpha.8"
26
26
  },
27
27
  "devDependencies": {
28
28
  "borp": "^0.17.0",
29
29
  "eslint": "9",
30
+ "fastify": "^4.28.1",
30
31
  "json-schema-to-typescript": "^15.0.1",
31
32
  "neostandard": "^0.11.1",
32
33
  "next": "^14.2.5",
@@ -34,12 +35,12 @@
34
35
  "react-dom": "^18.3.1",
35
36
  "typescript": "^5.5.4",
36
37
  "ws": "^8.18.0",
37
- "@platformatic/composer": "2.0.0-alpha.6",
38
- "@platformatic/service": "2.0.0-alpha.6"
38
+ "@platformatic/composer": "2.0.0-alpha.8",
39
+ "@platformatic/service": "2.0.0-alpha.8"
39
40
  },
40
41
  "scripts": {
41
- "test": "npm run lint && borp --concurrency=1 --timeout=180000",
42
- "coverage": "npm run lint && borp -C -X test -X test/fixtures --concurrency=1 --timeout=180000",
42
+ "test": "npm run lint && borp --concurrency=1 --no-timeout",
43
+ "coverage": "npm run lint && borp -C -X test -X test/fixtures --concurrency=1 --no-timeout",
43
44
  "gen-schema": "node lib/schema.js > schema.json",
44
45
  "gen-types": "json2ts > config.d.ts < schema.json",
45
46
  "build": "pnpm run gen-schema && pnpm run gen-types",
package/schema.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://schemas.platformatic.dev/@platformatic/next/2.0.0-alpha.6.json",
2
+ "$id": "https://schemas.platformatic.dev/@platformatic/next/2.0.0-alpha.8.json",
3
3
  "$schema": "http://json-schema.org/draft-07/schema#",
4
4
  "title": "Platformatic Next.js Stackable",
5
5
  "type": "object",
@@ -497,9 +497,43 @@
497
497
  "properties": {
498
498
  "basePath": {
499
499
  "type": "string"
500
+ },
501
+ "outputDirectory": {
502
+ "type": "string",
503
+ "default": "dist"
504
+ },
505
+ "include": {
506
+ "type": "array",
507
+ "items": {
508
+ "type": "string"
509
+ },
510
+ "default": [
511
+ "dist"
512
+ ]
513
+ },
514
+ "commands": {
515
+ "type": "object",
516
+ "properties": {
517
+ "install": {
518
+ "type": "string",
519
+ "default": "npm ci --omit-dev"
520
+ },
521
+ "build": {
522
+ "type": "string"
523
+ },
524
+ "development": {
525
+ "type": "string"
526
+ },
527
+ "production": {
528
+ "type": "string"
529
+ }
530
+ },
531
+ "default": {},
532
+ "additionalProperties": false
500
533
  }
501
534
  },
502
- "additionalProperties": false
535
+ "additionalProperties": false,
536
+ "default": {}
503
537
  }
504
538
  },
505
539
  "additionalProperties": false
@@ -1,5 +0,0 @@
1
- const nextConfig = {
2
- basePath: '/nested/base/dir',
3
- }
4
-
5
- export default nextConfig
@@ -1,15 +0,0 @@
1
- {
2
- "name": "next",
3
- "private": true,
4
- "version": "0.1.0",
5
- "scripts": {
6
- "dev": "next dev",
7
- "build": "next build",
8
- "start": "next start"
9
- },
10
- "dependencies": {
11
- "react": "^18.0.0",
12
- "react-dom": "^18.0.0",
13
- "next": "^14.2.5"
14
- }
15
- }
@@ -1,8 +0,0 @@
1
- {
2
- "$schema": "https://schemas.platformatic.dev/@platformatic/next/2.0.0.json",
3
- "server": {
4
- "logger": {
5
- "level": "error"
6
- }
7
- }
8
- }
@@ -1,20 +0,0 @@
1
- {
2
- "$schema": "https://schemas.platformatic.dev/@platformatic/runtime/1.52.0.json",
3
- "entrypoint": "main",
4
- "watch": false,
5
- "managementApi": false,
6
- "metrics": false,
7
- "server": {
8
- "logger": {
9
- "level": "error"
10
- }
11
- },
12
- "services": [
13
- {
14
- "id": "frontend",
15
- "path": "."
16
- },
17
- { "id": "service", "config": "platformatic.service.json", "path": "../../platformatic-service" },
18
- { "id": "main", "config": "platformatic.no-prefix.composer.json", "path": "../../platformatic-composer" }
19
- ]
20
- }
@@ -1,7 +0,0 @@
1
- export default function RootLayout ({ children }) {
2
- return (
3
- <html lang='en'>
4
- <body>{children}</body>
5
- </html>
6
- )
7
- }
@@ -1,5 +0,0 @@
1
- import { version } from '../../../../../tmp/version.js'
2
-
3
- export default function Home () {
4
- return <div>Hello from {version}</div>
5
- }
@@ -1 +0,0 @@
1
- module.exports = {}
@@ -1,15 +0,0 @@
1
- {
2
- "name": "next",
3
- "private": true,
4
- "version": "0.1.0",
5
- "scripts": {
6
- "dev": "next dev",
7
- "build": "next build",
8
- "start": "next start"
9
- },
10
- "dependencies": {
11
- "react": "^18.0.0",
12
- "react-dom": "^18.0.0",
13
- "next": "^14.2.5"
14
- }
15
- }
@@ -1,11 +0,0 @@
1
- {
2
- "$schema": "https://schemas.platformatic.dev/@platformatic/next/2.0.0.json",
3
- "server": {
4
- "logger": {
5
- "level": "error"
6
- }
7
- },
8
- "application": {
9
- "basePath": "/frontend"
10
- }
11
- }
@@ -1,21 +0,0 @@
1
- {
2
- "$schema": "https://schemas.platformatic.dev/@platformatic/runtime/1.52.0.json",
3
- "entrypoint": "main",
4
- "watch": false,
5
- "managementApi": false,
6
- "metrics": false,
7
- "server": {
8
- "logger": {
9
- "level": "error"
10
- }
11
- },
12
- "services": [
13
- {
14
- "id": "frontend",
15
- "path": ".",
16
- "config": "platformatic.application.json"
17
- },
18
- { "id": "service", "config": "platformatic.service.json", "path": "../../platformatic-service" },
19
- { "id": "main", "config": "platformatic.composer.json", "path": "../../platformatic-composer" }
20
- ]
21
- }
@@ -1,7 +0,0 @@
1
- export default function RootLayout ({ children }) {
2
- return (
3
- <html lang='en'>
4
- <body>{children}</body>
5
- </html>
6
- )
7
- }
@@ -1,5 +0,0 @@
1
- import { version } from '../../../../../tmp/version.js'
2
-
3
- export default function Home () {
4
- return <div>Hello from {version}</div>
5
- }
@@ -1,3 +0,0 @@
1
- export default function () {
2
- return {}
3
- }
@@ -1,15 +0,0 @@
1
- {
2
- "name": "next",
3
- "private": true,
4
- "version": "0.1.0",
5
- "scripts": {
6
- "dev": "next dev",
7
- "build": "next build",
8
- "start": "next start"
9
- },
10
- "dependencies": {
11
- "react": "^18.0.0",
12
- "react-dom": "^18.0.0",
13
- "next": "^14.2.5"
14
- }
15
- }
@@ -1,8 +0,0 @@
1
- {
2
- "$schema": "https://schemas.platformatic.dev/@platformatic/next/2.0.0.json",
3
- "server": {
4
- "logger": {
5
- "level": "error"
6
- }
7
- }
8
- }
@@ -1,21 +0,0 @@
1
- {
2
- "$schema": "https://schemas.platformatic.dev/@platformatic/runtime/1.52.0.json",
3
- "entrypoint": "main",
4
- "watch": false,
5
- "managementApi": false,
6
- "metrics": false,
7
- "server": {
8
- "logger": {
9
- "level": "error"
10
- }
11
- },
12
- "services": [
13
- {
14
- "id": "frontend",
15
- "path": ".",
16
- "config": "platformatic.application.json"
17
- },
18
- { "id": "service", "config": "platformatic.service.json", "path": "../../platformatic-service" },
19
- { "id": "main", "config": "platformatic.no-prefix.composer.json", "path": "../../platformatic-composer" }
20
- ]
21
- }
@@ -1,7 +0,0 @@
1
- export default function RootLayout ({ children }) {
2
- return (
3
- <html lang='en'>
4
- <body>{children}</body>
5
- </html>
6
- )
7
- }
@@ -1,5 +0,0 @@
1
- import { version } from '../../../../../tmp/version.js'
2
-
3
- export default function Home () {
4
- return <div>Hello from {version}</div>
5
- }
@@ -1 +0,0 @@
1
- module.exports = {}
@@ -1,15 +0,0 @@
1
- {
2
- "name": "next",
3
- "private": true,
4
- "version": "0.1.0",
5
- "scripts": {
6
- "dev": "next dev",
7
- "build": "next build",
8
- "start": "next start"
9
- },
10
- "dependencies": {
11
- "react": "^18.0.0",
12
- "react-dom": "^18.0.0",
13
- "next": "^14.2.5"
14
- }
15
- }
@@ -1,11 +0,0 @@
1
- {
2
- "$schema": "https://schemas.platformatic.dev/@platformatic/next/2.0.0.json",
3
- "server": {
4
- "logger": {
5
- "level": "error"
6
- }
7
- },
8
- "application": {
9
- "basePath": "/frontend"
10
- }
11
- }
@@ -1,21 +0,0 @@
1
- {
2
- "$schema": "https://schemas.platformatic.dev/@platformatic/runtime/1.52.0.json",
3
- "entrypoint": "main",
4
- "watch": false,
5
- "managementApi": false,
6
- "metrics": false,
7
- "server": {
8
- "logger": {
9
- "level": "error"
10
- }
11
- },
12
- "services": [
13
- {
14
- "id": "frontend",
15
- "path": ".",
16
- "config": "platformatic.application.json"
17
- },
18
- { "id": "service", "config": "platformatic.service.json", "path": "../../platformatic-service" },
19
- { "id": "main", "config": "platformatic.composer.json", "path": "../../platformatic-composer" }
20
- ]
21
- }
@@ -1,3 +0,0 @@
1
- export async function GET () {
2
- return Response.json({ ok: true })
3
- }
@@ -1,7 +0,0 @@
1
- export default function RootLayout ({ children }) {
2
- return (
3
- <html lang='en'>
4
- <body>{children}</body>
5
- </html>
6
- )
7
- }
@@ -1,12 +0,0 @@
1
- import { version } from '../../../../../tmp/version.js'
2
-
3
- export default async function Home () {
4
- const response = await fetch('http://service.plt.local/time')
5
- const { time } = await response.json()
6
-
7
- return (
8
- <div>
9
- Hello from v{version} t{time}
10
- </div>
11
- )
12
- }
@@ -1,3 +0,0 @@
1
- const nextConfig = {}
2
-
3
- export default nextConfig
@@ -1,15 +0,0 @@
1
- {
2
- "name": "next",
3
- "private": true,
4
- "version": "0.1.0",
5
- "scripts": {
6
- "dev": "next dev",
7
- "build": "next build",
8
- "start": "next start"
9
- },
10
- "dependencies": {
11
- "react": "^18.0.0",
12
- "react-dom": "^18.0.0",
13
- "next": "^14.2.5"
14
- }
15
- }
@@ -1,18 +0,0 @@
1
- {
2
- "$schema": "https://schemas.platformatic.dev/@platformatic/runtime/1.52.0.json",
3
- "entrypoint": "main",
4
- "watch": false,
5
- "managementApi": false,
6
- "metrics": false,
7
- "server": {
8
- "logger": {
9
- "level": "error"
10
- }
11
- },
12
- "services": [
13
- {
14
- "id": "main",
15
- "path": "."
16
- }
17
- ]
18
- }
@@ -1,7 +0,0 @@
1
- export default function RootLayout ({ children }) {
2
- return (
3
- <html lang='en'>
4
- <body>{children}</body>
5
- </html>
6
- )
7
- }
@@ -1,5 +0,0 @@
1
- import { version } from '../../../../../tmp/version.js'
2
-
3
- export default function Home () {
4
- return <div>Hello from {version}</div>
5
- }
@@ -1,20 +0,0 @@
1
- {
2
- "$schema": "https://schemas.platformatic.dev/@platformatic/composer/1.52.0.json",
3
- "composer": {
4
- "services": [
5
- {
6
- "id": "frontend"
7
- },
8
- {
9
- "id": "service"
10
- }
11
- ]
12
- },
13
- "plugins": {
14
- "paths": [
15
- {
16
- "path": "./plugin.js"
17
- }
18
- ]
19
- }
20
- }
@@ -1,23 +0,0 @@
1
- {
2
- "$schema": "https://schemas.platformatic.dev/@platformatic/composer/1.52.0.json",
3
- "composer": {
4
- "services": [
5
- {
6
- "id": "frontend",
7
- "proxy": {
8
- "prefix": ""
9
- }
10
- },
11
- {
12
- "id": "service"
13
- }
14
- ]
15
- },
16
- "plugins": {
17
- "paths": [
18
- {
19
- "path": "./plugin.js"
20
- }
21
- ]
22
- }
23
- }
@@ -1,9 +0,0 @@
1
- export default async function (app) {
2
- app.get('/plugin', async () => {
3
- return { ok: true }
4
- })
5
-
6
- app.get('/frontend/plugin', async () => {
7
- return { ok: true }
8
- })
9
- }
@@ -1,15 +0,0 @@
1
- {
2
- "$schema": "https://schemas.platformatic.dev/@platformatic/service/1.52.0.json",
3
- "server": {
4
- "logger": {
5
- "level": "error"
6
- }
7
- },
8
- "plugins": {
9
- "paths": [
10
- {
11
- "path": "./plugin.js"
12
- }
13
- ]
14
- }
15
- }
@@ -1,19 +0,0 @@
1
- 'use strict'
2
-
3
- export default async function (app) {
4
- app.get('/mesh', async () => {
5
- const meta = await globalThis[Symbol.for('plt.runtime.itc')].send('getServiceMeta', 'frontend')
6
-
7
- const url = new URL(`${meta.composer.prefix}/direct`.replaceAll(/\/+/g, '/'), 'http://frontend.plt.local')
8
- const response = await fetch(url)
9
- return response.json()
10
- })
11
-
12
- app.get('/direct', async () => {
13
- return { ok: true }
14
- })
15
-
16
- app.get('/time', async () => {
17
- return { time: Date.now() }
18
- })
19
- }
@@ -1,132 +0,0 @@
1
- import { dirname, resolve } from 'node:path'
2
- import { test } from 'node:test'
3
- import {
4
- createRuntime,
5
- fixturesDir,
6
- setFixturesDir,
7
- updateHMRVersion,
8
- verifyHMR,
9
- verifyHTMLViaHTTP,
10
- verifyHTMLViaInject,
11
- verifyJSONViaHTTP,
12
- verifyJSONViaInject,
13
- } from '../../basic/test/helper.js'
14
- import { safeRemove } from '../../utils/index.js'
15
-
16
- function websocketHMRHandler (message, resolveConnection, resolveReload) {
17
- switch (message.action) {
18
- case 'sync':
19
- resolveConnection()
20
- break
21
- case 'serverComponentChanges':
22
- resolveReload()
23
- }
24
- }
25
-
26
- function cleanNext (configFile) {
27
- const root = dirname(resolve(fixturesDir, configFile))
28
- return safeRemove(resolve(root, '.next'))
29
- }
30
-
31
- const packageRoot = resolve(import.meta.dirname, '..')
32
- setFixturesDir(resolve(import.meta.dirname, './fixtures'))
33
-
34
- test('can detect and start a Next application', async t => {
35
- await updateHMRVersion()
36
- await cleanNext('next/standalone/platformatic.runtime.json')
37
-
38
- const { url } = await createRuntime(t, 'next/standalone/platformatic.runtime.json', packageRoot)
39
-
40
- await verifyHTMLViaHTTP(url, '/', ['<script src="/_next/static/chunks/main-app.js'])
41
- await verifyHMR(url, '/_next/webpack-hmr', undefined, websocketHMRHandler)
42
- })
43
-
44
- test('can detect and start a Next application when exposed in a composer with a prefix', async t => {
45
- await updateHMRVersion()
46
- await cleanNext('next/composer-with-prefix/platformatic.runtime.json')
47
-
48
- const { runtime, url } = await createRuntime(t, 'next/composer-with-prefix/platformatic.runtime.json', packageRoot)
49
-
50
- const htmlContents = ['<script src="/frontend/_next/static/chunks/main-app.js']
51
-
52
- await verifyHTMLViaHTTP(url, '/frontend/', htmlContents)
53
- await verifyHTMLViaInject(runtime, 'main', '/frontend', htmlContents)
54
- await verifyHMR(url, '/frontend/_next/webpack-hmr', undefined, websocketHMRHandler)
55
-
56
- await verifyJSONViaHTTP(url, '/plugin', 200, { ok: true })
57
- await verifyJSONViaHTTP(url, '/frontend/plugin', 200, { ok: true })
58
- await verifyJSONViaHTTP(url, '/service/direct', 200, { ok: true })
59
-
60
- await verifyJSONViaInject(runtime, 'main', 'GET', 'plugin', 200, { ok: true })
61
- await verifyJSONViaInject(runtime, 'main', 'GET', '/frontend/plugin', 200, { ok: true })
62
- await verifyJSONViaInject(runtime, 'service', 'GET', '/direct', 200, { ok: true })
63
- })
64
-
65
- test('can detect and start a Next application when exposed in a composer without a prefix', async t => {
66
- await updateHMRVersion()
67
- await cleanNext('next/composer-without-prefix/platformatic.runtime.json')
68
-
69
- const { runtime, url } = await createRuntime(t, 'next/composer-without-prefix/platformatic.runtime.json', packageRoot)
70
-
71
- const htmlContents = ['<script src="/_next/static/chunks/main-app.js']
72
-
73
- await verifyHTMLViaHTTP(url, '/', htmlContents)
74
- await verifyHTMLViaInject(runtime, 'main', '/', htmlContents)
75
- await verifyHMR(url, '/_next/webpack-hmr', undefined, websocketHMRHandler)
76
-
77
- await verifyJSONViaHTTP(url, '/plugin', 200, { ok: true })
78
- await verifyJSONViaHTTP(url, '/frontend/plugin', 200, { ok: true })
79
- await verifyJSONViaHTTP(url, '/service/direct', 200, { ok: true })
80
-
81
- await verifyJSONViaInject(runtime, 'main', 'GET', 'plugin', 200, { ok: true })
82
- await verifyJSONViaInject(runtime, 'main', 'GET', '/frontend/plugin', 200, { ok: true })
83
- await verifyJSONViaInject(runtime, 'service', 'GET', '/direct', 200, { ok: true })
84
- })
85
-
86
- // In this file the application purposely does not specify a platformatic.application.json to see if we automatically detect one
87
- test('can detect and start a Next application when exposed in a composer with a custom config and by autodetecting the prefix', async t => {
88
- await updateHMRVersion()
89
- await cleanNext('next/composer-autodetect-prefix/platformatic.runtime.json')
90
-
91
- const { runtime, url } = await createRuntime(
92
- t,
93
- 'next/composer-autodetect-prefix/platformatic.runtime.json',
94
- packageRoot
95
- )
96
-
97
- const htmlContents = ['<script src="/nested/base/dir/_next/static/chunks/main-app.js']
98
-
99
- await verifyHTMLViaHTTP(url, '/nested/base/dir/', htmlContents)
100
- await verifyHTMLViaInject(runtime, 'main', '/nested/base/dir', htmlContents)
101
- await verifyHMR(url, '/nested/base/dir/_next/webpack-hmr', undefined, websocketHMRHandler)
102
-
103
- await verifyJSONViaHTTP(url, '/plugin', 200, { ok: true })
104
- await verifyJSONViaHTTP(url, '/frontend/plugin', 200, { ok: true })
105
- await verifyJSONViaHTTP(url, '/service/direct', 200, { ok: true })
106
-
107
- await verifyJSONViaInject(runtime, 'main', 'GET', 'plugin', 200, { ok: true })
108
- await verifyJSONViaInject(runtime, 'main', 'GET', '/frontend/plugin', 200, { ok: true })
109
- await verifyJSONViaInject(runtime, 'service', 'GET', '/direct', 200, { ok: true })
110
- })
111
-
112
- test('can detect and start a Next application with working React Server Components and Next server API', async t => {
113
- await updateHMRVersion()
114
- await cleanNext('next/server-side/platformatic.runtime.json')
115
- const { runtime, url } = await createRuntime(t, 'next/server-side/platformatic.runtime.json', packageRoot)
116
-
117
- const htmlContents = ['<script src="/frontend/_next/static/chunks/main-app.js']
118
-
119
- await verifyHTMLViaHTTP(url, '/frontend/', htmlContents)
120
- await verifyHTMLViaInject(runtime, 'main', '/frontend', htmlContents)
121
- await verifyHMR(url, '/frontend/_next/webpack-hmr', undefined, websocketHMRHandler)
122
-
123
- await verifyJSONViaHTTP(url, '/plugin', 200, { ok: true })
124
- await verifyJSONViaHTTP(url, '/frontend/plugin', 200, { ok: true })
125
- await verifyJSONViaHTTP(url, '/service/direct', 200, { ok: true })
126
- await verifyJSONViaHTTP(url, '/service/mesh', 200, { ok: true })
127
-
128
- await verifyJSONViaInject(runtime, 'main', 'GET', 'plugin', 200, { ok: true })
129
- await verifyJSONViaInject(runtime, 'main', 'GET', '/frontend/plugin', 200, { ok: true })
130
- await verifyJSONViaInject(runtime, 'service', 'GET', '/direct', 200, { ok: true })
131
- await verifyJSONViaInject(runtime, 'service', 'GET', '/mesh', 200, { ok: true })
132
- })