@platformatic/next 2.0.0-alpha.7 → 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 +7 -6
  2. package/eslint.config.js +1 -1
  3. package/index.js +149 -41
  4. package/lib/loader.js +27 -12
  5. package/lib/schema.js +4 -5
  6. package/package.json +9 -8
  7. package/schema.json +26 -19
  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,12 +152,13 @@ export interface PlatformaticNextJsStackable {
152
152
  | string;
153
153
  application?: {
154
154
  basePath?: string;
155
- };
156
- deploy?: {
155
+ outputDirectory?: string;
157
156
  include?: string[];
158
- buildCommand?: string;
159
- installCommand?: string;
160
- startCommand?: string;
161
- [k: string]: unknown;
157
+ commands?: {
158
+ install?: string;
159
+ build?: string;
160
+ development?: string;
161
+ production?: string;
162
+ };
162
163
  };
163
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
@@ -2,14 +2,18 @@ import {
2
2
  BaseStackable,
3
3
  transformConfig as basicTransformConfig,
4
4
  ChildManager,
5
+ cleanBasePath,
6
+ createChildProcessListener,
7
+ createServerListener,
5
8
  errors,
9
+ getServerUrl,
6
10
  importFile,
11
+ resolvePackage,
7
12
  schemaOptions
8
13
  } from '@platformatic/basic'
9
14
  import { ConfigManager } from '@platformatic/config'
10
15
  import { once } from 'node:events'
11
16
  import { readFile } from 'node:fs/promises'
12
- import { createRequire } from 'node:module'
13
17
  import { dirname, resolve as pathResolve } from 'node:path'
14
18
  import { pathToFileURL } from 'node:url'
15
19
  import { satisfies } from 'semver'
@@ -21,13 +25,15 @@ export class NextStackable extends BaseStackable {
21
25
  #basePath
22
26
  #next
23
27
  #manager
28
+ #child
29
+ #server
24
30
 
25
31
  constructor (options, root, configManager) {
26
32
  super('next', packageJson.version, options, root, configManager)
27
33
  }
28
34
 
29
35
  async init () {
30
- this.#next = pathResolve(dirname(createRequire(this.root).resolve('next')), '../..')
36
+ this.#next = pathResolve(dirname(resolvePackage(this.root, 'next')), '../..')
31
37
  const nextPackage = JSON.parse(await readFile(pathResolve(this.#next, 'package.json'), 'utf-8'))
32
38
 
33
39
  /* c8 ignore next 3 */
@@ -36,15 +42,91 @@ export class NextStackable extends BaseStackable {
36
42
  }
37
43
  }
38
44
 
39
- async start () {
45
+ async start ({ listen }) {
40
46
  // Make this idempotent
41
47
  if (this.url) {
42
48
  return this.url
43
49
  }
44
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 () {
45
83
  const config = this.configManager.current
46
- const require = createRequire(this.root)
47
- const nextRoot = require.resolve('next')
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 () {
121
+ const config = this.configManager.current
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
+ }
48
130
 
49
131
  const { hostname, port } = this.serverConfig ?? {}
50
132
  const serverOptions = {
@@ -52,65 +134,91 @@ export class NextStackable extends BaseStackable {
52
134
  port: port || 0
53
135
  }
54
136
 
55
- this.#basePath = config.application?.basePath
56
- ? `/${config.application?.basePath}`.replaceAll(/\/+/g, '/').replace(/\/$/, '')
57
- : ''
58
-
59
137
  this.#manager = new ChildManager({
60
- loader: new URL('./lib/loader.js', import.meta.url),
138
+ loader: loaderUrl,
61
139
  context: {
140
+ id: this.id,
62
141
  // Always use URL to avoid serialization problem in Windows
63
- root: pathToFileURL(this.root),
142
+ root: pathToFileURL(this.root).toString(),
64
143
  basePath: this.#basePath,
65
- logger: { id: this.id, level: this.logger.level }
144
+ logLevel: this.logger.level
66
145
  }
67
146
  })
68
147
 
148
+ const promise = once(this.#manager, 'url')
149
+ await this.#startDevelopmentNext(serverOptions)
150
+ this.url = (await promise)[0]
151
+ }
152
+
153
+ async #startDevelopmentNext (serverOptions) {
154
+ const { nextDev } = await importFile(pathResolve(this.#next, './dist/cli/next-dev.js'))
155
+
69
156
  this.#manager.on('config', config => {
70
157
  this.#basePath = config.basePath.replace(/(^\/)|(\/$)/g, '')
71
158
  })
72
159
 
73
- const promise = once(this.#manager, 'url')
74
- await this.#startNext(nextRoot, serverOptions)
75
- this.url = (await promise)[0]
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()
167
+ }
76
168
  }
77
169
 
78
- async stop () {
79
- const exitPromise = once(this.#manager, 'exit')
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
80
174
 
81
- this.#manager.close()
82
- await exitPromise
83
- }
175
+ this.#basePath = config.application?.basePath ? cleanBasePath(config.application?.basePath) : ''
84
176
 
85
- /* c8 ignore next 5 */
86
- async getWatchConfig () {
87
- return {
88
- enabled: false
177
+ if (command) {
178
+ return this.startWithCommand(command, loaderUrl)
89
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()
90
194
  }
91
195
 
92
- getMeta () {
93
- const deploy = this.configManager.current.deploy
94
- let composer
196
+ async #startProductionNext () {
197
+ try {
198
+ await this.#manager.inject()
199
+ const { nextStart } = await importFile(pathResolve(this.#next, './dist/cli/next-start.js'))
95
200
 
96
- if (this.url) {
97
- composer = {
98
- tcp: true,
99
- url: this.url,
100
- prefix: this.#basePath,
101
- wantsAbsoluteUrls: true
201
+ const { hostname, port } = this.serverConfig ?? {}
202
+ const serverOptions = {
203
+ hostname: hostname || '127.0.0.1',
204
+ port: port || 0
102
205
  }
103
- }
104
206
 
105
- return { deploy, composer }
106
- }
207
+ // Since we are in the same process
208
+ process.once('plt:next:config', config => {
209
+ this.#basePath = config.basePath.replace(/(^\/)|(\/$)/g, '')
210
+ })
107
211
 
108
- async #startNext (nextRoot, serverOptions) {
109
- const { nextDev } = await importFile(pathResolve(this.#next, './dist/cli/next-dev.js'))
212
+ this.#manager.register()
213
+ const serverPromise = createServerListener((this.isEntrypoint ? serverOptions?.port : undefined) ?? true)
110
214
 
111
- this.#manager.inject()
112
- await nextDev(serverOptions, 'default', this.root)
113
- this.#manager.eject()
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
+ }
114
222
  }
115
223
  }
116
224
 
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,14 +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
- deploy: schemaComponents.deploy
20
19
  },
21
- additionalProperties: false,
20
+ additionalProperties: false
22
21
  }
23
22
 
24
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.7",
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.7",
24
- "@platformatic/config": "2.0.0-alpha.7",
25
- "@platformatic/utils": "2.0.0-alpha.7"
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.7",
38
- "@platformatic/service": "2.0.0-alpha.7"
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.7.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,13 +497,11 @@
497
497
  "properties": {
498
498
  "basePath": {
499
499
  "type": "string"
500
- }
501
- },
502
- "additionalProperties": false
503
- },
504
- "deploy": {
505
- "type": "object",
506
- "properties": {
500
+ },
501
+ "outputDirectory": {
502
+ "type": "string",
503
+ "default": "dist"
504
+ },
507
505
  "include": {
508
506
  "type": "array",
509
507
  "items": {
@@ -513,19 +511,28 @@
513
511
  "dist"
514
512
  ]
515
513
  },
516
- "buildCommand": {
517
- "type": "string",
518
- "default": "npm run build"
519
- },
520
- "installCommand": {
521
- "type": "string",
522
- "default": "npm ci --omit-dev"
523
- },
524
- "startCommand": {
525
- "type": "string",
526
- "default": "npm run start"
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
527
533
  }
528
534
  },
535
+ "additionalProperties": false,
529
536
  "default": {}
530
537
  }
531
538
  },
@@ -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
- })