@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.
- package/NOTICE +13 -0
- package/README.md +13 -0
- package/config.d.ts +11 -0
- package/eslint.config.js +3 -1
- package/index.js +63 -77
- package/lib/base.js +11 -9
- package/lib/errors.js +8 -0
- package/lib/schema.js +31 -15
- package/lib/utils.js +6 -39
- package/lib/worker/child-manager.js +90 -0
- package/lib/worker/child-process.js +150 -0
- package/lib/worker/server-listener.js +29 -0
- package/package.json +18 -11
- package/schema.json +12 -0
- package/test/helper.js +116 -16
- package/lib/server.js +0 -135
- package/test/express.test.js +0 -34
- package/test/fastify.test.js +0 -34
- package/test/fixtures/express/no-build/index.js +0 -16
- package/test/fixtures/express/no-build/package.json +0 -7
- package/test/fixtures/express/no-build/platformatic.app.json +0 -3
- package/test/fixtures/express/no-build/platformatic.as-entrypoint.runtime.json +0 -19
- package/test/fixtures/express/no-build/platformatic.no-entrypoint.runtime.json +0 -16
- package/test/fixtures/express/with-build/index.js +0 -17
- package/test/fixtures/express/with-build/package.json +0 -7
- package/test/fixtures/express/with-build/platformatic.app.json +0 -3
- package/test/fixtures/express/with-build/platformatic.as-entrypoint.runtime.json +0 -19
- package/test/fixtures/express/with-build/platformatic.no-entrypoint.runtime.json +0 -16
- package/test/fixtures/fastify/no-build/index.js +0 -14
- package/test/fixtures/fastify/no-build/package.json +0 -7
- package/test/fixtures/fastify/no-build/platformatic.app.json +0 -3
- package/test/fixtures/fastify/no-build/platformatic.as-entrypoint.runtime.json +0 -19
- package/test/fixtures/fastify/no-build/platformatic.no-entrypoint.runtime.json +0 -16
- package/test/fixtures/fastify/with-build/index.js +0 -15
- package/test/fixtures/fastify/with-build/package.json +0 -7
- package/test/fixtures/fastify/with-build/platformatic.app.json +0 -3
- package/test/fixtures/fastify/with-build/platformatic.as-entrypoint.runtime.json +0 -19
- package/test/fixtures/fastify/with-build/platformatic.no-entrypoint.runtime.json +0 -16
- package/test/fixtures/nodejs/no-build/index.js +0 -21
- package/test/fixtures/nodejs/no-build/package.json +0 -7
- package/test/fixtures/nodejs/no-build/platformatic.app.json +0 -3
- package/test/fixtures/nodejs/no-build/platformatic.as-entrypoint.runtime.json +0 -19
- package/test/fixtures/nodejs/no-build/platformatic.no-entrypoint.runtime.json +0 -16
- package/test/fixtures/nodejs/no-configuration/index.js +0 -21
- package/test/fixtures/nodejs/no-configuration/platformatic.as-entrypoint.runtime.json +0 -18
- package/test/fixtures/nodejs/no-configuration/platformatic.no-entrypoint.runtime.json +0 -16
- package/test/fixtures/nodejs/with-build/index.js +0 -20
- package/test/fixtures/nodejs/with-build/package.json +0 -7
- package/test/fixtures/nodejs/with-build/platformatic.app.json +0 -3
- package/test/fixtures/nodejs/with-build/platformatic.as-entrypoint.runtime.json +0 -19
- package/test/fixtures/nodejs/with-build/platformatic.no-entrypoint.runtime.json +0 -16
- package/test/fixtures/platformatic-service/platformatic.service.json +0 -15
- package/test/fixtures/platformatic-service/plugin.js +0 -12
- 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
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
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 {
|
|
6
|
-
|
|
7
|
-
import {
|
|
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 {
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
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
|
-
|
|
45
|
-
|
|
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 (
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
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
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
81
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
|
90
|
-
await configManager.parseAndValidate()
|
|
64
|
+
const { dependencies, devDependencies } = rootPackageJson
|
|
91
65
|
|
|
92
|
-
|
|
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
|
-
|
|
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 =
|
|
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 = {
|
|
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
|
-
|
|
25
|
-
|
|
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:
|
|
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
|
-
|
|
72
|
+
setOpenapiSchema (schema) {
|
|
71
73
|
this.openapiSchema = schema
|
|
72
74
|
}
|
|
73
75
|
|
|
74
|
-
|
|
76
|
+
setGraphqlSchema (schema) {
|
|
75
77
|
this.graphqlSchema = schema
|
|
76
78
|
}
|
|
77
79
|
|
package/lib/errors.js
ADDED
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(
|
|
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:
|
|
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
|
-
|
|
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
|
+
}
|