@platformatic/basic 3.4.1 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config.d.ts +321 -1
- package/eslint.config.js +1 -3
- package/index.d.ts +81 -0
- package/index.js +4 -155
- package/lib/capability.js +734 -0
- package/lib/config.js +81 -0
- package/lib/creation.js +9 -0
- package/lib/errors.js +1 -1
- package/lib/modules.js +101 -0
- package/lib/schema.js +26 -6
- package/lib/utils.js +4 -2
- package/lib/worker/child-manager.js +82 -37
- package/lib/worker/child-process.js +320 -93
- package/lib/worker/listeners.js +10 -4
- package/package.json +19 -18
- package/schema.json +1277 -2
- package/lib/base.js +0 -276
- package/lib/worker/child-transport.js +0 -59
package/lib/config.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import {
|
|
2
|
+
listRecognizedConfigurationFiles,
|
|
3
|
+
NoConfigFileFoundError,
|
|
4
|
+
findConfigurationFile as utilsFindConfigurationFile
|
|
5
|
+
} from '@platformatic/foundation'
|
|
6
|
+
import jsonPatch from 'fast-json-patch'
|
|
7
|
+
import { stat } from 'node:fs/promises'
|
|
8
|
+
import { dirname, resolve as resolvePath } from 'node:path'
|
|
9
|
+
import { workerData } from 'node:worker_threads'
|
|
10
|
+
|
|
11
|
+
export async function findConfigurationFile (root, suffixes) {
|
|
12
|
+
const file = await utilsFindConfigurationFile(root, suffixes)
|
|
13
|
+
|
|
14
|
+
if (!file) {
|
|
15
|
+
const err = new NoConfigFileFoundError()
|
|
16
|
+
err.message = `No config file found in the directory ${root}. Please create one of the following files: ${listRecognizedConfigurationFiles(suffixes, ['json']).join(', ')}`
|
|
17
|
+
|
|
18
|
+
throw err
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return resolvePath(root, file)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function resolve (fileOrDirectory, sourceOrConfig, suffixes) {
|
|
25
|
+
if (sourceOrConfig && typeof sourceOrConfig !== 'string') {
|
|
26
|
+
return {
|
|
27
|
+
root: fileOrDirectory,
|
|
28
|
+
source: sourceOrConfig
|
|
29
|
+
}
|
|
30
|
+
} else if (typeof fileOrDirectory === 'string' && typeof sourceOrConfig === 'string') {
|
|
31
|
+
return {
|
|
32
|
+
root: fileOrDirectory,
|
|
33
|
+
source: resolvePath(fileOrDirectory, sourceOrConfig)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const fileInfo = await stat(fileOrDirectory)
|
|
39
|
+
|
|
40
|
+
if (fileInfo.isFile()) {
|
|
41
|
+
return {
|
|
42
|
+
root: dirname(fileOrDirectory),
|
|
43
|
+
source: fileOrDirectory
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
} catch {
|
|
47
|
+
// No-op
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
root: fileOrDirectory,
|
|
52
|
+
source: await findConfigurationFile(fileOrDirectory, suffixes)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function transform (config) {
|
|
57
|
+
const patch = workerData?.applicationConfig?.configPatch
|
|
58
|
+
|
|
59
|
+
if (!config) {
|
|
60
|
+
return config
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (Array.isArray(patch)) {
|
|
64
|
+
config = jsonPatch.applyPatch(config, patch).newDocument
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (config.watch === undefined) {
|
|
68
|
+
config.watch = { enabled: workerData?.config?.watch ?? false }
|
|
69
|
+
} else if (typeof config.watch !== 'object') {
|
|
70
|
+
config.watch = { enabled: config.watch || false }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return config
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const validationOptions = {
|
|
77
|
+
useDefaults: true,
|
|
78
|
+
coerceTypes: true,
|
|
79
|
+
allErrors: true,
|
|
80
|
+
strict: false
|
|
81
|
+
}
|
package/lib/creation.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { resolve } from './config.js'
|
|
2
|
+
import { importCapabilityAndConfig } from './modules.js'
|
|
3
|
+
|
|
4
|
+
export async function create (fileOrDirectory, sourceOrConfig, context) {
|
|
5
|
+
const { root, source } = await resolve(fileOrDirectory, sourceOrConfig)
|
|
6
|
+
const { capability } = await importCapabilityAndConfig(root, source, context)
|
|
7
|
+
|
|
8
|
+
return capability.create(root, source, context)
|
|
9
|
+
}
|
package/lib/errors.js
CHANGED
package/lib/modules.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { detectApplicationType, findConfigurationFile } from '@platformatic/foundation'
|
|
2
|
+
import { readFile } from 'node:fs/promises'
|
|
3
|
+
import { createRequire } from 'node:module'
|
|
4
|
+
import { relative, resolve } from 'node:path'
|
|
5
|
+
import { workerData } from 'node:worker_threads'
|
|
6
|
+
import pino from 'pino'
|
|
7
|
+
import { packageJson } from './schema.js'
|
|
8
|
+
import { importFile } from './utils.js'
|
|
9
|
+
|
|
10
|
+
const importCapabilityPackageMarker = '__pltImportCapabilityPackage.js'
|
|
11
|
+
|
|
12
|
+
export function isImportFailedError (error, pkg) {
|
|
13
|
+
if (error.code !== 'ERR_MODULE_NOT_FOUND' && error.code !== 'MODULE_NOT_FOUND') {
|
|
14
|
+
return false
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const match = error.message.match(/Cannot find package '(.+)' imported from (.+)/)
|
|
18
|
+
|
|
19
|
+
return match?.[1] === pkg || error.requireStack?.[0].endsWith(importCapabilityPackageMarker)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function importCapabilityPackage (directory, pkg) {
|
|
23
|
+
let imported
|
|
24
|
+
try {
|
|
25
|
+
try {
|
|
26
|
+
// Try regular import
|
|
27
|
+
imported = await import(pkg)
|
|
28
|
+
} catch (e) {
|
|
29
|
+
if (!isImportFailedError(e, pkg)) {
|
|
30
|
+
throw e
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Scope to the application
|
|
34
|
+
const require = createRequire(resolve(directory, importCapabilityPackageMarker))
|
|
35
|
+
const toImport = require.resolve(pkg)
|
|
36
|
+
imported = await importFile(toImport)
|
|
37
|
+
}
|
|
38
|
+
} catch (e) {
|
|
39
|
+
if (!isImportFailedError(e, pkg)) {
|
|
40
|
+
throw e
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const applicationDirectory = workerData ? relative(workerData.dirname, directory) : directory
|
|
44
|
+
throw new Error(
|
|
45
|
+
`Unable to import package '${pkg}'. Please add it as a dependency in the package.json file in the folder ${applicationDirectory}.`
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return imported.default ?? imported
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function importCapabilityAndConfig (root, config, context) {
|
|
53
|
+
let rootPackageJson
|
|
54
|
+
try {
|
|
55
|
+
rootPackageJson = JSON.parse(await readFile(resolve(root, 'package.json'), 'utf-8'))
|
|
56
|
+
} catch {
|
|
57
|
+
rootPackageJson = {}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const hadConfig = !!config
|
|
61
|
+
|
|
62
|
+
if (!config) {
|
|
63
|
+
config = await findConfigurationFile(root, 'application')
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const appType = await detectApplicationType(root, rootPackageJson)
|
|
67
|
+
|
|
68
|
+
if (!appType) {
|
|
69
|
+
throw new Error(`Unable to detect application type in ${root}.`)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const { label, name: moduleName } = appType
|
|
73
|
+
|
|
74
|
+
if (context) {
|
|
75
|
+
const applicationRoot = relative(process.cwd(), root)
|
|
76
|
+
|
|
77
|
+
if (!hadConfig && context.applicationId && !(await findConfigurationFile(root)) && context.worker?.index === 0) {
|
|
78
|
+
const autodetectDescription =
|
|
79
|
+
moduleName === '@platformatic/node' ? 'is a generic Node.js application' : `is using ${label}`
|
|
80
|
+
|
|
81
|
+
const logger = pino({ level: context.serverConfig?.logger?.level ?? 'warn', name: context.applicationId })
|
|
82
|
+
|
|
83
|
+
logger.warn(`We have auto-detected that application "${context.applicationId}" ${autodetectDescription}.`)
|
|
84
|
+
logger.warn(
|
|
85
|
+
`We suggest you create a watt.json or a platformatic.json file in the folder ${applicationRoot} with the "$schema" property set to "https://schemas.platformatic.dev/${moduleName}/${packageJson.version}.json".`
|
|
86
|
+
)
|
|
87
|
+
logger.warn(`Also don't forget to add "${moduleName}" to the application dependencies.`)
|
|
88
|
+
logger.warn('You can also run "wattpm import" to do this automatically.\n')
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const capability = await importCapabilityPackage(root, moduleName)
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
capability,
|
|
96
|
+
config,
|
|
97
|
+
autodetectDescription:
|
|
98
|
+
moduleName === '@platformatic/node' ? 'is a generic Node.js application' : `is using ${label}`,
|
|
99
|
+
moduleName
|
|
100
|
+
}
|
|
101
|
+
}
|
package/lib/schema.js
CHANGED
|
@@ -1,11 +1,27 @@
|
|
|
1
|
-
import { schemaComponents as utilsSchemaComponents } from '@platformatic/
|
|
1
|
+
import { schemaComponents as utilsSchemaComponents } from '@platformatic/foundation'
|
|
2
2
|
import { readFileSync } from 'node:fs'
|
|
3
3
|
|
|
4
4
|
export const packageJson = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf-8'))
|
|
5
|
+
export const version = packageJson.version
|
|
5
6
|
|
|
7
|
+
// This is used by applications to have common properties.
|
|
6
8
|
const application = {
|
|
9
|
+
type: 'object',
|
|
10
|
+
properties: {},
|
|
11
|
+
additionalProperties: false,
|
|
12
|
+
required: [],
|
|
13
|
+
default: {}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/*
|
|
17
|
+
For legacy purposes, when an application is buildable (like Astro), it should use for the application properties.
|
|
18
|
+
Make sure this always extends the `application` schema above.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const buildableApplication = {
|
|
7
22
|
type: 'object',
|
|
8
23
|
properties: {
|
|
24
|
+
...application.properties,
|
|
9
25
|
basePath: {
|
|
10
26
|
type: 'string'
|
|
11
27
|
},
|
|
@@ -28,7 +44,7 @@ const application = {
|
|
|
28
44
|
default: 'npm ci --omit-dev'
|
|
29
45
|
},
|
|
30
46
|
// All the following options purposely don't have a default so
|
|
31
|
-
// that
|
|
47
|
+
// that capabilities can detect if the user explicitly set them.
|
|
32
48
|
build: {
|
|
33
49
|
type: 'string'
|
|
34
50
|
},
|
|
@@ -44,7 +60,10 @@ const application = {
|
|
|
44
60
|
}
|
|
45
61
|
},
|
|
46
62
|
additionalProperties: false,
|
|
47
|
-
|
|
63
|
+
required: [...application.required],
|
|
64
|
+
default: {
|
|
65
|
+
...application.default
|
|
66
|
+
}
|
|
48
67
|
}
|
|
49
68
|
|
|
50
69
|
const watch = {
|
|
@@ -59,17 +78,18 @@ const watch = {
|
|
|
59
78
|
]
|
|
60
79
|
}
|
|
61
80
|
|
|
62
|
-
export const schemaComponents = { application, watch }
|
|
81
|
+
export const schemaComponents = { application, buildableApplication, watch }
|
|
63
82
|
|
|
64
83
|
export const schema = {
|
|
65
84
|
$id: `https://schemas.platformatic.dev/@platformatic/basic/${packageJson.version}.json`,
|
|
66
85
|
$schema: 'http://json-schema.org/draft-07/schema#',
|
|
67
|
-
title: 'Platformatic
|
|
86
|
+
title: 'Platformatic Basic Config',
|
|
68
87
|
type: 'object',
|
|
69
88
|
properties: {
|
|
70
89
|
$schema: {
|
|
71
90
|
type: 'string'
|
|
72
|
-
}
|
|
91
|
+
},
|
|
92
|
+
runtime: utilsSchemaComponents.wrappedRuntime
|
|
73
93
|
},
|
|
74
94
|
additionalProperties: true
|
|
75
95
|
}
|
package/lib/utils.js
CHANGED
|
@@ -62,8 +62,10 @@ export function importFile (path) {
|
|
|
62
62
|
/* c8 ignore next 6 */
|
|
63
63
|
export function resolvePackage (root, pkg) {
|
|
64
64
|
const require = createRequire(root)
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
// We need to add the main module paths to the require.resolve call
|
|
66
|
+
// Note that `require.main` is not defined in `next` if we set sthe instrumentation hook reequired for ESM applications.
|
|
67
|
+
// see: https://github.com/open-telemetry/opentelemetry-js/blob/main/doc/esm-support.md#instrumentation-hook-required-for-esm
|
|
68
|
+
return require.resolve(pkg, { paths: [root, ...(require.main?.paths || [])] })
|
|
67
69
|
}
|
|
68
70
|
|
|
69
71
|
export function cleanBasePath (basePath) {
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { createDirectory, ensureLoggableError } from '@platformatic/foundation'
|
|
2
|
+
import { ITC } from '@platformatic/itc/lib/index.js'
|
|
3
3
|
import { once } from 'node:events'
|
|
4
4
|
import { rm, writeFile } from 'node:fs/promises'
|
|
5
5
|
import { createServer } from 'node:http'
|
|
6
|
-
import { register } from 'node:module'
|
|
6
|
+
import { createRequire, register } from 'node:module'
|
|
7
7
|
import { platform, tmpdir } from 'node:os'
|
|
8
|
-
import { dirname, resolve } from 'node:path'
|
|
9
|
-
import {
|
|
8
|
+
import { dirname, join, resolve } from 'node:path'
|
|
9
|
+
import { pathToFileURL } from 'node:url'
|
|
10
10
|
import { request } from 'undici'
|
|
11
11
|
import { WebSocketServer } from 'ws'
|
|
12
12
|
import { exitCodes } from '../errors.js'
|
|
13
13
|
import { ensureFileUrl } from '../utils.js'
|
|
14
|
+
|
|
14
15
|
export const isWindows = platform() === 'win32'
|
|
15
16
|
|
|
16
17
|
// In theory we could use the context.id to namespace even more, but due to
|
|
@@ -41,9 +42,11 @@ export class ChildManager extends ITC {
|
|
|
41
42
|
#scripts
|
|
42
43
|
#logger
|
|
43
44
|
#server
|
|
45
|
+
#websocketServer
|
|
44
46
|
#socketPath
|
|
45
47
|
#clients
|
|
46
48
|
#requests
|
|
49
|
+
#currentSender
|
|
47
50
|
#currentClient
|
|
48
51
|
#listener
|
|
49
52
|
#originalNodeOptions
|
|
@@ -56,19 +59,8 @@ export class ChildManager extends ITC {
|
|
|
56
59
|
scripts ??= []
|
|
57
60
|
|
|
58
61
|
super({
|
|
59
|
-
name: 'child-manager',
|
|
60
62
|
...itcOpts,
|
|
61
|
-
|
|
62
|
-
log: message => {
|
|
63
|
-
/* c8 ignore next */
|
|
64
|
-
const logs = Array.isArray(message.logs) ? message.logs : [message.logs]
|
|
65
|
-
this._forwardLogs(logs)
|
|
66
|
-
},
|
|
67
|
-
fetch: request => {
|
|
68
|
-
return this.#fetch(request)
|
|
69
|
-
},
|
|
70
|
-
...itcOpts.handlers
|
|
71
|
-
}
|
|
63
|
+
name: 'child-manager'
|
|
72
64
|
})
|
|
73
65
|
|
|
74
66
|
this.#id = generateChildrenId(context)
|
|
@@ -77,7 +69,7 @@ export class ChildManager extends ITC {
|
|
|
77
69
|
this.#scripts = scripts
|
|
78
70
|
this.#originalNodeOptions = process.env.NODE_OPTIONS
|
|
79
71
|
this.#logger = globalThis.platformatic.logger
|
|
80
|
-
this.#server = createServer()
|
|
72
|
+
this.#server = createServer(this.#childProcessFetchHandler.bind(this))
|
|
81
73
|
this.#socketPath ??= getSocketPath(this.#id)
|
|
82
74
|
this.#clients = new Set()
|
|
83
75
|
this.#requests = new Map()
|
|
@@ -90,18 +82,21 @@ export class ChildManager extends ITC {
|
|
|
90
82
|
await createDirectory(dirname(this.#socketPath))
|
|
91
83
|
}
|
|
92
84
|
|
|
93
|
-
|
|
85
|
+
this.#websocketServer = new WebSocketServer({ server: this.#server })
|
|
94
86
|
|
|
95
|
-
|
|
87
|
+
this.#websocketServer.on('connection', ws => {
|
|
96
88
|
this.#clients.add(ws)
|
|
97
89
|
|
|
98
90
|
ws.on('message', raw => {
|
|
99
91
|
try {
|
|
92
|
+
this.#currentSender = ws
|
|
100
93
|
const message = JSON.parse(raw)
|
|
101
94
|
this.#requests.set(message.reqId, ws)
|
|
102
95
|
this.#listener(message)
|
|
103
96
|
} catch (error) {
|
|
104
97
|
this.#handleUnexpectedError(error, 'Handling a message failed.', exitCodes.MANAGER_MESSAGE_HANDLING_FAILED)
|
|
98
|
+
} finally {
|
|
99
|
+
this.#currentSender = null
|
|
105
100
|
}
|
|
106
101
|
})
|
|
107
102
|
|
|
@@ -124,24 +119,25 @@ export class ChildManager extends ITC {
|
|
|
124
119
|
})
|
|
125
120
|
}
|
|
126
121
|
|
|
127
|
-
async close (
|
|
122
|
+
async close () {
|
|
128
123
|
if (this.#dataPath) {
|
|
129
124
|
await rm(this.#dataPath, { force: true })
|
|
130
125
|
}
|
|
131
126
|
|
|
132
127
|
for (const client of this.#clients) {
|
|
133
|
-
|
|
134
|
-
|
|
128
|
+
client.close()
|
|
129
|
+
await once(client, 'close')
|
|
135
130
|
}
|
|
136
131
|
|
|
137
|
-
this.#
|
|
138
|
-
|
|
132
|
+
await this.#closeServer(this.#websocketServer)
|
|
133
|
+
await this.#closeServer(this.#server)
|
|
134
|
+
await super.close()
|
|
139
135
|
}
|
|
140
136
|
|
|
141
137
|
async inject () {
|
|
142
138
|
await this.listen()
|
|
143
139
|
|
|
144
|
-
// Serialize data into a JSON file for the
|
|
140
|
+
// Serialize data into a JSON file for the capability to use
|
|
145
141
|
this.#dataPath = resolve(tmpdir(), 'platformatic', 'runtimes', `${this.#id}.json`)
|
|
146
142
|
await createDirectory(dirname(this.#dataPath))
|
|
147
143
|
|
|
@@ -161,8 +157,19 @@ export class ChildManager extends ITC {
|
|
|
161
157
|
)
|
|
162
158
|
|
|
163
159
|
process.env.PLT_MANAGER_ID = this.#id
|
|
164
|
-
|
|
165
|
-
|
|
160
|
+
|
|
161
|
+
const nodeOptions = process.env.NODE_OPTIONS ?? ''
|
|
162
|
+
const childProcessInclude = `--import="${new URL('./child-process.js', import.meta.url)}"`
|
|
163
|
+
|
|
164
|
+
let telemetryInclude = ''
|
|
165
|
+
if (this.#context.telemetryConfig && this.#context.telemetryConfig.enabled !== false) {
|
|
166
|
+
const require = createRequire(import.meta.url)
|
|
167
|
+
const telemetryPath = require.resolve('@platformatic/telemetry')
|
|
168
|
+
const openTelemetrySetupPath = join(telemetryPath, '..', 'lib', 'node-telemetry.js')
|
|
169
|
+
telemetryInclude = `--import="${pathToFileURL(openTelemetrySetupPath)}"`
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
process.env.NODE_OPTIONS = `${telemetryInclude} ${childProcessInclude} ${nodeOptions}`.trim()
|
|
166
173
|
}
|
|
167
174
|
|
|
168
175
|
async eject () {
|
|
@@ -179,14 +186,24 @@ export class ChildManager extends ITC {
|
|
|
179
186
|
}
|
|
180
187
|
|
|
181
188
|
register () {
|
|
189
|
+
Object.assign(globalThis.platformatic, this.#context)
|
|
182
190
|
register(this.#loader, { data: this.#context })
|
|
183
191
|
}
|
|
184
192
|
|
|
193
|
+
emit (...args) {
|
|
194
|
+
super.emit(...args, this.#currentSender)
|
|
195
|
+
}
|
|
196
|
+
|
|
185
197
|
send (client, name, message) {
|
|
186
198
|
this.#currentClient = client
|
|
187
199
|
return super.send(name, message)
|
|
188
200
|
}
|
|
189
201
|
|
|
202
|
+
notify (client, name, message) {
|
|
203
|
+
this.#currentClient = client
|
|
204
|
+
return super.notify(name, message)
|
|
205
|
+
}
|
|
206
|
+
|
|
190
207
|
_send (message, stringify = true) {
|
|
191
208
|
if (!this.#currentClient) {
|
|
192
209
|
this.#currentClient = this.#requests.get(message.reqId)
|
|
@@ -213,22 +230,50 @@ export class ChildManager extends ITC {
|
|
|
213
230
|
this.#server.close()
|
|
214
231
|
}
|
|
215
232
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
workerData.loggingPort.postMessage({ logs: logs.map(m => JSON.stringify(m)) })
|
|
219
|
-
}
|
|
233
|
+
async #childProcessFetchHandler (req, res) {
|
|
234
|
+
const { url, headers } = req
|
|
220
235
|
|
|
221
|
-
|
|
222
|
-
const { statusCode, headers, body } = await request(opts)
|
|
236
|
+
const requestOptions = { method: req.method, headers }
|
|
223
237
|
|
|
224
|
-
|
|
225
|
-
|
|
238
|
+
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
239
|
+
requestOptions.body = req
|
|
240
|
+
}
|
|
226
241
|
|
|
227
|
-
|
|
242
|
+
try {
|
|
243
|
+
const {
|
|
244
|
+
statusCode,
|
|
245
|
+
headers: responseHeaders,
|
|
246
|
+
body
|
|
247
|
+
} = await request(`http://${headers.host}${url ?? '/'}`, requestOptions)
|
|
248
|
+
|
|
249
|
+
res.writeHead(statusCode, responseHeaders)
|
|
250
|
+
body.pipe(res)
|
|
251
|
+
} catch (error) {
|
|
252
|
+
res.writeHead(502, { 'content-type': 'application/json' })
|
|
253
|
+
res.end(JSON.stringify(ensureLoggableError(error)))
|
|
254
|
+
}
|
|
228
255
|
}
|
|
229
256
|
|
|
230
257
|
#handleUnexpectedError (error, message, exitCode) {
|
|
231
258
|
this.#logger.error({ err: ensureLoggableError(error) }, message)
|
|
232
259
|
process.exit(exitCode)
|
|
233
260
|
}
|
|
261
|
+
|
|
262
|
+
#closeServer (server) {
|
|
263
|
+
return new Promise((resolve, reject) => {
|
|
264
|
+
if (!server || server.listening === false) {
|
|
265
|
+
resolve()
|
|
266
|
+
return
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
server.close(err => {
|
|
270
|
+
if (err) {
|
|
271
|
+
reject(err)
|
|
272
|
+
return
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
resolve()
|
|
276
|
+
})
|
|
277
|
+
})
|
|
278
|
+
}
|
|
234
279
|
}
|