@platformatic/basic 2.0.0-alpha.3 → 2.0.0-alpha.5

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 (54) hide show
  1. package/NOTICE +13 -0
  2. package/README.md +13 -0
  3. package/config.d.ts +11 -0
  4. package/eslint.config.js +3 -1
  5. package/index.js +63 -77
  6. package/lib/base.js +11 -9
  7. package/lib/errors.js +8 -0
  8. package/lib/schema.js +31 -15
  9. package/lib/utils.js +6 -39
  10. package/lib/worker/child-manager.js +90 -0
  11. package/lib/worker/child-process.js +150 -0
  12. package/lib/worker/server-listener.js +29 -0
  13. package/package.json +18 -11
  14. package/schema.json +12 -0
  15. package/test/helper.js +116 -16
  16. package/lib/server.js +0 -135
  17. package/test/express.test.js +0 -34
  18. package/test/fastify.test.js +0 -34
  19. package/test/fixtures/express/no-build/index.js +0 -16
  20. package/test/fixtures/express/no-build/package.json +0 -7
  21. package/test/fixtures/express/no-build/platformatic.app.json +0 -3
  22. package/test/fixtures/express/no-build/platformatic.as-entrypoint.runtime.json +0 -19
  23. package/test/fixtures/express/no-build/platformatic.no-entrypoint.runtime.json +0 -16
  24. package/test/fixtures/express/with-build/index.js +0 -17
  25. package/test/fixtures/express/with-build/package.json +0 -7
  26. package/test/fixtures/express/with-build/platformatic.app.json +0 -3
  27. package/test/fixtures/express/with-build/platformatic.as-entrypoint.runtime.json +0 -19
  28. package/test/fixtures/express/with-build/platformatic.no-entrypoint.runtime.json +0 -16
  29. package/test/fixtures/fastify/no-build/index.js +0 -14
  30. package/test/fixtures/fastify/no-build/package.json +0 -7
  31. package/test/fixtures/fastify/no-build/platformatic.app.json +0 -3
  32. package/test/fixtures/fastify/no-build/platformatic.as-entrypoint.runtime.json +0 -19
  33. package/test/fixtures/fastify/no-build/platformatic.no-entrypoint.runtime.json +0 -16
  34. package/test/fixtures/fastify/with-build/index.js +0 -15
  35. package/test/fixtures/fastify/with-build/package.json +0 -7
  36. package/test/fixtures/fastify/with-build/platformatic.app.json +0 -3
  37. package/test/fixtures/fastify/with-build/platformatic.as-entrypoint.runtime.json +0 -19
  38. package/test/fixtures/fastify/with-build/platformatic.no-entrypoint.runtime.json +0 -16
  39. package/test/fixtures/nodejs/no-build/index.js +0 -21
  40. package/test/fixtures/nodejs/no-build/package.json +0 -7
  41. package/test/fixtures/nodejs/no-build/platformatic.app.json +0 -3
  42. package/test/fixtures/nodejs/no-build/platformatic.as-entrypoint.runtime.json +0 -19
  43. package/test/fixtures/nodejs/no-build/platformatic.no-entrypoint.runtime.json +0 -16
  44. package/test/fixtures/nodejs/no-configuration/index.js +0 -21
  45. package/test/fixtures/nodejs/no-configuration/platformatic.as-entrypoint.runtime.json +0 -18
  46. package/test/fixtures/nodejs/no-configuration/platformatic.no-entrypoint.runtime.json +0 -16
  47. package/test/fixtures/nodejs/with-build/index.js +0 -20
  48. package/test/fixtures/nodejs/with-build/package.json +0 -7
  49. package/test/fixtures/nodejs/with-build/platformatic.app.json +0 -3
  50. package/test/fixtures/nodejs/with-build/platformatic.as-entrypoint.runtime.json +0 -19
  51. package/test/fixtures/nodejs/with-build/platformatic.no-entrypoint.runtime.json +0 -16
  52. package/test/fixtures/platformatic-service/platformatic.service.json +0 -15
  53. package/test/fixtures/platformatic-service/plugin.js +0 -12
  54. package/test/node.test.js +0 -66
package/NOTICE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2024 Platformatic
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
package/README.md ADDED
@@ -0,0 +1,13 @@
1
+ # @platformatic/basic
2
+
3
+ Check out the full documentation for Platformatic on [our website](https://docs.platformatic.dev).
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ npm install @platformatic/basic
9
+ ```
10
+
11
+ ## License
12
+
13
+ Apache 2.0
package/config.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ /* eslint-disable */
2
+ /**
3
+ * This file was automatically generated by json-schema-to-typescript.
4
+ * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
5
+ * and run json-schema-to-typescript to regenerate this file.
6
+ */
7
+
8
+ export interface PlatformaticStackable {
9
+ $schema?: string;
10
+ [k: string]: unknown;
11
+ }
package/eslint.config.js CHANGED
@@ -1,3 +1,5 @@
1
1
  import neostandard from 'neostandard'
2
2
 
3
- export default neostandard({})
3
+ export default neostandard({
4
+ ignores: ['test/tmp/version.js', '**/.next'],
5
+ })
package/index.js CHANGED
@@ -1,105 +1,91 @@
1
- 'use strict'
2
-
3
1
  import { existsSync } from 'node:fs'
4
2
  import { readFile } from 'node:fs/promises'
5
- import { resolve } from 'node:path'
6
-
7
- import { ConfigManager } from '@platformatic/config'
8
-
3
+ import { createRequire } from 'node:module'
4
+ import { relative, resolve } from 'node:path'
5
+ import { workerData } from 'node:worker_threads'
9
6
  import { packageJson, schema } from './lib/schema.js'
10
- import { ServerStackable } from './lib/server.js'
11
-
12
- const validFields = [
13
- 'main',
14
- 'exports',
15
- 'exports',
16
- 'exports#node',
17
- 'exports#import',
18
- 'exports#require',
19
- 'exports#default',
20
- 'exports#.#node',
21
- 'exports#.#import',
22
- 'exports#.#require',
23
- 'exports#.#default',
24
- ]
25
-
26
- const validFilesBasenames = ['index', 'main', 'app', 'application', 'server', 'start', 'bundle', 'run', 'entrypoint']
27
-
28
- async function parsePackageJson (root) {
29
- let entrypoint
30
- let packageJson
31
- let hadEntrypointField = false
7
+ import { importFile } from './lib/utils.js'
32
8
 
9
+ async function importStackablePackage (opts, pkg, autodetectDescription) {
33
10
  try {
34
- packageJson = JSON.parse(await readFile(resolve(root, 'package.json'), 'utf-8'))
35
- } catch {
36
- // No package.json, we only load the index.js file
37
- packageJson = {}
38
- }
11
+ try {
12
+ // Try regular import
13
+ return await import(pkg)
14
+ } catch (e) {
15
+ // Scope to the service
16
+ const require = createRequire(resolve(opts.context.directory, 'index.js'))
17
+ const imported = require.resolve(pkg)
18
+ return await importFile(imported)
19
+ }
20
+ } catch (e) {
21
+ const rootFolder = relative(process.cwd(), workerData.dirname)
39
22
 
40
- for (const field of validFields) {
41
- let current = packageJson
42
- const sequence = field.split('#')
23
+ let errorMessage = `Unable to import package, "${pkg}". Please add it as a dependency `
43
24
 
44
- while (current && sequence.length && typeof current !== 'string') {
45
- current = current[sequence.shift()]
25
+ if (rootFolder) {
26
+ errorMessage += `in the package.json file in the folder ${rootFolder}.`
27
+ } else {
28
+ errorMessage += 'in the root package.json file.'
46
29
  }
47
30
 
48
- if (typeof current === 'string') {
49
- entrypoint = current
50
- hadEntrypointField = true
51
- break
31
+ if (!opts.config) {
32
+ const serviceRoot = relative(process.cwd(), opts.context.directory)
33
+ errorMessage += [
34
+ '', // Do not remove this
35
+ `Platformatic has auto-detected that service ${opts.context.serviceId} ${autodetectDescription}.`,
36
+ `We suggest you create a platformatic.application.json file in the folder ${serviceRoot} with the "$schema" property set to "https://schemas.platformatic.dev/${pkg}/${packageJson.version}.json".`,
37
+ ].join('\n')
52
38
  }
53
- }
54
39
 
55
- if (!entrypoint) {
56
- for (const basename of validFilesBasenames) {
57
- for (const ext of ['js', 'mjs', 'cjs']) {
58
- const file = `${basename}.${ext}`
59
-
60
- if (existsSync(resolve(root, file))) {
61
- entrypoint = file
62
- break
63
- }
64
- }
65
-
66
- if (entrypoint) {
67
- break
68
- }
69
- }
40
+ throw new Error(errorMessage)
70
41
  }
71
-
72
- return { packageJson, entrypoint, hadEntrypointField }
73
42
  }
74
43
 
75
- function transformConfig () {
76
- if (this.current.watch === undefined) {
77
- this.current.watch = { enabled: false }
78
- }
44
+ async function buildStackable (opts) {
45
+ const root = opts.context.directory
46
+ let toImport = '@platformatic/node'
47
+ let autodetectDescription = 'is using a generic Node.js application'
79
48
 
80
- if (typeof this.current.watch !== 'object') {
81
- this.current.watch = { enabled: this.current.watch || false }
49
+ let rootPackageJson
50
+ try {
51
+ rootPackageJson = JSON.parse(await readFile(resolve(root, 'package.json'), 'utf-8'))
52
+ } catch {
53
+ rootPackageJson = {}
82
54
  }
83
- }
84
55
 
85
- export async function buildStackable (opts) {
86
- const root = opts.context.directory
87
- const { entrypoint, hadEntrypointField } = await parsePackageJson(root)
56
+ if (!opts.config) {
57
+ const candidate = resolve(root, 'platformatic.application.json')
58
+
59
+ if (existsSync(candidate)) {
60
+ opts.config = candidate
61
+ }
62
+ }
88
63
 
89
- const configManager = new ConfigManager({ schema, source: opts.config ?? {} })
90
- await configManager.parseAndValidate()
64
+ const { dependencies, devDependencies } = rootPackageJson
91
65
 
92
- const stackable = new ServerStackable(opts, root, configManager, entrypoint, hadEntrypointField)
66
+ if (dependencies?.next || devDependencies?.next) {
67
+ autodetectDescription = 'is using Next.js'
68
+ toImport = '@platformatic/next'
69
+ } else if (dependencies?.vite || devDependencies?.vite) {
70
+ autodetectDescription = 'is using Vite'
71
+ toImport = '@platformatic/vite'
72
+ }
93
73
 
94
- return stackable
74
+ const imported = await importStackablePackage(opts, toImport, autodetectDescription)
75
+ return imported.buildStackable(opts)
95
76
  }
96
77
 
97
78
  export default {
98
79
  configType: 'nodejs',
99
- configManagerConfig: {
100
- transformConfig,
101
- },
80
+ configManagerConfig: {},
102
81
  buildStackable,
103
82
  schema,
104
83
  version: packageJson.version,
105
84
  }
85
+
86
+ export * from './lib/base.js'
87
+ export * as errors from './lib/errors.js'
88
+ export { schema, schemaComponents } from './lib/schema.js'
89
+ export * from './lib/utils.js'
90
+ export * from './lib/worker/child-manager.js'
91
+ export * from './lib/worker/server-listener.js'
package/lib/base.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import pino from 'pino'
2
- import { packageJson } from './schema.js'
3
2
 
4
3
  export class BaseStackable {
5
- constructor (options, root, configManager) {
6
- this.type = 'nodejs'
4
+ constructor (type, version, options, root, configManager) {
5
+ this.type = type
6
+ this.version = version
7
7
  this.id = options.context.serviceId
8
8
  this.root = root
9
9
  this.configManager = configManager
@@ -12,7 +12,9 @@ export class BaseStackable {
12
12
  this.getGraphqlSchema = null
13
13
 
14
14
  // Setup the logger
15
- const pinoOptions = { level: this.serverConfig?.logger?.level ?? 'trace' }
15
+ const pinoOptions = {
16
+ level: (this.configManager.current.server ?? this.serverConfig)?.logger?.level ?? 'trace',
17
+ }
16
18
 
17
19
  if (this.id) {
18
20
  pinoOptions.name = this.id
@@ -21,8 +23,8 @@ export class BaseStackable {
21
23
 
22
24
  // Setup globals
23
25
  globalThis.platformatic = {
24
- setOpenAPISchema: this.setOpenAPISchema.bind(this),
25
- setGraphQLSchema: this.setGraphQLSchema.bind(this),
26
+ setOpenapiSchema: this.setOpenapiSchema.bind(this),
27
+ setGraphqlSchema: this.setGraphqlSchema.bind(this),
26
28
  }
27
29
  }
28
30
 
@@ -48,7 +50,7 @@ export class BaseStackable {
48
50
  }
49
51
 
50
52
  async getInfo () {
51
- return { type: this.type, version: packageJson.version }
53
+ return { type: this.type, version: this.version }
52
54
  }
53
55
 
54
56
  getDispatchFunc () {
@@ -67,11 +69,11 @@ export class BaseStackable {
67
69
  return this.graphqlSchema
68
70
  }
69
71
 
70
- setOpenAPISchema (schema) {
72
+ setOpenapiSchema (schema) {
71
73
  this.openapiSchema = schema
72
74
  }
73
75
 
74
- setGraphQLSchema (schema) {
76
+ setGraphqlSchema (schema) {
75
77
  this.graphqlSchema = schema
76
78
  }
77
79
 
package/lib/errors.js ADDED
@@ -0,0 +1,8 @@
1
+ import createError from '@fastify/error'
2
+
3
+ const ERROR_PREFIX = 'PLT_BASIC'
4
+
5
+ export const UnsupportedVersion = createError(
6
+ `${ERROR_PREFIX}_UNSUPPORTED_VERSION`,
7
+ '%s version %s is not supported. Please use version %s.'
8
+ )
package/lib/schema.js CHANGED
@@ -1,8 +1,31 @@
1
1
  import { schemas } from '@platformatic/utils'
2
2
  import { readFileSync } from 'node:fs'
3
- import { fileURLToPath } from 'node:url'
4
3
 
5
- export const packageJson = JSON.parse(readFileSync(fileURLToPath(new URL('../package.json', import.meta.url))))
4
+ export const packageJson = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf-8'))
5
+
6
+ const application = {
7
+ type: 'object',
8
+ properties: {
9
+ basePath: {
10
+ type: 'string',
11
+ },
12
+ },
13
+ additionalProperties: false,
14
+ }
15
+
16
+ const watch = {
17
+ anyOf: [
18
+ schemas.watch,
19
+ {
20
+ type: 'boolean',
21
+ },
22
+ {
23
+ type: 'string',
24
+ },
25
+ ],
26
+ }
27
+
28
+ export const schemaComponents = { application, watch }
6
29
 
7
30
  export const schema = {
8
31
  $id: `https://schemas.platformatic.dev/@platformatic/basic/${packageJson.version}.json`,
@@ -13,18 +36,11 @@ export const schema = {
13
36
  $schema: {
14
37
  type: 'string',
15
38
  },
16
- server: schemas.server,
17
- watch: {
18
- anyOf: [
19
- schemas.watch,
20
- {
21
- type: 'boolean',
22
- },
23
- {
24
- type: 'string',
25
- },
26
- ],
27
- },
28
39
  },
29
- additionalProperties: false,
40
+ additionalProperties: true,
41
+ }
42
+
43
+ /* c8 ignore next 3 */
44
+ if (process.argv[1] === import.meta.filename) {
45
+ console.log(JSON.stringify(schema, null, 2))
30
46
  }
package/lib/utils.js CHANGED
@@ -1,6 +1,4 @@
1
- 'use strict'
2
-
3
- import { tracingChannel } from 'node:diagnostics_channel'
1
+ import { pathToFileURL } from 'node:url'
4
2
  import { request } from 'undici'
5
3
 
6
4
  export function getServerUrl (server) {
@@ -9,42 +7,6 @@ export function getServerUrl (server) {
9
7
  return new URL(family === 'IPv6' ? `http://[${address}]:${port}` : `http://${address}:${port}`).origin
10
8
  }
11
9
 
12
- // Paolo: This is kinda hackish but there is no better way. I apologize.
13
- export function isFastify (app) {
14
- return Object.getOwnPropertySymbols(app).some(s => s.description === 'fastify.state')
15
- }
16
-
17
- export function createPortManager () {
18
- let resolve
19
- let reject
20
-
21
- const promise = new Promise((_resolve, _reject) => {
22
- resolve = _resolve
23
- reject = _reject
24
- })
25
-
26
- const subscribers = {
27
- asyncStart ({ options }) {
28
- options.port = 0
29
- },
30
- asyncEnd: ({ server }) => {
31
- resolve(server)
32
- },
33
- error: reject,
34
- }
35
-
36
- tracingChannel('net.server.listen').subscribe(subscribers)
37
-
38
- return {
39
- getServer () {
40
- return promise
41
- },
42
- destroy () {
43
- tracingChannel('net.server.listen').unsubscribe(subscribers)
44
- },
45
- }
46
- }
47
-
48
10
  export async function injectViaRequest (baseUrl, injectParams, onInject) {
49
11
  const url = new URL(injectParams.url, baseUrl).href
50
12
  const requestParams = { method: injectParams.method, headers: injectParams.headers }
@@ -75,3 +37,8 @@ export async function injectViaRequest (baseUrl, injectParams, onInject) {
75
37
  throw error
76
38
  }
77
39
  }
40
+
41
+ // This is to avoid common path/URL problems on Windows
42
+ export function importFile (path) {
43
+ return import(pathToFileURL(path))
44
+ }
@@ -0,0 +1,90 @@
1
+ import { ITC } from '@platformatic/itc'
2
+ import { subscribe, unsubscribe } from 'node:diagnostics_channel'
3
+ import { once } from 'node:events'
4
+ import { workerData } from 'node:worker_threads'
5
+ import { request } from 'undici'
6
+
7
+ export const childProcessWorkerFile = new URL('./child-process.js', import.meta.url)
8
+
9
+ export class ChildManager extends ITC {
10
+ #child
11
+ #listener
12
+ #injectedNodeOptions
13
+ #originalNodeOptions
14
+
15
+ constructor ({ loader, context }) {
16
+ super({})
17
+
18
+ const childHandler = ({ process: child }) => {
19
+ unsubscribe('child_process', childHandler)
20
+
21
+ this.#child = child
22
+ this.#child.once('exit', () => {
23
+ this.emit('exit')
24
+ })
25
+
26
+ this.listen()
27
+ }
28
+
29
+ subscribe('child_process', childHandler)
30
+
31
+ this.handle('log', message => {
32
+ workerData.loggingPort.postMessage(JSON.parse(message))
33
+ })
34
+
35
+ this.handle('fetch', this.#fetch.bind(this))
36
+
37
+ this.#prepareChildEnvironment(loader, context)
38
+ }
39
+
40
+ inject () {
41
+ process.env.NODE_OPTIONS = this.#injectedNodeOptions
42
+ }
43
+
44
+ eject () {
45
+ process.env.NODE_OPTIONS = this.#originalNodeOptions
46
+ }
47
+
48
+ _setupListener (listener) {
49
+ this.#listener = listener
50
+ this.#child.on('message', this.#listener)
51
+ }
52
+
53
+ _send (request) {
54
+ this.#child.send(request)
55
+ }
56
+
57
+ _createClosePromise () {
58
+ return once(this.#child, 'exit')
59
+ }
60
+
61
+ _close () {
62
+ this.#child.removeListener('message', this.#listener)
63
+ this.#child.kill('SIGKILL')
64
+ }
65
+
66
+ #prepareChildEnvironment (loader, context) {
67
+ this.#originalNodeOptions = process.env.NODE_OPTIONS
68
+
69
+ const loaderScript = `
70
+ import { register } from 'node:module';
71
+ globalThis.platformatic=${JSON.stringify(context).replaceAll('"', '\\"')};
72
+ register('${loader}',{ data: globalThis.platformatic });
73
+ `
74
+
75
+ this.#injectedNodeOptions = [
76
+ `--import="data:text/javascript,${loaderScript.replaceAll(/\n/g, '')}"`,
77
+ `--import=${childProcessWorkerFile}`,
78
+ process.env.NODE_OPTIONS ?? '',
79
+ ].join(' ')
80
+ }
81
+
82
+ async #fetch (opts) {
83
+ const { statusCode, headers, body } = await request(opts)
84
+
85
+ const rawPayload = Buffer.from(await body.arrayBuffer())
86
+ const payload = rawPayload.toString()
87
+
88
+ return { statusCode, headers, body: payload, payload, rawPayload }
89
+ }
90
+ }
@@ -0,0 +1,150 @@
1
+ import { ITC } from '@platformatic/itc'
2
+ import { createPinoWritable, DestinationWritable, withResolvers } from '@platformatic/utils'
3
+ import { tracingChannel } from 'node:diagnostics_channel'
4
+ import pino from 'pino'
5
+ import { getGlobalDispatcher, setGlobalDispatcher } from 'undici'
6
+
7
+ function createInterceptor (itc) {
8
+ return function (dispatch) {
9
+ return async (opts, handler) => {
10
+ let url = opts.origin
11
+ if (!(url instanceof URL)) {
12
+ url = new URL(opts.path, url)
13
+ }
14
+
15
+ // Other URLs are handled normally
16
+ if (!url.hostname.endsWith('.plt.local')) {
17
+ return dispatch(opts, handler)
18
+ }
19
+
20
+ const headers = {
21
+ ...opts?.headers,
22
+ }
23
+
24
+ delete headers.connection
25
+ delete headers['transfer-encoding']
26
+ headers.host = url.host
27
+
28
+ const requestOpts = {
29
+ ...opts,
30
+ headers,
31
+ }
32
+ delete requestOpts.dispatcher
33
+
34
+ itc
35
+ .send('fetch', requestOpts)
36
+ .then(res => {
37
+ if (res.rawPayload && !Buffer.isBuffer(res.rawPayload)) {
38
+ res.rawPayload = Buffer.from(res.rawPayload.data)
39
+ }
40
+
41
+ const headers = []
42
+ for (const [key, value] of Object.entries(res.headers)) {
43
+ if (Array.isArray(value)) {
44
+ for (const v of value) {
45
+ headers.push(key)
46
+ headers.push(v)
47
+ }
48
+ } else {
49
+ headers.push(key)
50
+ headers.push(value)
51
+ }
52
+ }
53
+
54
+ handler.onHeaders(res.statusCode, headers, () => {}, res.statusMessage)
55
+ handler.onData(res.rawPayload)
56
+ handler.onComplete([])
57
+ })
58
+ .catch(e => {
59
+ handler.onError(new Error(e.message))
60
+ })
61
+
62
+ return true
63
+ }
64
+ }
65
+ }
66
+
67
+ class ChildProcessWritable extends DestinationWritable {
68
+ #itc
69
+
70
+ constructor (options) {
71
+ const { itc, ...opts } = options
72
+
73
+ super(opts)
74
+ this.#itc = itc
75
+ }
76
+
77
+ _send (message) {
78
+ this.#itc.send('log', JSON.stringify(message))
79
+ }
80
+ }
81
+
82
+ class ChildProcess extends ITC {
83
+ #listener
84
+ #child
85
+ #logger
86
+
87
+ constructor () {
88
+ super({})
89
+
90
+ this.listen()
91
+ this.#setupLogger()
92
+ this.#setupServer()
93
+ this.#setupInterceptors()
94
+ }
95
+
96
+ _setupListener (listener) {
97
+ this.#listener = listener
98
+ process.on('message', this.#listener)
99
+ }
100
+
101
+ _send (request) {
102
+ process.send(request)
103
+ }
104
+
105
+ _createClosePromise () {
106
+ const { promise } = withResolvers()
107
+ return promise
108
+ }
109
+
110
+ _close () {
111
+ process.kill(process.pid, 'SIGKILL')
112
+ this.#child.removeListener('message', this.#listener)
113
+ }
114
+
115
+ #setupLogger () {
116
+ const destination = new ChildProcessWritable({ itc: this })
117
+ this.#logger = pino({ level: 'info', ...globalThis.platformatic.logger }, destination)
118
+
119
+ Reflect.defineProperty(process, 'stdout', { value: createPinoWritable(this.#logger, 'info') })
120
+ Reflect.defineProperty(process, 'stderr', { value: createPinoWritable(this.#logger, 'error') })
121
+ }
122
+
123
+ #setupServer () {
124
+ const subscribers = {
125
+ asyncStart ({ options }) {
126
+ options.port = 0
127
+ },
128
+ asyncEnd: ({ server }) => {
129
+ tracingChannel('net.server.listen').unsubscribe(subscribers)
130
+
131
+ const { family, address, port } = server.address()
132
+ const url = new URL(family === 'IPv6' ? `http://[${address}]:${port}` : `http://${address}:${port}`).origin
133
+
134
+ this.notify('url', url)
135
+ },
136
+ error: error => {
137
+ tracingChannel('net.server.listen').unsubscribe(subscribers)
138
+ this.notify('error', error)
139
+ },
140
+ }
141
+
142
+ tracingChannel('net.server.listen').subscribe(subscribers)
143
+ }
144
+
145
+ #setupInterceptors () {
146
+ setGlobalDispatcher(getGlobalDispatcher().compose(createInterceptor(this)))
147
+ }
148
+ }
149
+
150
+ globalThis[Symbol.for('plt.children.itc')] = new ChildProcess()
@@ -0,0 +1,29 @@
1
+ import { withResolvers } from '@platformatic/utils'
2
+ import { tracingChannel } from 'node:diagnostics_channel'
3
+
4
+ export function createServerListener () {
5
+ const { promise, resolve, reject } = withResolvers()
6
+
7
+ const subscribers = {
8
+ asyncStart ({ options }) {
9
+ options.port = 0
10
+ },
11
+ asyncEnd ({ server }) {
12
+ resolve(server)
13
+ },
14
+ error (error) {
15
+ cancel()
16
+ reject(error)
17
+ },
18
+ }
19
+
20
+ function cancel () {
21
+ tracingChannel('net.server.listen').unsubscribe(subscribers)
22
+ }
23
+
24
+ tracingChannel('net.server.listen').subscribe(subscribers)
25
+ promise.finally(cancel)
26
+ promise.cancel = resolve.bind(null, null)
27
+
28
+ return promise
29
+ }